Compare commits

...

17 commits

Author SHA1 Message Date
James Rich
f21d8af9ae
fix(transport): improve BLE / TCP / USB reconnect and handshake resilience (#5196)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-20 17:34:16 +00:00
James Rich
a90cb2d89e
chore: Scheduled updates (Firmware, Hardware, Translations, Graphs) (#5195)
Co-authored-by: github-merge-queue <118344674+github-merge-queue@users.noreply.github.com>
2026-04-20 17:32:58 +00:00
Copilot
7492a33cf8
Fix node-details remove action to preserve confirmation flow (#5192)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: jamesarich <2199651+jamesarich@users.noreply.github.com>
Co-authored-by: James Rich <james.a.rich@gmail.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-20 15:59:20 +00:00
James Rich
2b47da3b61
chore: Scheduled updates (Firmware, Hardware, Translations, Graphs) (#5193)
Co-authored-by: github-merge-queue <118344674+github-merge-queue@users.noreply.github.com>
2026-04-20 07:40:08 -05:00
renovate[bot]
3322257cfd
chore(deps): update plugin com.gradle.develocity to v4.4.1 (#5194)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-20 11:47:09 +00:00
James Rich
99e7407a90
chore: Scheduled updates (Firmware, Hardware, Translations, Graphs) (#5189)
Co-authored-by: github-merge-queue <118344674+github-merge-queue@users.noreply.github.com>
2026-04-19 20:07:52 +00:00
renovate[bot]
9dd57725f2
chore(deps): update vico to v3.2.0-next.1 (#5191)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-19 12:31:11 -05:00
renovate[bot]
2c1984ace5
chore(deps): update fastlane to v2.233.0 (#5190)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-19 16:30:34 +00:00
James Rich
94856d257f
chore: Scheduled updates (Firmware, Hardware, Translations, Graphs) (#5186)
Co-authored-by: github-merge-queue <118344674+github-merge-queue@users.noreply.github.com>
2026-04-18 12:09:22 +00:00
James Rich
84fe24467f
fix(widget): drive updates via debounced state observer (#5185)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-18 04:11:32 +00:00
renovate[bot]
68a414b75b
chore(deps): update compose-multiplatform to v1.11.0-rc01 (#5184)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-17 22:00:34 -05:00
James Rich
4257e7b7e4
chore(deps): split androidx-compose version ref from CMP (#5183)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-17 21:41:36 -05:00
James Rich
14e86b90f1
feat(mqtt): adopt mqttastic-client-kmp 0.2.0 — disconnect reasons + Test Connection (#5181)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-17 21:33:55 -05:00
James Rich
ef0e159abb
chore: Scheduled updates (Firmware, Hardware, Translations, Graphs) (#5177)
Co-authored-by: github-merge-queue <118344674+github-merge-queue@users.noreply.github.com>
2026-04-17 21:20:58 -05:00
James Rich
61d7f6fef3
fix(deps): pin androidx-compose runtime-tracing/ui-test to CMP version (#5179)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-17 15:46:59 -05:00
James Rich
a273dc6623
Revert "diag(r8): disable minify for release builds (animation-freeze diagnostic)" (#5176)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-17 15:07:54 -05:00
James Rich
c866f60b59
diag(r8): disable minify for release builds (animation-freeze diagnostic) (#5174)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-17 18:36:33 +00:00
78 changed files with 1164 additions and 255 deletions

View file

@ -56,6 +56,15 @@
"changelogUrl": "https://github.com/meshtastic/protobufs/compare/{{currentDigest}}...{{newDigest}}", "changelogUrl": "https://github.com/meshtastic/protobufs/compare/{{currentDigest}}...{{newDigest}}",
"automerge": true "automerge": true
}, },
{
"description": "Group CMP and the androidx.compose artifacts that track it so Renovate bumps them together (see PR #5180)",
"groupName": "compose-multiplatform",
"matchPackageNames": [
"/^org\\.jetbrains\\.compose/",
"androidx.compose.runtime:runtime-tracing",
"androidx.compose.ui:ui-test-manifest"
]
},
{ {
"description": "Restrict sensitive infrastructure to manual minor updates", "description": "Restrict sensitive infrastructure to manual minor updates",
"matchUpdateTypes": [ "matchUpdateTypes": [

1
.gitignore vendored
View file

@ -55,3 +55,4 @@ wireless-install.sh
firebase-debug.log firebase-debug.log
.agent_plans/ .agent_plans/
.agent_refs/ .agent_refs/
.agent_artifacts/

View file

@ -3,13 +3,13 @@ GEM
specs: specs:
CFPropertyList (3.0.8) CFPropertyList (3.0.8)
abbrev (0.1.2) abbrev (0.1.2)
addressable (2.8.8) addressable (2.9.0)
public_suffix (>= 2.0.2, < 8.0) public_suffix (>= 2.0.2, < 8.0)
artifactory (3.0.17) artifactory (3.0.17)
atomos (0.1.3) atomos (0.1.3)
aws-eventstream (1.4.0) aws-eventstream (1.4.0)
aws-partitions (1.1213.0) aws-partitions (1.1240.0)
aws-sdk-core (3.242.0) aws-sdk-core (3.245.0)
aws-eventstream (~> 1, >= 1.3.0) aws-eventstream (~> 1, >= 1.3.0)
aws-partitions (~> 1, >= 1.992.0) aws-partitions (~> 1, >= 1.992.0)
aws-sigv4 (~> 1.9) aws-sigv4 (~> 1.9)
@ -17,11 +17,11 @@ GEM
bigdecimal bigdecimal
jmespath (~> 1, >= 1.6.1) jmespath (~> 1, >= 1.6.1)
logger logger
aws-sdk-kms (1.121.0) aws-sdk-kms (1.123.0)
aws-sdk-core (~> 3, >= 3.241.4) aws-sdk-core (~> 3, >= 3.244.0)
aws-sigv4 (~> 1.5) aws-sigv4 (~> 1.5)
aws-sdk-s3 (1.213.0) aws-sdk-s3 (1.219.0)
aws-sdk-core (~> 3, >= 3.241.4) aws-sdk-core (~> 3, >= 3.244.0)
aws-sdk-kms (~> 1) aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.5) aws-sigv4 (~> 1.5)
aws-sigv4 (1.12.1) aws-sigv4 (1.12.1)
@ -29,7 +29,7 @@ GEM
babosa (1.0.4) babosa (1.0.4)
base64 (0.2.0) base64 (0.2.0)
benchmark (0.5.0) benchmark (0.5.0)
bigdecimal (4.0.1) bigdecimal (4.1.2)
claide (1.1.0) claide (1.1.0)
colored (1.2) colored (1.2)
colored2 (3.1.2) colored2 (3.1.2)
@ -68,11 +68,11 @@ GEM
faraday-net_http_persistent (1.2.0) faraday-net_http_persistent (1.2.0)
faraday-patron (1.0.0) faraday-patron (1.0.0)
faraday-rack (1.0.0) faraday-rack (1.0.0)
faraday-retry (1.0.3) faraday-retry (1.0.4)
faraday_middleware (1.2.1) faraday_middleware (1.2.1)
faraday (~> 1.0) faraday (~> 1.0)
fastimage (2.4.0) fastimage (2.4.1)
fastlane (2.232.2) fastlane (2.233.0)
CFPropertyList (>= 2.3, < 4.0.0) CFPropertyList (>= 2.3, < 4.0.0)
abbrev (~> 0.1.2) abbrev (~> 0.1.2)
addressable (>= 2.8, < 3.0.0) addressable (>= 2.8, < 3.0.0)
@ -92,7 +92,7 @@ GEM
faraday-cookie_jar (~> 0.0.6) faraday-cookie_jar (~> 0.0.6)
faraday_middleware (~> 1.0) faraday_middleware (~> 1.0)
fastimage (>= 2.1.0, < 3.0.0) fastimage (>= 2.1.0, < 3.0.0)
fastlane-sirp (>= 1.0.0) fastlane-sirp (>= 1.1.0)
gh_inspector (>= 1.1.2, < 2.0.0) gh_inspector (>= 1.1.2, < 2.0.0)
google-apis-androidpublisher_v3 (~> 0.3) google-apis-androidpublisher_v3 (~> 0.3)
google-apis-playcustomapp_v1 (~> 0.1) google-apis-playcustomapp_v1 (~> 0.1)
@ -122,10 +122,9 @@ GEM
xcodeproj (>= 1.13.0, < 2.0.0) xcodeproj (>= 1.13.0, < 2.0.0)
xcpretty (~> 0.4.1) xcpretty (~> 0.4.1)
xcpretty-travis-formatter (>= 0.0.3, < 2.0.0) xcpretty-travis-formatter (>= 0.0.3, < 2.0.0)
fastlane-sirp (1.0.0) fastlane-sirp (1.1.0)
sysrandom (~> 1.0)
gh_inspector (1.1.3) gh_inspector (1.1.3)
google-apis-androidpublisher_v3 (0.95.0) google-apis-androidpublisher_v3 (0.99.0)
google-apis-core (>= 0.15.0, < 2.a) google-apis-core (>= 0.15.0, < 2.a)
google-apis-core (0.18.0) google-apis-core (0.18.0)
addressable (~> 2.5, >= 2.5.1) addressable (~> 2.5, >= 2.5.1)
@ -139,15 +138,15 @@ GEM
google-apis-core (>= 0.15.0, < 2.a) google-apis-core (>= 0.15.0, < 2.a)
google-apis-playcustomapp_v1 (0.17.0) google-apis-playcustomapp_v1 (0.17.0)
google-apis-core (>= 0.15.0, < 2.a) google-apis-core (>= 0.15.0, < 2.a)
google-apis-storage_v1 (0.59.0) google-apis-storage_v1 (0.61.0)
google-apis-core (>= 0.15.0, < 2.a) google-apis-core (>= 0.15.0, < 2.a)
google-cloud-core (1.8.0) google-cloud-core (1.8.0)
google-cloud-env (>= 1.0, < 3.a) google-cloud-env (>= 1.0, < 3.a)
google-cloud-errors (~> 1.0) google-cloud-errors (~> 1.0)
google-cloud-env (2.1.1) google-cloud-env (2.1.1)
faraday (>= 1.0, < 3.a) faraday (>= 1.0, < 3.a)
google-cloud-errors (1.5.0) google-cloud-errors (1.6.0)
google-cloud-storage (1.58.0) google-cloud-storage (1.59.0)
addressable (~> 2.8) addressable (~> 2.8)
digest-crc (~> 0.4) digest-crc (~> 0.4)
google-apis-core (>= 0.18, < 2) google-apis-core (>= 0.18, < 2)
@ -169,13 +168,13 @@ GEM
httpclient (2.9.0) httpclient (2.9.0)
mutex_m mutex_m
jmespath (1.6.2) jmespath (1.6.2)
json (2.18.1) json (2.19.4)
jwt (2.10.2) jwt (2.10.2)
base64 base64
logger (1.7.0) logger (1.7.0)
mini_magick (4.13.2) mini_magick (4.13.2)
mini_mime (1.1.5) mini_mime (1.1.5)
multi_json (1.19.1) multi_json (1.20.1)
multipart-post (2.4.1) multipart-post (2.4.1)
mutex_m (0.3.0) mutex_m (0.3.0)
nanaimo (0.4.0) nanaimo (0.4.0)
@ -185,13 +184,13 @@ GEM
os (1.1.4) os (1.1.4)
ostruct (0.6.3) ostruct (0.6.3)
plist (3.7.2) plist (3.7.2)
public_suffix (7.0.2) public_suffix (7.0.5)
rake (13.3.1) rake (13.4.2)
representable (3.2.0) representable (3.2.0)
declarative (< 0.1.0) declarative (< 0.1.0)
trailblazer-option (>= 0.1.1, < 0.2.0) trailblazer-option (>= 0.1.1, < 0.2.0)
uber (< 0.2.0) uber (< 0.2.0)
retriable (3.1.2) retriable (3.4.1)
rexml (3.4.4) rexml (3.4.4)
rouge (3.28.0) rouge (3.28.0)
ruby2_keywords (0.0.5) ruby2_keywords (0.0.5)
@ -205,7 +204,6 @@ GEM
simctl (1.6.10) simctl (1.6.10)
CFPropertyList CFPropertyList
naturally naturally
sysrandom (1.0.5)
terminal-notifier (2.0.0) terminal-notifier (2.0.0)
terminal-table (3.0.2) terminal-table (3.0.2)
unicode-display_width (>= 1.1.1, < 3) unicode-display_width (>= 1.1.1, < 3)

View file

@ -59,6 +59,7 @@ import org.meshtastic.app.node.metrics.getTracerouteMapOverlayInsets
import org.meshtastic.app.ui.MainScreen import org.meshtastic.app.ui.MainScreen
import org.meshtastic.core.barcode.rememberBarcodeScanner import org.meshtastic.core.barcode.rememberBarcodeScanner
import org.meshtastic.core.navigation.DEEP_LINK_BASE_URI import org.meshtastic.core.navigation.DEEP_LINK_BASE_URI
import org.meshtastic.core.network.repository.UsbRepository
import org.meshtastic.core.nfc.NfcScannerEffect import org.meshtastic.core.nfc.NfcScannerEffect
import org.meshtastic.core.resources.Res import org.meshtastic.core.resources.Res
import org.meshtastic.core.resources.channel_invalid import org.meshtastic.core.resources.channel_invalid
@ -91,6 +92,8 @@ import org.meshtastic.feature.node.metrics.TracerouteMapScreen
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
private val model: UIViewModel by viewModel() private val model: UIViewModel by viewModel()
private val usbRepository: UsbRepository by inject()
/** /**
* Activity-lifecycle-aware client that binds to the mesh service. Note: This is used implicitly as it registers * Activity-lifecycle-aware client that binds to the mesh service. Note: This is used implicitly as it registers
* itself as a LifecycleObserver in its init block. * itself as a LifecycleObserver in its init block.
@ -166,6 +169,16 @@ class MainActivity : ComponentActivity() {
handleIntent(intent) handleIntent(intent)
} }
override fun onResume() {
super.onResume()
// Belt-and-suspenders for the Android 12+ attach-intent quirk: if the activity is
// resumed while a USB device is already attached (e.g. process restart, returning
// from another app), the manifest-declared attach intent may have already fired
// before UsbRepository was constructed. Re-poll deviceList here so the UI reflects
// reality without requiring the user to physically replug.
usbRepository.refreshState()
}
@Composable @Composable
private fun AppCompositionLocals(content: @Composable () -> Unit) { private fun AppCompositionLocals(content: @Composable () -> Unit) {
CompositionLocalProvider( CompositionLocalProvider(
@ -257,6 +270,11 @@ class MainActivity : ComponentActivity() {
UsbManager.ACTION_USB_DEVICE_ATTACHED -> { UsbManager.ACTION_USB_DEVICE_ATTACHED -> {
Logger.d { "USB device attached" } Logger.d { "USB device attached" }
// Android 12+ delivers ACTION_USB_DEVICE_ATTACHED only to manifest-declared
// receivers, so the runtime-registered UsbBroadcastReceiver inside UsbRepository
// never sees this event. Forward it explicitly so the serialDevices StateFlow
// refreshes and the device shows up in the Connect → Serial tab.
usbRepository.refreshState()
showSettingsPage() showSettingsPage()
} }

View file

@ -30,7 +30,7 @@ pluginManagement {
} }
plugins { plugins {
id("com.gradle.develocity") version("4.4.0") id("com.gradle.develocity") version("4.4.1")
} }
dependencyResolutionManagement { dependencyResolutionManagement {

View file

@ -26,7 +26,9 @@ import com.juul.kable.UnmetRequirementException
/** /**
* Classification of a BLE-layer exception for the transport layer to act on. * Classification of a BLE-layer exception for the transport layer to act on.
* *
* @property isPermanent `true` if the condition won't resolve without user intervention (e.g. Bluetooth disabled). * @property isPermanent `true` if the condition cannot resolve without explicit user re-selection of the device.
* Currently always `false` all known BLE exceptions can resolve without user intervention (BT toggling, permission
* grants, transient GATT errors). Reserved for future use.
* @property gattStatus the platform GATT status code when available (Android-specific). * @property gattStatus the platform GATT status code when available (Android-specific).
* @property message a human-readable description of the failure. * @property message a human-readable description of the failure.
*/ */
@ -50,6 +52,9 @@ fun Throwable.classifyBleException(): BleExceptionInfo? = when (this) {
is GattRequestRejectedException -> is GattRequestRejectedException ->
BleExceptionInfo(isPermanent = false, message = "GATT request rejected (busy)") BleExceptionInfo(isPermanent = false, message = "GATT request rejected (busy)")
is UnmetRequirementException -> is UnmetRequirementException ->
BleExceptionInfo(isPermanent = true, message = message ?: "Bluetooth LE unavailable") // Bluetooth disabled or runtime permission missing. Both can resolve without re-selecting the
// device (user re-enables BT, or grants permission). Surface as transient so the transport keeps
// retrying; UI can show a hint based on the message.
BleExceptionInfo(isPermanent = false, message = message ?: "Bluetooth LE unavailable")
else -> null else -> null
} }

View file

@ -60,6 +60,7 @@ import org.meshtastic.proto.AdminMessage
import org.meshtastic.proto.Config import org.meshtastic.proto.Config
import org.meshtastic.proto.Telemetry import org.meshtastic.proto.Telemetry
import org.meshtastic.proto.ToRadio import org.meshtastic.proto.ToRadio
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds import kotlin.time.Duration.Companion.seconds
import kotlin.time.DurationUnit import kotlin.time.DurationUnit
@ -211,11 +212,11 @@ class MeshConnectionManagerImpl(
} }
} }
private fun startHandshakeStallGuard(stage: Int, action: () -> Unit) { private fun startHandshakeStallGuard(stage: Int, timeout: Duration, action: () -> Unit) {
handshakeTimeout?.cancel() handshakeTimeout?.cancel()
handshakeTimeout = handshakeTimeout =
scope.handledLaunch { scope.handledLaunch {
delay(HANDSHAKE_TIMEOUT) delay(timeout)
if (serviceRepository.connectionState.value is ConnectionState.Connecting) { if (serviceRepository.connectionState.value is ConnectionState.Connecting) {
// Attempt one retry. Note: the firmware silently drops identical consecutive // Attempt one retry. Note: the firmware silently drops identical consecutive
// writes (per-connection dedup). If the first want_config_id was received and // writes (per-connection dedup). If the first want_config_id was received and
@ -291,13 +292,13 @@ class MeshConnectionManagerImpl(
override fun startConfigOnly() { override fun startConfigOnly() {
val action = { packetHandler.sendToRadio(ToRadio(want_config_id = HandshakeConstants.CONFIG_NONCE)) } val action = { packetHandler.sendToRadio(ToRadio(want_config_id = HandshakeConstants.CONFIG_NONCE)) }
startHandshakeStallGuard(1, action) startHandshakeStallGuard(1, HANDSHAKE_TIMEOUT_STAGE1, action)
action() action()
} }
override fun startNodeInfoOnly() { override fun startNodeInfoOnly() {
val action = { packetHandler.sendToRadio(ToRadio(want_config_id = HandshakeConstants.NODE_INFO_NONCE)) } val action = { packetHandler.sendToRadio(ToRadio(want_config_id = HandshakeConstants.NODE_INFO_NONCE)) }
startHandshakeStallGuard(2, action) startHandshakeStallGuard(2, HANDSHAKE_TIMEOUT_STAGE2, action)
action() action()
} }
@ -404,7 +405,14 @@ class MeshConnectionManagerImpl(
*/ */
private const val PRE_HANDSHAKE_SETTLE_MS = 100L private const val PRE_HANDSHAKE_SETTLE_MS = 100L
private val HANDSHAKE_TIMEOUT = 30.seconds private val HANDSHAKE_TIMEOUT_STAGE1 = 30.seconds
/**
* Stage 2 drains the full node database, which can be significantly larger than Stage 1 config on big meshes.
* 60 s matches the meshtastic-client SDK timeout and avoids premature stall-guard triggers on meshes with 50+
* nodes.
*/
private val HANDSHAKE_TIMEOUT_STAGE2 = 60.seconds
// Shorter window for the retry attempt: if the device genuinely didn't receive the // Shorter window for the retry attempt: if the device genuinely didn't receive the
// first want_config_id the retry completes within a few seconds. Waiting another 30s // first want_config_id the retry completes within a few seconds. Waiting another 30s

View file

@ -31,12 +31,17 @@ import kotlinx.coroutines.flow.stateIn
import org.koin.core.annotation.Named import org.koin.core.annotation.Named
import org.koin.core.annotation.Single import org.koin.core.annotation.Single
import org.meshtastic.core.model.MqttConnectionState import org.meshtastic.core.model.MqttConnectionState
import org.meshtastic.core.model.MqttProbeStatus
import org.meshtastic.core.network.repository.MQTTRepository import org.meshtastic.core.network.repository.MQTTRepository
import org.meshtastic.core.network.repository.resolveEndpoint
import org.meshtastic.core.repository.MqttManager import org.meshtastic.core.repository.MqttManager
import org.meshtastic.core.repository.PacketHandler import org.meshtastic.core.repository.PacketHandler
import org.meshtastic.core.repository.ServiceRepository import org.meshtastic.core.repository.ServiceRepository
import org.meshtastic.mqtt.ConnectionState import org.meshtastic.mqtt.ConnectionState
import org.meshtastic.mqtt.MqttClient
import org.meshtastic.mqtt.MqttException import org.meshtastic.mqtt.MqttException
import org.meshtastic.mqtt.ProbeResult
import org.meshtastic.mqtt.probe
import org.meshtastic.proto.MqttClientProxyMessage import org.meshtastic.proto.MqttClientProxyMessage
import org.meshtastic.proto.ToRadio import org.meshtastic.proto.ToRadio
@ -52,9 +57,9 @@ class MqttManagerImpl(
override val mqttConnectionState: StateFlow<MqttConnectionState> = override val mqttConnectionState: StateFlow<MqttConnectionState> =
combine(proxyActive, mqttRepository.connectionState) { active, libState -> combine(proxyActive, mqttRepository.connectionState) { active, libState ->
if (!active) MqttConnectionState.INACTIVE else libState.toAppState() if (!active) MqttConnectionState.Inactive else libState.toAppState()
} }
.stateIn(scope, SharingStarted.Eagerly, MqttConnectionState.INACTIVE) .stateIn(scope, SharingStarted.Eagerly, MqttConnectionState.Inactive)
override fun startProxy(enabled: Boolean, proxyToClientEnabled: Boolean) { override fun startProxy(enabled: Boolean, proxyToClientEnabled: Boolean) {
if (mqttMessageFlow?.isActive == true) return if (mqttMessageFlow?.isActive == true) return
@ -102,9 +107,55 @@ class MqttManagerImpl(
} }
private fun ConnectionState.toAppState(): MqttConnectionState = when (this) { private fun ConnectionState.toAppState(): MqttConnectionState = when (this) {
ConnectionState.DISCONNECTED -> MqttConnectionState.DISCONNECTED is ConnectionState.Connecting -> MqttConnectionState.Connecting
ConnectionState.CONNECTING -> MqttConnectionState.CONNECTING is ConnectionState.Connected -> MqttConnectionState.Connected
ConnectionState.CONNECTED -> MqttConnectionState.CONNECTED is ConnectionState.Reconnecting ->
ConnectionState.RECONNECTING -> MqttConnectionState.RECONNECTING MqttConnectionState.Reconnecting(attempt = attempt, lastError = lastError?.message)
is ConnectionState.Disconnected ->
reason?.let { MqttConnectionState.Disconnected(reason = it.message) }
?: MqttConnectionState.Disconnected.Idle
}
override suspend fun probe(
address: String,
tlsEnabled: Boolean,
username: String?,
password: String?,
): MqttProbeStatus {
val endpoint = resolveEndpoint(address, tlsEnabled)
val result =
MqttClient.probe(endpoint = endpoint) {
val user = username?.takeUnless { it.isEmpty() }
val pass = password?.takeUnless { it.isEmpty() }
if (user != null) this.username = user
if (pass != null) password(pass)
}
return result.toAppStatus()
}
private fun ProbeResult.toAppStatus(): MqttProbeStatus = when (this) {
is ProbeResult.Success -> {
val info = serverInfo
val summary =
buildList {
info.assignedClientIdentifier?.let { add("client=$it") }
info.maximumQosOrdinal?.let { add("maxQoS=$it") }
info.serverKeepAliveSeconds?.let { add("keepalive=${it}s") }
}
.joinToString(", ")
.ifEmpty { null }
MqttProbeStatus.Success(serverInfo = summary)
}
is ProbeResult.Rejected ->
MqttProbeStatus.Rejected(
reasonCode = reasonCode.value,
reason = message,
serverReference = serverReference,
)
is ProbeResult.DnsFailure -> MqttProbeStatus.DnsFailure(message = cause.message)
is ProbeResult.TcpFailure -> MqttProbeStatus.TcpFailure(message = cause.message)
is ProbeResult.TlsFailure -> MqttProbeStatus.TlsFailure(message = cause.message)
is ProbeResult.Timeout -> MqttProbeStatus.Timeout(timeoutMs = durationMs)
is ProbeResult.Other -> MqttProbeStatus.Other(message = cause.message)
} }
} }

View file

@ -16,20 +16,41 @@
*/ */
package org.meshtastic.core.model package org.meshtastic.core.model
/** App-level MQTT proxy connection state, decoupled from the MQTT library's internal type. */ /**
enum class MqttConnectionState { * App-level MQTT proxy connection state, decoupled from the MQTT library's internal type.
*
* Modeled as a sealed class so disconnect / reconnect events can carry diagnostic context the user-facing reason for
* an unexpected disconnect, or the most recent reconnect attempt failure without requiring downstream consumers to
* depend on the MQTT library's exception types.
*/
sealed class MqttConnectionState {
/** The MQTT proxy has not been started (disabled or not yet initialized). */ /** The MQTT proxy has not been started (disabled or not yet initialized). */
INACTIVE, data object Inactive : MqttConnectionState()
/** The MQTT client is not connected to the broker. */
DISCONNECTED,
/** The MQTT client is actively connecting to the broker. */ /** The MQTT client is actively connecting to the broker. */
CONNECTING, data object Connecting : MqttConnectionState()
/** The MQTT client is connected and subscribed to topics. */ /** The MQTT client is connected and subscribed to topics. */
CONNECTED, data object Connected : MqttConnectionState()
/** The MQTT client lost connection and is attempting to reconnect. */ /**
RECONNECTING, * The MQTT client lost connection and is attempting to reconnect.
*
* @property attempt 1-based attempt counter for the current reconnect loop.
* @property lastError Localized message from the most recent reconnect failure, if any.
*/
data class Reconnecting(val attempt: Int = 0, val lastError: String? = null) : MqttConnectionState()
/**
* The MQTT client is not connected to the broker.
*
* @property reason Localized failure message for an unexpected disconnect, or `null` for the idle / initial /
* intentional-close case (use [Idle]).
*/
data class Disconnected(val reason: String? = null) : MqttConnectionState() {
companion object {
/** Singleton for the idle / no-reason disconnected state. */
val Idle: Disconnected = Disconnected(reason = null)
}
}
} }

View file

@ -0,0 +1,52 @@
/*
* Copyright (c) 2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.model
/**
* UI-friendly outcome of a one-shot MQTT broker reachability probe.
*
* Mirrors the failure shapes of `org.meshtastic.mqtt.ProbeResult` but stays in the model module so feature/UI code can
* consume the result without depending on the MQTT library.
*/
sealed class MqttProbeStatus {
/** Probe is currently in flight. */
data object Probing : MqttProbeStatus()
/**
* Broker accepted the connection. [serverInfo] is a short human-readable summary of any CONNACK properties that are
* useful to surface to the user.
*/
data class Success(val serverInfo: String?) : MqttProbeStatus()
/** Broker rejected the connection (CONNACK with non-zero reason code). */
data class Rejected(val reasonCode: Int, val reason: String?, val serverReference: String?) : MqttProbeStatus()
/** DNS lookup failed. */
data class DnsFailure(val message: String?) : MqttProbeStatus()
/** TCP socket could not be opened. */
data class TcpFailure(val message: String?) : MqttProbeStatus()
/** TLS handshake failed. */
data class TlsFailure(val message: String?) : MqttProbeStatus()
/** Probe exceeded its timeout. */
data class Timeout(val timeoutMs: Long) : MqttProbeStatus()
/** Any other / unclassified failure. */
data class Other(val message: String?) : MqttProbeStatus()
}

View file

@ -108,7 +108,10 @@ class SerialRadioTransport(
"Uptime: ${uptime}ms, " + "Uptime: ${uptime}ms, " +
"Packets RX: $packetsReceived ($bytesReceived bytes)" "Packets RX: $packetsReceived ($bytesReceived bytes)"
} }
onDeviceDisconnect(false) // USB unplug / cable error is transient — the transport will reconnect when
// the device is replugged or the OS re-enumerates the port. Only an explicit
// close() (user disconnects) should signal a permanent disconnect.
onDeviceDisconnect(waitForStopped = false, isPermanent = false)
} }
}, },
) )

View file

@ -87,6 +87,11 @@ internal class SerialConnectionImpl(
port.open(usbDeviceConnection) port.open(usbDeviceConnection)
port.setParameters(115200, UsbSerialPort.DATABITS_8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE) port.setParameters(115200, UsbSerialPort.DATABITS_8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE)
// Assert DTR/RTS so native USB-CDC firmware (RAK4631 / nRF52840) recognizes the host as
// present and starts its serial-side Meshtastic protocol. Empirically, omitting these
// signals causes the firmware to never respond to WAKE_BYTES, stalling the handshake at
// Stage 1. Bridge-chip boards (CH340, CP210x, FTDI) tolerate the assertion.
port.dtr = true port.dtr = true
port.rts = true port.rts = true

View file

@ -54,9 +54,7 @@ class UsbRepository(
_serialDevices _serialDevices
.mapLatest { serialDevices -> .mapLatest { serialDevices ->
val serialProber = usbSerialProberLazy.value val serialProber = usbSerialProberLazy.value
buildMap { buildMap { serialDevices.forEach { (k, v) -> serialProber.probeDevice(v)?.let { put(k, it) } } }
serialDevices.forEach { (k, v) -> serialProber.probeDevice(v)?.let { driver -> put(k, driver) } }
}
} }
.stateIn(processLifecycle.coroutineScope, SharingStarted.Eagerly, emptyMap()) .stateIn(processLifecycle.coroutineScope, SharingStarted.Eagerly, emptyMap())
@ -83,6 +81,8 @@ class UsbRepository(
processLifecycle.coroutineScope.launch(dispatchers.default) { refreshStateInternal() } processLifecycle.coroutineScope.launch(dispatchers.default) { refreshStateInternal() }
} }
private suspend fun refreshStateInternal() = private suspend fun refreshStateInternal() = withContext(dispatchers.default) {
withContext(dispatchers.default) { _serialDevices.emit(usbManagerLazy.value?.deviceList ?: emptyMap()) } val devices = usbManagerLazy.value?.deviceList ?: emptyMap()
_serialDevices.emit(devices)
}
} }

View file

@ -133,7 +133,11 @@ class BleRadioTransport(
@Volatile private var isFullyConnected = false @Volatile private var isFullyConnected = false
private var connectionJob: Job? = null private var connectionJob: Job? = null
private val reconnectPolicy = BleReconnectPolicy()
// Never give up while the user has this device selected. Higher layers (SharedRadioInterfaceService)
// own the explicit-disconnect lifecycle and will close() us when the user picks a different device or
// toggles the connection off; until then, retry forever with the policy's exponential-backoff cap (60 s).
private val reconnectPolicy = BleReconnectPolicy(maxFailures = Int.MAX_VALUE)
private val heartbeatSender = private val heartbeatSender =
HeartbeatSender( HeartbeatSender(

View file

@ -26,10 +26,11 @@ import kotlin.time.Duration.Companion.seconds
/** /**
* Encapsulates the BLE reconnection policy with exponential backoff. * Encapsulates the BLE reconnection policy with exponential backoff.
* *
* The policy tracks consecutive failures and decides whether to retry, signal a transient disconnect (DeviceSleep), or * The policy tracks consecutive failures and decides whether to retry or signal a transient disconnect (DeviceSleep).
* give up permanently. * When [maxFailures] is reached the [execute] loop invokes [execute]'s `onPermanentDisconnect` callback and returns;
* set [maxFailures] to [Int.MAX_VALUE] (as [BleRadioTransport] does) to disable the give-up path entirely.
* *
* @param maxFailures maximum consecutive failures before giving up permanently * @param maxFailures maximum consecutive failures before giving up; use [Int.MAX_VALUE] to retry indefinitely
* @param failureThreshold after this many consecutive failures, signal a transient disconnect * @param failureThreshold after this many consecutive failures, signal a transient disconnect
* @param settleDelay delay before each connection attempt to let the BLE stack settle * @param settleDelay delay before each connection attempt to let the BLE stack settle
* @param minStableConnection minimum time a connection must stay up to be considered "stable" * @param minStableConnection minimum time a connection must stay up to be considered "stable"
@ -148,7 +149,18 @@ class BleReconnectPolicy(
companion object { companion object {
const val DEFAULT_MAX_FAILURES = 10 const val DEFAULT_MAX_FAILURES = 10
const val DEFAULT_FAILURE_THRESHOLD = 3 const val DEFAULT_FAILURE_THRESHOLD = 3
val DEFAULT_SETTLE_DELAY = 1.seconds
/**
* Delay applied before every connection attempt (including the first) so the BLE stack and the firmware-side
* GATT session have time to settle.
*
* Empirically validated against the meshtastic-client KMP SDK probes (Apr 2026): with a 1.5 s pause between
* disconnectreconnect cycles, 3/54/5 attempts failed mid-handshake (Stage1Draining timeouts) because the
* firmware had not yet released its GATT session from the previous cycle. With 5 s pause, success rate rose
* to 5/5 against a strong (-53 dBm) link. 3 s is a conservative compromise on Android, whose BLE stack is more
* mature than btleplug+CoreBluetooth, but the firmware-side cleanup constraint is the same.
*/
val DEFAULT_SETTLE_DELAY = 3.seconds
val DEFAULT_MIN_STABLE_CONNECTION = 5.seconds val DEFAULT_MIN_STABLE_CONNECTION = 5.seconds
internal val RECONNECT_BASE_DELAY = 5.seconds internal val RECONNECT_BASE_DELAY = 5.seconds

View file

@ -37,18 +37,20 @@ abstract class StreamTransport(protected val callback: RadioTransportCallback, p
override suspend fun close() { override suspend fun close() {
Logger.d { "Closing stream for good" } Logger.d { "Closing stream for good" }
onDeviceDisconnect(true) onDeviceDisconnect(waitForStopped = true, isPermanent = true)
} }
/** /**
* Notify the transport callback that our device has gone away, but wait for it to come back. * Signals the transport callback that the device has disconnected and optionally waits for the transport to stop.
* *
* @param waitForStopped if true we should wait for the transport to finish - must be false if called from inside * @param waitForStopped if true we should wait for the transport to finish - must be false if called from inside
* transport callbacks * transport callbacks
* @param isPermanent true if the device is definitely gone (e.g. USB unplugged), false if it may come back (e.g. * @param isPermanent true only when the user has explicitly disconnected (e.g. [close] was called). USB unplug, I/O
* TCP transient disconnect). Defaults to true for serial subclasses may override with false. * errors, and similar conditions are transient the transport may recover when the device is replugged or the OS
* re-enumerates. Defaults to false so callbacks default to "may come back"; [close] passes true explicitly to
* signal a user-initiated terminal disconnect.
*/ */
protected open fun onDeviceDisconnect(waitForStopped: Boolean, isPermanent: Boolean = true) { protected open fun onDeviceDisconnect(waitForStopped: Boolean, isPermanent: Boolean = false) {
callback.onDisconnect(isPermanent = isPermanent) callback.onDisconnect(isPermanent = isPermanent)
} }

View file

@ -65,7 +65,6 @@ class MQTTRepositoryImpl(
private const val DEFAULT_TOPIC_LEVEL = "/2/e/" private const val DEFAULT_TOPIC_LEVEL = "/2/e/"
private const val JSON_TOPIC_LEVEL = "/2/json/" private const val JSON_TOPIC_LEVEL = "/2/json/"
private const val DEFAULT_SERVER_ADDRESS = "mqtt.meshtastic.org" private const val DEFAULT_SERVER_ADDRESS = "mqtt.meshtastic.org"
private const val WEBSOCKET_PATH = "/mqtt"
private const val KEEPALIVE_SECONDS = 30 private const val KEEPALIVE_SECONDS = 30
private const val INITIAL_RECONNECT_DELAY_MS = 1000L private const val INITIAL_RECONNECT_DELAY_MS = 1000L
private const val MAX_RECONNECT_DELAY_MS = 30_000L private const val MAX_RECONNECT_DELAY_MS = 30_000L
@ -74,7 +73,7 @@ class MQTTRepositoryImpl(
@Volatile private var client: MqttClient? = null @Volatile private var client: MqttClient? = null
private val _connectionState = MutableStateFlow(ConnectionState.DISCONNECTED) private val _connectionState = MutableStateFlow<ConnectionState>(ConnectionState.Disconnected.Idle)
override val connectionState: StateFlow<ConnectionState> = _connectionState.asStateFlow() override val connectionState: StateFlow<ConnectionState> = _connectionState.asStateFlow()
@OptIn(ExperimentalSerializationApi::class) @OptIn(ExperimentalSerializationApi::class)
@ -89,7 +88,7 @@ class MQTTRepositoryImpl(
Logger.i { "MQTT Disconnecting" } Logger.i { "MQTT Disconnecting" }
val c = client val c = client
client = null client = null
_connectionState.value = ConnectionState.DISCONNECTED _connectionState.value = ConnectionState.Disconnected.Idle
scope.launch { safeCatching { c?.close() }.onFailure { e -> Logger.w(e) { "MQTT clean disconnect failed" } } } scope.launch { safeCatching { c?.close() }.onFailure { e -> Logger.w(e) { "MQTT clean disconnect failed" } } }
} }
@ -102,14 +101,7 @@ class MQTTRepositoryImpl(
val rootTopic = mqttConfig?.root?.ifEmpty { DEFAULT_TOPIC_ROOT } ?: DEFAULT_TOPIC_ROOT val rootTopic = mqttConfig?.root?.ifEmpty { DEFAULT_TOPIC_ROOT } ?: DEFAULT_TOPIC_ROOT
val rawAddress = mqttConfig?.address ?: DEFAULT_SERVER_ADDRESS val rawAddress = mqttConfig?.address ?: DEFAULT_SERVER_ADDRESS
val endpoint = val endpoint = resolveEndpoint(rawAddress, mqttConfig?.tls_enabled == true)
if (rawAddress.contains("://")) {
MqttEndpoint.parse(rawAddress)
} else {
// Use WebSocket transport on all platforms for firewall/CDN compatibility.
val scheme = if (mqttConfig?.tls_enabled == true) "wss" else "ws"
MqttEndpoint.parse("$scheme://$rawAddress$WEBSOCKET_PATH")
}
val newClient = val newClient =
MqttClient(ownerId) { MqttClient(ownerId) {
@ -226,3 +218,26 @@ class MQTTRepositoryImpl(
} }
} }
} }
/**
* Resolve a user-supplied broker address into an [MqttEndpoint].
*
* Address resolution rules:
* - If [rawAddress] already contains a URI scheme (`scheme://…`), parse it directly via [MqttEndpoint.parse] and
* respect whatever transport / port the user encoded.
* - Otherwise wrap it as a WebSocket endpoint (`ws[s]://host${WEBSOCKET_PATH}`) so the proxy works over CDNs and
* firewall-restricted networks where raw 1883/8883 may be blocked. The scheme is `wss` when [tlsEnabled] is `true`,
* `ws` otherwise.
*
* Extracted as a top-level function so [MQTTRepositoryImplTest] can exercise every branch without spinning up the full
* repository, and so `MqttManagerImpl` (in `:core:data`) can reuse the same parsing rules for the probe API. Visibility
* is `public` because Kotlin's `internal` is scoped per Gradle module.
*/
fun resolveEndpoint(rawAddress: String, tlsEnabled: Boolean): MqttEndpoint = if (rawAddress.contains("://")) {
MqttEndpoint.parse(rawAddress)
} else {
val scheme = if (tlsEnabled) "wss" else "ws"
MqttEndpoint.parse("$scheme://$rawAddress$WEBSOCKET_PATH")
}
private const val WEBSOCKET_PATH = "/mqtt"

View file

@ -22,6 +22,7 @@ import dev.mokkery.every
import dev.mokkery.matcher.any import dev.mokkery.matcher.any
import dev.mokkery.mock import dev.mokkery.mock
import dev.mokkery.verify import dev.mokkery.verify
import dev.mokkery.verify.VerifyMode
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceTimeBy import kotlinx.coroutines.test.advanceTimeBy
@ -95,10 +96,10 @@ class BleRadioTransportTest {
* [RadioInterfaceService.onDisconnect] must be called so the higher layers can react (e.g. start the device-sleep * [RadioInterfaceService.onDisconnect] must be called so the higher layers can react (e.g. start the device-sleep
* timeout in [MeshConnectionManagerImpl]). * timeout in [MeshConnectionManagerImpl]).
* *
* Virtual-time breakdown (DEFAULT_FAILURE_THRESHOLD = 3): t = 1 000 ms iteration 1 settle delay elapses, * Virtual-time breakdown (DEFAULT_FAILURE_THRESHOLD = 3, DEFAULT_SETTLE_DELAY = 3 s): t = 3 000 ms iteration 1
* connectAndAwait throws, backoff 5 s starts t = 6 000 ms backoff ends t = 7 000 ms iteration 2 settle delay * settle delay elapses, connectAndAwait throws, backoff 5 s starts t = 8 000 ms backoff ends t = 11 000 ms
* elapses, connectAndAwait throws, backoff 10 s starts t = 17 000 ms backoff ends t = 18 000 ms iteration 3 * iteration 2 settle delay elapses, connectAndAwait throws, backoff 10 s starts t = 21 000 ms backoff ends t = 24
* settle delay elapses, connectAndAwait throws onDisconnect called * 000 ms iteration 3 settle delay elapses, connectAndAwait throws onDisconnect called
*/ */
@Test @Test
fun `onDisconnect is called after DEFAULT_FAILURE_THRESHOLD consecutive failures`() = runTest { fun `onDisconnect is called after DEFAULT_FAILURE_THRESHOLD consecutive failures`() = runTest {
@ -119,10 +120,10 @@ class BleRadioTransportTest {
) )
bleTransport.start() bleTransport.start()
// Advance through exactly 3 failure iterations (≈18 001 ms virtual time). // Advance through exactly 3 failure iterations (≈24 001 ms virtual time).
// The 4th iteration's backoff hasn't elapsed yet, so the coroutine is suspended // The 4th iteration's backoff hasn't elapsed yet, so the coroutine is suspended
// and advanceTimeBy returns cleanly. // and advanceTimeBy returns cleanly.
advanceTimeBy(18_001L) advanceTimeBy(24_001L)
verify { service.onDisconnect(any(), any()) } verify { service.onDisconnect(any(), any()) }
@ -131,16 +132,17 @@ class BleRadioTransportTest {
} }
/** /**
* After [BleReconnectPolicy.DEFAULT_MAX_FAILURES] (10) consecutive failures, the reconnect loop should stop and * Reconnect policy must NEVER give up on its own. The transport is only ever instantiated for the user-selected
* signal a permanent disconnect. This prevents infinite battery drain when the device is genuinely offline. * device, and explicit-disconnect is owned by the service layer (close()). Even after a sustained failure storm
* well beyond the legacy [BleReconnectPolicy.DEFAULT_MAX_FAILURES] the transport must keep retrying and must
* never call `onDisconnect(isPermanent = true)` from the give-up path.
* *
* Time budget for 10 failures with bonded device (no scan): Each iteration = 1s settle + connectAndAwait throw + * Time budget for 15 failures with bonded device (no scan): each iteration 3 s settle + immediate throw +
* backoff Backoffs: 5s, 10s, 20s, 40s, 60s, 60s, 60s, 60s, 60s, (exit at failure 10 before backoff) Total 10×1s * backoff. Backoffs cap at 60 s after failure 5: 5+10+20+40+60+60+60+60+60+60+60+60+60+60+60 = 735 s, plus 15×3 s
* settle + 5+10+20+40+60+60+60+60+60 = 10 + 375 = 385s 385_000ms We use a generous 400_000ms to cover any timing * settle = 45 s, total 780 s. Use 800_000 ms to cover variance.
* variance.
*/ */
@Test @Test
fun `reconnect loop stops after DEFAULT_MAX_FAILURES with permanent disconnect`() = runTest { fun `reconnect loop never gives up - no permanent disconnect from policy`() = runTest {
val device = FakeBleDevice(address = address, name = "Test Device") val device = FakeBleDevice(address = address, name = "Test Device")
bluetoothRepository.bond(device) bluetoothRepository.bond(device)
@ -158,11 +160,13 @@ class BleRadioTransportTest {
) )
bleTransport.start() bleTransport.start()
// Advance enough time for all 10 failures to occur. // Run well past where the legacy policy (maxFailures = 10) would have given up.
advanceTimeBy(400_001L) advanceTimeBy(800_001L)
// Should have been called with isPermanent=true at least once (the final call). // Transient disconnects (isPermanent = false) are expected once the failure threshold is hit;
verify { service.onDisconnect(isPermanent = true, errorMessage = any()) } // the policy must NEVER signal a permanent disconnect on its own. Only explicit close()
// (verified separately by the service layer) may emit isPermanent = true.
verify(mode = VerifyMode.not) { service.onDisconnect(isPermanent = true, errorMessage = any()) }
bleTransport.close() bleTransport.close()
} }

View file

@ -18,25 +18,82 @@ package org.meshtastic.core.network.repository
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import org.meshtastic.core.model.MqttJsonPayload import org.meshtastic.core.model.MqttJsonPayload
import org.meshtastic.mqtt.MqttEndpoint
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertIs
import kotlin.test.assertTrue import kotlin.test.assertTrue
class MQTTRepositoryImplTest { class MQTTRepositoryImplTest {
@Test // region resolveEndpoint — every behavioral branch of address parsing.
fun `test address parsing logic`() {
val address1 = "mqtt.example.com:1883"
val (host1, port1) = address1.split(":", limit = 2).let { it[0] to (it.getOrNull(1)?.toIntOrNull() ?: 1883) }
assertEquals("mqtt.example.com", host1)
assertEquals(1883, port1)
val address2 = "mqtt.example.com" @Test
val (host2, port2) = address2.split(":", limit = 2).let { it[0] to (it.getOrNull(1)?.toIntOrNull() ?: 1883) } fun `bare host without scheme is wrapped as ws WebSocket on the standard port`() {
assertEquals("mqtt.example.com", host2) val endpoint = resolveEndpoint(rawAddress = "broker.example.com", tlsEnabled = false)
assertEquals(1883, port2)
val ws = assertIs<MqttEndpoint.WebSocket>(endpoint)
assertEquals("ws://broker.example.com/mqtt", ws.url)
} }
@Test
fun `bare host with TLS enabled is upgraded to wss`() {
val endpoint = resolveEndpoint(rawAddress = "broker.example.com", tlsEnabled = true)
val ws = assertIs<MqttEndpoint.WebSocket>(endpoint)
assertEquals("wss://broker.example.com/mqtt", ws.url)
}
@Test
fun `host with explicit port is preserved when wrapped`() {
val endpoint = resolveEndpoint(rawAddress = "broker.example.com:9001", tlsEnabled = false)
val ws = assertIs<MqttEndpoint.WebSocket>(endpoint)
assertEquals("ws://broker.example.com:9001/mqtt", ws.url)
}
@Test
fun `address with ws scheme is parsed as-is and tls flag is ignored`() {
// tlsEnabled is intentionally true here — when the user supplies a full URL we
// must honor whatever scheme they provided, not silently upgrade it.
val endpoint = resolveEndpoint(rawAddress = "ws://broker.example.com:8080/custom-path", tlsEnabled = true)
val ws = assertIs<MqttEndpoint.WebSocket>(endpoint)
assertEquals("ws://broker.example.com:8080/custom-path", ws.url)
}
@Test
fun `address with wss scheme is parsed as-is`() {
val endpoint = resolveEndpoint(rawAddress = "wss://broker.example.com/secure-mqtt", tlsEnabled = false)
val ws = assertIs<MqttEndpoint.WebSocket>(endpoint)
assertEquals("wss://broker.example.com/secure-mqtt", ws.url)
}
@Test
fun `address with mqtt tcp scheme is parsed as Tcp endpoint`() {
val endpoint = resolveEndpoint(rawAddress = "mqtt://broker.example.com:1883", tlsEnabled = false)
val tcp = assertIs<MqttEndpoint.Tcp>(endpoint)
assertEquals("broker.example.com", tcp.host)
assertEquals(1883, tcp.port)
assertEquals(false, tcp.tls)
}
@Test
fun `address with mqtts tcp scheme is parsed as Tcp endpoint with tls true`() {
val endpoint = resolveEndpoint(rawAddress = "mqtts://broker.example.com:8883", tlsEnabled = false)
val tcp = assertIs<MqttEndpoint.Tcp>(endpoint)
assertEquals("broker.example.com", tcp.host)
assertEquals(8883, tcp.port)
assertEquals(true, tcp.tls)
}
// endregion
// region MqttJsonPayload — keep the existing JSON contract tests.
@Test @Test
fun `test json payload parsing`() { fun `test json payload parsing`() {
val jsonStr = val jsonStr =
@ -72,4 +129,6 @@ class MQTTRepositoryImplTest {
assertTrue(jsonStr.contains("\"from\":12345678")) assertTrue(jsonStr.contains("\"from\":12345678"))
assertTrue(jsonStr.contains("\"payload\":\"Hello World\"")) assertTrue(jsonStr.contains("\"payload\":\"Hello World\""))
} }
// endregion
} }

View file

@ -78,7 +78,11 @@ open class TcpRadioTransport(
Logger.d { "[$address] Closing TCP transport" } Logger.d { "[$address] Closing TCP transport" }
closing = true closing = true
transport.stop() transport.stop()
callback.onDisconnect(isPermanent = true) // Do NOT emit onDisconnect(isPermanent = true) here. The explicit-disconnect signal is the
// service layer's responsibility (SharedRadioInterfaceService.stopTransportLocked); emitting
// it from close() caused a double-disconnect and prevented the auto-reconnect loop from
// owning its own lifecycle. The `closing` guard above suppresses the listener's transient
// disconnect during teardown.
} }
override fun keepAlive() { override fun keepAlive() {

View file

@ -129,7 +129,10 @@ private constructor(
// Ignore errors during port close // Ignore errors during port close
} }
if (isActive) { if (isActive) {
onDeviceDisconnect(true) // Serial read loop ended unexpectedly (cable unplug, I/O error). Treat as
// transient — the user did not explicitly disconnect, and the port may come
// back when the device is replugged or the OS re-enumerates it.
onDeviceDisconnect(waitForStopped = true, isPermanent = false)
} }
} }
} }
@ -169,8 +172,10 @@ private constructor(
private const val READ_TIMEOUT_MS = 100 private const val READ_TIMEOUT_MS = 100
/** /**
* Creates and opens a [SerialTransport]. If the port cannot be opened, the transport signals a permanent * Creates and opens a [SerialTransport]. If the port cannot be opened, the transport signals a transient
* disconnect to the [callback] and returns the (non-connected) instance. * disconnect to the [callback] and returns the (non-connected) instance. The open failure is treated as
* non-permanent so higher-layer reconnect orchestration can retry (e.g. when the device is replugged or the
* user grants permission); only an explicit close should signal a permanent disconnect.
*/ */
fun open( fun open(
portName: String, portName: String,
@ -183,7 +188,7 @@ private constructor(
if (!transport.startConnection()) { if (!transport.startConnection()) {
val errorMessage = diagnoseOpenFailure(portName) val errorMessage = diagnoseOpenFailure(portName)
Logger.w { "[$portName] Serial port could not be opened; signalling disconnect. $errorMessage" } Logger.w { "[$portName] Serial port could not be opened; signalling disconnect. $errorMessage" }
callback.onDisconnect(isPermanent = true, errorMessage = errorMessage) callback.onDisconnect(isPermanent = false, errorMessage = errorMessage)
} }
return transport return transport
} }

View file

@ -18,6 +18,7 @@ package org.meshtastic.core.repository
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import org.meshtastic.core.model.MqttConnectionState import org.meshtastic.core.model.MqttConnectionState
import org.meshtastic.core.model.MqttProbeStatus
import org.meshtastic.proto.MqttClientProxyMessage import org.meshtastic.proto.MqttClientProxyMessage
/** Interface for managing MQTT proxy communication. */ /** Interface for managing MQTT proxy communication. */
@ -33,4 +34,15 @@ interface MqttManager {
/** Handles an MQTT proxy message from the radio. */ /** Handles an MQTT proxy message from the radio. */
fun handleMqttProxyMessage(message: MqttClientProxyMessage) fun handleMqttProxyMessage(message: MqttClientProxyMessage)
/**
* Probe an MQTT broker to verify connectivity and credentials without joining the proxy lifecycle. Intended for UI
* "Test Connection" affordances.
*
* @param address Raw broker address as the user would type it (host, host:port, or full URL).
* @param tlsEnabled `true` to upgrade bare addresses to `wss://` (ignored when [address] already has a scheme).
* @param username Optional MQTT username.
* @param password Optional MQTT password.
*/
suspend fun probe(address: String, tlsEnabled: Boolean, username: String?, password: String?): MqttProbeStatus
} }

View file

@ -174,4 +174,5 @@
<!-- Message Filter --> <!-- Message Filter -->
<string name="bluetooth_permission">إعدادات بلوتوث</string> <string name="bluetooth_permission">إعدادات بلوتوث</string>
<!-- Network Map Layers --> <!-- Network Map Layers -->
<string name="filter_icon">عربي</string>
</resources> </resources>

View file

@ -222,4 +222,5 @@
<string name="tak_team_blue">Сіні</string> <string name="tak_team_blue">Сіні</string>
<string name="tak_team_green">Зялёны</string> <string name="tak_team_green">Зялёны</string>
<string name="desktop_notification_title">Meshtastic</string> <string name="desktop_notification_title">Meshtastic</string>
<string name="filter_icon">Фільтраваць</string>
</resources> </resources>

View file

@ -324,12 +324,9 @@
<string name="encryption_error_text">Публичният ключ не съвпада със записания ключ. Можете да премахнете възела и да го оставите да обмени ключове отново, но това може да показва по-сериозен проблем със сигурността. Свържете се с потребителя чрез друг надежден канал, за да определите дали промяната на ключа се дължи на фабрично нулиране или друго умишлено действие.</string> <string name="encryption_error_text">Публичният ключ не съвпада със записания ключ. Можете да премахнете възела и да го оставите да обмени ключове отново, но това може да показва по-сериозен проблем със сигурността. Свържете се с потребителя чрез друг надежден канал, за да определите дали промяната на ключа се дължи на фабрично нулиране или друго умишлено действие.</string>
<string name="meshtastic_new_nodes_notifications">Известия за нови възли</string> <string name="meshtastic_new_nodes_notifications">Известия за нови възли</string>
<string name="snr">SNR</string> <string name="snr">SNR</string>
<string name="snr_definition">Съотношение сигнал/шум, мярка, използвана в комуникациите за количествено определяне на нивото на желания сигнал спрямо нивото на фоновия шум. В Meshtastic и други безжични системи, по-високото съотношение сигнал/шум показва по-ясен сигнал, който може да подобри надеждността и качеството на предаване на данни.</string>
<string name="rssi">RSSI</string> <string name="rssi">RSSI</string>
<string name="rssi_definition">Индикатор за силата на получения сигнал - измерване, използвано за определяне на нивото на получения сигнал, приемано от антената. По-високата стойност на RSSI обикновено показва по-силна и по-стабилна връзка.</string>
<string name="iaq_definition">(Качество на въздуха в помещенията) относителна скала за IAQ, стойностите са измерени с Bosch BME680. Диапазон на стойностите 0500.</string> <string name="iaq_definition">(Качество на въздуха в помещенията) относителна скала за IAQ, стойностите са измерени с Bosch BME680. Диапазон на стойностите 0500.</string>
<string name="device_metrics_log">Метрики на устройството</string> <string name="device_metrics_log">Метрики на устройството</string>
<string name="node_map">Карта на възела</string>
<string name="position_log">Позиция</string> <string name="position_log">Позиция</string>
<string name="last_position_update">Последна актуализация на позицията</string> <string name="last_position_update">Последна актуализация на позицията</string>
<string name="env_metrics_log">Показатели на околната среда</string> <string name="env_metrics_log">Показатели на околната среда</string>
@ -369,7 +366,6 @@
<string name="one_month">1М</string> <string name="one_month">1М</string>
<string name="max">Макс</string> <string name="max">Макс</string>
<string name="min">Мин</string> <string name="min">Мин</string>
<string name="avg">Ср</string>
<string name="expand_chart">Разгъване на диаграмата</string> <string name="expand_chart">Разгъване на диаграмата</string>
<string name="collapse_chart">Свиване на диаграмата</string> <string name="collapse_chart">Свиване на диаграмата</string>
<string name="unknown_age">Неизвестна възраст</string> <string name="unknown_age">Неизвестна възраст</string>
@ -490,8 +486,17 @@
<string name="frequency_slot">Честотен слот</string> <string name="frequency_slot">Честотен слот</string>
<string name="ignore_mqtt">Игнориране на MQTT</string> <string name="ignore_mqtt">Игнориране на MQTT</string>
<string name="mqtt_config">Конфигуриране на MQTT</string> <string name="mqtt_config">Конфигуриране на MQTT</string>
<string name="mqtt_status_inactive">Неактивен</string>
<string name="mqtt_status_disconnected">Прекъсната връзка</string> <string name="mqtt_status_disconnected">Прекъсната връзка</string>
<string name="mqtt_status_connecting">Свързване…</string>
<string name="mqtt_status_connected">Свързано</string> <string name="mqtt_status_connected">Свързано</string>
<string name="mqtt_status_reconnecting">Повторно свързване…</string>
<string name="mqtt_status_reconnecting_with_attempt">Повторно свързване (опит %1$d) — %2$s</string>
<string name="mqtt_test_connection">Тестване на връзката</string>
<string name="mqtt_probe_success">Достъпен. Брокерът е приел идентификационните данни.</string>
<string name="mqtt_probe_success_with_info">Достъпен (%1$s)</string>
<string name="mqtt_probe_dns_failure">Хостът не е намерен</string>
<string name="mqtt_probe_other_failure">Връзката е неуспешна</string>
<string name="mqtt_enabled">MQTT е активиран</string> <string name="mqtt_enabled">MQTT е активиран</string>
<string name="address">Адрес</string> <string name="address">Адрес</string>
<string name="username">Потребителско име</string> <string name="username">Потребителско име</string>
@ -971,4 +976,7 @@
<string name="wifi_provision_status_failed">Прилагането на конфигурацията за WiFi не е успешно</string> <string name="wifi_provision_status_failed">Прилагането на конфигурацията за WiFi не е успешно</string>
<string name="desktop_tray_quit">Изход</string> <string name="desktop_tray_quit">Изход</string>
<string name="desktop_notification_title">Meshtastic</string> <string name="desktop_notification_title">Meshtastic</string>
<string name="filter_icon">Филтър</string>
<string name="action_select_device">Изберете устройство</string>
<string name="action_select_network">Изберете мрежа</string>
</resources> </resources>

View file

@ -201,4 +201,5 @@
<!-- Message Filter --> <!-- Message Filter -->
<!-- Network Map Layers --> <!-- Network Map Layers -->
<string name="desktop_notification_title">Meshtastic</string> <string name="desktop_notification_title">Meshtastic</string>
<string name="filter_icon">Filtre</string>
</resources> </resources>

View file

@ -331,12 +331,9 @@
<string name="userinfo">Informace o uživateli</string> <string name="userinfo">Informace o uživateli</string>
<string name="meshtastic_new_nodes_notifications">Oznámení o nových uzlech</string> <string name="meshtastic_new_nodes_notifications">Oznámení o nových uzlech</string>
<string name="snr">SNR</string> <string name="snr">SNR</string>
<string name="snr_definition">Poměr signálu k šumu (SNR) je veličina používaná k vyjádření poměru mezi úrovní požadovaného signálu a úrovní šumu na pozadí. V Meshtastic a dalších bezdrátových systémech vyšší hodnota SNR značí čistší signál, což může zvýšit spolehlivost a kvalitu přenosu dat.</string>
<string name="rssi">RSSI</string> <string name="rssi">RSSI</string>
<string name="rssi_definition">Indikátor síly přijímaného signálu, měření, které se používá k určení hladiny výkonu přijímané anténou. Vyšší hodnota RSSI obvykle znamená silnější a stabilnější spojení.</string>
<string name="iaq_definition">(Vnitřní kvalita ovzduší) relativní hodnota IAQ měřená Bosch BME680. Hodnota rozsahu 0500.</string> <string name="iaq_definition">(Vnitřní kvalita ovzduší) relativní hodnota IAQ měřená Bosch BME680. Hodnota rozsahu 0500.</string>
<string name="device_metrics_log">Metriky zařízení</string> <string name="device_metrics_log">Metriky zařízení</string>
<string name="node_map">Mapa uzlu</string>
<string name="position_log">Pozice</string> <string name="position_log">Pozice</string>
<string name="last_position_update">Poslední aktualizace pozice</string> <string name="last_position_update">Poslední aktualizace pozice</string>
<string name="env_metrics_log">Metriky prostředí</string> <string name="env_metrics_log">Metriky prostředí</string>
@ -971,4 +968,5 @@
<string name="connect">Připojit</string> <string name="connect">Připojit</string>
<string name="done">Hotovo</string> <string name="done">Hotovo</string>
<string name="desktop_notification_title">Meshtastic</string> <string name="desktop_notification_title">Meshtastic</string>
<string name="filter_icon">Filtr</string>
</resources> </resources>

View file

@ -383,12 +383,9 @@
<string name="userinfo">Benutzerinfo</string> <string name="userinfo">Benutzerinfo</string>
<string name="meshtastic_new_nodes_notifications">Benachrichtigung neue Knoten</string> <string name="meshtastic_new_nodes_notifications">Benachrichtigung neue Knoten</string>
<string name="snr">SNR</string> <string name="snr">SNR</string>
<string name="snr_definition">Signal-Rausch-Verhältnis, ein in der Kommunikation verwendetes Maß, um den Pegel eines gewünschten Signals im Verhältnis zum Pegel des Hintergrundrauschens zu quantifizieren. Bei Meshtastic und anderen drahtlosen Systemen weist ein höheres SNR auf ein klareres Signal hin, das die Zuverlässigkeit und Qualität der Datenübertragung verbessern kann.</string>
<string name="rssi">RSSI</string> <string name="rssi">RSSI</string>
<string name="rssi_definition">Indikator für die empfangene Signalstärke, eine Messung zur Bestimmung der von der Antenne empfangenen Leistungsstärke. Ein höherer RSSI-Wert weist im Allgemeinen auf eine stärkere und stabilere Verbindung hin.</string>
<string name="iaq_definition">(Innenluftqualität) relativer IAQ-Wert gemessen von Bosch BME680.</string> <string name="iaq_definition">(Innenluftqualität) relativer IAQ-Wert gemessen von Bosch BME680.</string>
<string name="device_metrics_log">Gerätedaten</string> <string name="device_metrics_log">Gerätedaten</string>
<string name="node_map">Standortkarte Knoten</string>
<string name="position_log">Standort</string> <string name="position_log">Standort</string>
<string name="last_position_update">Letzte Standortaktualisierung</string> <string name="last_position_update">Letzte Standortaktualisierung</string>
<string name="env_metrics_log">Umweltdaten</string> <string name="env_metrics_log">Umweltdaten</string>
@ -435,7 +432,6 @@
<string name="one_month">1 Monat</string> <string name="one_month">1 Monat</string>
<string name="max">Maximal</string> <string name="max">Maximal</string>
<string name="min">Minimum</string> <string name="min">Minimum</string>
<string name="avg">Durchschnitt</string>
<string name="expand_chart">Diagramm einblenden</string> <string name="expand_chart">Diagramm einblenden</string>
<string name="collapse_chart">Diagramm ausblenden</string> <string name="collapse_chart">Diagramm ausblenden</string>
<string name="unknown_age">Alter unbekannt</string> <string name="unknown_age">Alter unbekannt</string>
@ -613,8 +609,23 @@
<string name="ignore_mqtt">MQTT ignorieren</string> <string name="ignore_mqtt">MQTT ignorieren</string>
<string name="ok_to_mqtt">OK für MQTT</string> <string name="ok_to_mqtt">OK für MQTT</string>
<string name="mqtt_config">MQTT Einstellungen</string> <string name="mqtt_config">MQTT Einstellungen</string>
<string name="mqtt_status_inactive">Inaktiv</string>
<string name="mqtt_status_disconnected">Verbindung getrennt</string> <string name="mqtt_status_disconnected">Verbindung getrennt</string>
<string name="mqtt_status_disconnected_with_reason">Verbindung getrennt - %1$s</string>
<string name="mqtt_status_connecting">Wird verbunden</string>
<string name="mqtt_status_connected">Verbunden</string> <string name="mqtt_status_connected">Verbunden</string>
<string name="mqtt_status_reconnecting">Erneut verbinden</string>
<string name="mqtt_status_reconnecting_with_attempt">Erneut verbinden (Versuch %1$d) - %2$s</string>
<string name="mqtt_test_connection">Verbindung testen</string>
<string name="mqtt_probe_running">Broker prüfen.</string>
<string name="mqtt_probe_success">Erreichbar. Broker akzeptierte Anmeldedaten.</string>
<string name="mqtt_probe_success_with_info">Erreichbar (%1$s)</string>
<string name="mqtt_probe_rejected">Broker abgelehnt: %1$s</string>
<string name="mqtt_probe_dns_failure">Host nicht gefunden</string>
<string name="mqtt_probe_tcp_failure">Broker (TCP) nicht erreichbar</string>
<string name="mqtt_probe_tls_failure">TLS Handshake fehlgeschlagen</string>
<string name="mqtt_probe_timeout">Zeitüberschreitung nach %1$d ms</string>
<string name="mqtt_probe_other_failure">Verbindung fehlgeschlagen</string>
<string name="mqtt_enabled">MQTT aktiviert</string> <string name="mqtt_enabled">MQTT aktiviert</string>
<string name="address">Adresse</string> <string name="address">Adresse</string>
<string name="username">Benutzername</string> <string name="username">Benutzername</string>
@ -1210,5 +1221,21 @@
<string name="wifi_provision_ssid_placeholder">Netzwerk eingeben oder auswählen</string> <string name="wifi_provision_ssid_placeholder">Netzwerk eingeben oder auswählen</string>
<string name="wifi_provision_status_applied">WLAN erfolgreich konfiguriert!</string> <string name="wifi_provision_status_applied">WLAN erfolgreich konfiguriert!</string>
<string name="wifi_provision_status_failed">WLAN Konfiguration konnte nicht angewendet werden</string> <string name="wifi_provision_status_failed">WLAN Konfiguration konnte nicht angewendet werden</string>
<string name="desktop_tray_tooltip">Meshtastic Desktop</string>
<string name="desktop_tray_show">Meshtastic anzeigen</string>
<string name="desktop_tray_quit">Beenden</string>
<string name="desktop_notification_title">Meshtastic</string> <string name="desktop_notification_title">Meshtastic</string>
<string name="export_tak_data_package">TAK Datenpaket exportieren</string>
<string name="clear_time_zone">Zeitzone löschen</string>
<string name="filter_icon">Filter</string>
<string name="remove_filter">Filter entfernen</string>
<string name="show_iaq_legend">Legende für Luftqualität anzeigen</string>
<string name="action_show_message_status">Nachrichtenstatus anzeigen</string>
<string name="action_send_reply">Antwort senden</string>
<string name="action_copy_message">Nachricht kopieren</string>
<string name="action_select_message">Nachricht auswählen</string>
<string name="action_delete_message">Nachricht löschen</string>
<string name="action_react_with_emoji">Mit Emoji reagieren</string>
<string name="action_select_device">Gerät auswählen</string>
<string name="action_select_network">Wählen Sie ein Netzwerk</string>
</resources> </resources>

View file

@ -201,4 +201,5 @@
<string name="tak_team_red">Κόκκινο</string> <string name="tak_team_red">Κόκκινο</string>
<string name="tak_team_blue">Μπλε</string> <string name="tak_team_blue">Μπλε</string>
<string name="tak_team_green">Πράσινο</string> <string name="tak_team_green">Πράσινο</string>
<string name="filter_icon">Φίλτρο</string>
</resources> </resources>

View file

@ -300,13 +300,10 @@
<string name="encryption_error">Clave pública no coincide</string> <string name="encryption_error">Clave pública no coincide</string>
<string name="meshtastic_new_nodes_notifications">Notificaciones de nuevo nodo</string> <string name="meshtastic_new_nodes_notifications">Notificaciones de nuevo nodo</string>
<string name="snr">SNR</string> <string name="snr">SNR</string>
<string name="snr_definition">SNR: Ratio de señal a ruido, una medida utilizada en las comunicaciones para cuantificar el nivel de una señal deseada respecto al nivel del ruido de fondo. En Meshtastic y otros sistemas inalámbricos, un mayor SNR indica una señal más clara que puede mejorar la fiabilidad y la calidad de la transmisión de datos.</string>
<string name="rssi">RSSI</string> <string name="rssi">RSSI</string>
<string name="rssi_definition">Indicador de Fuerza de Señal Recibida (RSSI en inglés), una medida utilizada para determinar el nivel de potencia que está siendo recibido por la antena. Un valor de RSSI más alto generalmente indica una conexión más fuerte y estable.</string>
<string name="iaq_definition">(Calidad de Aire interior) escala relativa del valor IAQ como mediciones del sensor Bosch BME680. <string name="iaq_definition">(Calidad de Aire interior) escala relativa del valor IAQ como mediciones del sensor Bosch BME680.
Rango de Valores 0 - 500.</string> Rango de Valores 0 - 500.</string>
<string name="device_metrics_log">Métricas de Dispositivo</string> <string name="device_metrics_log">Métricas de Dispositivo</string>
<string name="node_map">Mapa de Nodos</string>
<string name="position_log">Posición</string> <string name="position_log">Posición</string>
<string name="last_position_update">Última actualización</string> <string name="last_position_update">Última actualización</string>
<string name="env_metrics_log">Métricas de Entorno</string> <string name="env_metrics_log">Métricas de Entorno</string>
@ -839,4 +836,5 @@ Estos datos de ubicación pueden ser utilizados para fines como aparecer en un m
<string name="connect">Conectar</string> <string name="connect">Conectar</string>
<string name="done">Hecho</string> <string name="done">Hecho</string>
<string name="desktop_notification_title">Meshtastic</string> <string name="desktop_notification_title">Meshtastic</string>
<string name="filter_icon">Filtro</string>
</resources> </resources>

View file

@ -383,12 +383,9 @@
<string name="userinfo">Kasutaja teave</string> <string name="userinfo">Kasutaja teave</string>
<string name="meshtastic_new_nodes_notifications">Uue sõlme teade</string> <string name="meshtastic_new_nodes_notifications">Uue sõlme teade</string>
<string name="snr">SNR</string> <string name="snr">SNR</string>
<string name="snr_definition">Signaali ja müra suhe (SNR) on mõõdik, mida kasutatakse soovitud signaali taseme ja taustamüra taseme vahelise suhte määramisel. Meshtastic ja teistes traadita süsteemides näitab kõrgem signaali ja müra suhe selgemat signaali, mis võib parandada andmeedastuse usaldusväärsust ja kvaliteeti.</string>
<string name="rssi">RSSI</string> <string name="rssi">RSSI</string>
<string name="rssi_definition">Vastuvõetud signaali tugevuse indikaator (RSSI), mõõt mida kasutatakse antenni poolt vastuvõetava võimsustaseme määramiseks. Kõrgem RSSI väärtus näitab üldiselt tugevamat ja stabiilsemat ühendust.</string>
<string name="iaq_definition">Siseõhu kvaliteet (IAQ) on suhtelise skaala väärtus, näiteks mõõtes Bosch BME680 abil. Väärtuste vahemik 0500.</string> <string name="iaq_definition">Siseõhu kvaliteet (IAQ) on suhtelise skaala väärtus, näiteks mõõtes Bosch BME680 abil. Väärtuste vahemik 0500.</string>
<string name="device_metrics_log">Seadme mõõdikud</string> <string name="device_metrics_log">Seadme mõõdikud</string>
<string name="node_map">Sõlmede kaart</string>
<string name="position_log">Asukoht</string> <string name="position_log">Asukoht</string>
<string name="last_position_update">Viimase asukoha värskendus</string> <string name="last_position_update">Viimase asukoha värskendus</string>
<string name="env_metrics_log">Keskkonnamõõdikud</string> <string name="env_metrics_log">Keskkonnamõõdikud</string>
@ -435,7 +432,6 @@
<string name="one_month">1k</string> <string name="one_month">1k</string>
<string name="max">Maksimaalselt</string> <string name="max">Maksimaalselt</string>
<string name="min">Min</string> <string name="min">Min</string>
<string name="avg">Keskm</string>
<string name="expand_chart">Laienda diagrammi</string> <string name="expand_chart">Laienda diagrammi</string>
<string name="collapse_chart">Ahenda diagrammi</string> <string name="collapse_chart">Ahenda diagrammi</string>
<string name="unknown_age">Tundmatu vanus</string> <string name="unknown_age">Tundmatu vanus</string>
@ -613,8 +609,23 @@
<string name="ignore_mqtt">Keela MQTT</string> <string name="ignore_mqtt">Keela MQTT</string>
<string name="ok_to_mqtt">Ok MQTTi</string> <string name="ok_to_mqtt">Ok MQTTi</string>
<string name="mqtt_config">MQTT sätted</string> <string name="mqtt_config">MQTT sätted</string>
<string name="mqtt_status_inactive">Mitteaktiivne</string>
<string name="mqtt_status_disconnected">Ühendus katkenud</string> <string name="mqtt_status_disconnected">Ühendus katkenud</string>
<string name="mqtt_status_disconnected_with_reason">Ühendus katkenud — %1$s</string>
<string name="mqtt_status_connecting">Ühendan…</string>
<string name="mqtt_status_connected">Ühendatud</string> <string name="mqtt_status_connected">Ühendatud</string>
<string name="mqtt_status_reconnecting">Taas ühendan…</string>
<string name="mqtt_status_reconnecting_with_attempt">Ühendan uuesti (katse %1$d) — %2$s</string>
<string name="mqtt_test_connection">Test ühendus</string>
<string name="mqtt_probe_running">Kontrollin vahendajat…</string>
<string name="mqtt_probe_success">Ühendus õnnestus. Vahendaja aktsepteeris kasutajateave.</string>
<string name="mqtt_probe_success_with_info">Kättesaadav (%1$s)</string>
<string name="mqtt_probe_rejected">Vahendaja lükkas tagasi: %1$s</string>
<string name="mqtt_probe_dns_failure">Hosti ei leitud</string>
<string name="mqtt_probe_tcp_failure">Vahendajaga ei saa ühendust (TCP)</string>
<string name="mqtt_probe_tls_failure">TLS ühendus ebaõnnestus</string>
<string name="mqtt_probe_timeout">Ajaline katkestus peale %1$d ms</string>
<string name="mqtt_probe_other_failure">Ühendus ebaõnnestus</string>
<string name="mqtt_enabled">MQTT lubatud</string> <string name="mqtt_enabled">MQTT lubatud</string>
<string name="address">Aadress</string> <string name="address">Aadress</string>
<string name="username">Kasutajatunnus</string> <string name="username">Kasutajatunnus</string>
@ -1214,4 +1225,17 @@
<string name="desktop_tray_show">Näita Meshtastic</string> <string name="desktop_tray_show">Näita Meshtastic</string>
<string name="desktop_tray_quit">Sule</string> <string name="desktop_tray_quit">Sule</string>
<string name="desktop_notification_title">Kärgvõrgustik</string> <string name="desktop_notification_title">Kärgvõrgustik</string>
<string name="export_tak_data_package">Ekspordi TAK andmepakett</string>
<string name="clear_time_zone">Eemalda ajatsoon</string>
<string name="filter_icon">Filtreeri</string>
<string name="remove_filter">Eemalda filter</string>
<string name="show_iaq_legend">Näita õhukvaliteedi ajalugu</string>
<string name="action_show_message_status">Kuva sõnumi olek</string>
<string name="action_send_reply">Saada vastus</string>
<string name="action_copy_message">Kopeeri sõnum</string>
<string name="action_select_message">Vali sõnum</string>
<string name="action_delete_message">Kustuta sõnum</string>
<string name="action_react_with_emoji">Vasta emotikoniga</string>
<string name="action_select_device">Vali seade</string>
<string name="action_select_network">Vali võrk</string>
</resources> </resources>

View file

@ -383,12 +383,9 @@
<string name="userinfo">Käyttäjätiedot</string> <string name="userinfo">Käyttäjätiedot</string>
<string name="meshtastic_new_nodes_notifications">Uuden laitteen ilmoitukset</string> <string name="meshtastic_new_nodes_notifications">Uuden laitteen ilmoitukset</string>
<string name="snr">SNR</string> <string name="snr">SNR</string>
<string name="snr_definition">Signaali-kohinasuhde (SNR) on mittari, jota käytetään viestinnässä halutun signaalin tason ja taustahälyn tason määrittämisessä. Meshtasticissa ja muissa langattomissa järjestelmissä korkeampi SNR tarkoittaa selkeämpää signaalia, joka voi parantaa tiedonsiirron luotettavuutta ja laatua.</string>
<string name="rssi">RSSI</string> <string name="rssi">RSSI</string>
<string name="rssi_definition">Vastaanotetun signaalin voimakkuusindikaattori (RSSI) on mittari, jota käytetään määrittämään antennilla vastaanotetun signaalin voimakkuus. Korkeampi RSSI-arvo yleensä osoittaa vahvemman ja vakaamman yhteyden.</string>
<string name="iaq_definition">Sisäilman laatu (IAQ) on suhteellinen asteikko, jota voidaan mitata mm. Bosch BME680 anturilla ja sen arvoväli on 0500.</string> <string name="iaq_definition">Sisäilman laatu (IAQ) on suhteellinen asteikko, jota voidaan mitata mm. Bosch BME680 anturilla ja sen arvoväli on 0500.</string>
<string name="device_metrics_log">Laitteen mittausloki</string> <string name="device_metrics_log">Laitteen mittausloki</string>
<string name="node_map">Laitekartta</string>
<string name="position_log">Sijainti</string> <string name="position_log">Sijainti</string>
<string name="last_position_update">Viimeisin sijainnin päivitys</string> <string name="last_position_update">Viimeisin sijainnin päivitys</string>
<string name="env_metrics_log">Ympäristöarvot</string> <string name="env_metrics_log">Ympäristöarvot</string>
@ -435,7 +432,6 @@
<string name="one_month">1 kk</string> <string name="one_month">1 kk</string>
<string name="max">Kaikki</string> <string name="max">Kaikki</string>
<string name="min">Minimi</string> <string name="min">Minimi</string>
<string name="avg">Keskiarvo</string>
<string name="expand_chart">Laajenna kaavio</string> <string name="expand_chart">Laajenna kaavio</string>
<string name="collapse_chart">Pienennä kaavio</string> <string name="collapse_chart">Pienennä kaavio</string>
<string name="unknown_age">Tuntematon ikä</string> <string name="unknown_age">Tuntematon ikä</string>
@ -613,8 +609,23 @@
<string name="ignore_mqtt">Ohita MQTT</string> <string name="ignore_mqtt">Ohita MQTT</string>
<string name="ok_to_mqtt">MQTT päällä</string> <string name="ok_to_mqtt">MQTT päällä</string>
<string name="mqtt_config">MQTT asetukset</string> <string name="mqtt_config">MQTT asetukset</string>
<string name="mqtt_status_inactive">Passiivinen</string>
<string name="mqtt_status_disconnected">Ei yhdistetty</string> <string name="mqtt_status_disconnected">Ei yhdistetty</string>
<string name="mqtt_status_disconnected_with_reason">Yhteys katkaistu — %1$s</string>
<string name="mqtt_status_connecting">Yhdistetään…</string>
<string name="mqtt_status_connected">Yhdistetty</string> <string name="mqtt_status_connected">Yhdistetty</string>
<string name="mqtt_status_reconnecting">Yhdistetään uudelleen…</string>
<string name="mqtt_status_reconnecting_with_attempt">Yhdistetään uudelleen (yritys %1$d) — %2$s</string>
<string name="mqtt_test_connection">Testaa yhteys</string>
<string name="mqtt_probe_running">Tarkistetaan välityspalvelinta…</string>
<string name="mqtt_probe_success">Yhteys onnistui. Välityspalvelin hyväksyi tunnistetiedot.</string>
<string name="mqtt_probe_success_with_info">Yhteys onnistui (%1$s)</string>
<string name="mqtt_probe_rejected">Välityspalvelin ei hyväksynyt: %1$s</string>
<string name="mqtt_probe_dns_failure">Palvelinta ei löytynyt</string>
<string name="mqtt_probe_tcp_failure">Yhteyttä välityspalvelimeen ei saada (TCP)</string>
<string name="mqtt_probe_tls_failure">TLS-yhteyden muodostus epäonnistui</string>
<string name="mqtt_probe_timeout">Aikakatkaistu %1$d ms jälkeen</string>
<string name="mqtt_probe_other_failure">Yhdistäminen epäonnistui</string>
<string name="mqtt_enabled">MQTT käytössä</string> <string name="mqtt_enabled">MQTT käytössä</string>
<string name="address">Osoite</string> <string name="address">Osoite</string>
<string name="username">Käyttäjänimi</string> <string name="username">Käyttäjänimi</string>
@ -1215,4 +1226,17 @@
<string name="desktop_tray_show">Näytä Meshtastic</string> <string name="desktop_tray_show">Näytä Meshtastic</string>
<string name="desktop_tray_quit">Lopeta</string> <string name="desktop_tray_quit">Lopeta</string>
<string name="desktop_notification_title">Meshtastic</string> <string name="desktop_notification_title">Meshtastic</string>
<string name="export_tak_data_package">Vie TAK-datapaketti</string>
<string name="clear_time_zone">Tyhjennä aikavyöhyke</string>
<string name="filter_icon">Suodatus</string>
<string name="remove_filter">Poista suodatin</string>
<string name="show_iaq_legend">Näytä ilmanlaadun selite</string>
<string name="action_show_message_status">Näytä viestin tila</string>
<string name="action_send_reply">Lähetä vastaus</string>
<string name="action_copy_message">Kopioi viesti</string>
<string name="action_select_message">Valitse viesti</string>
<string name="action_delete_message">Poista viesti</string>
<string name="action_react_with_emoji">Reaktio emojin kanssa</string>
<string name="action_select_device">Valitse laite</string>
<string name="action_select_network">Valitse verkko</string>
</resources> </resources>

View file

@ -18,6 +18,7 @@
<resources> <resources>
<string name="meshtastic_app_name">Meshtastic</string> <string name="meshtastic_app_name">Meshtastic</string>
<!-- Language tags native names (not available via .getDisplayLanguage) --> <!-- Language tags native names (not available via .getDisplayLanguage) -->
<string name="fallback_node_name">Meshtastic %1$s</string>
<string name="node_filter_placeholder">Filtre</string> <string name="node_filter_placeholder">Filtre</string>
<string name="desc_node_filter_clear">Effacer le filtre de nœud</string> <string name="desc_node_filter_clear">Effacer le filtre de nœud</string>
<string name="node_filter_title">Filtrer par</string> <string name="node_filter_title">Filtrer par</string>
@ -40,9 +41,11 @@
<string name="internal">Interne</string> <string name="internal">Interne</string>
<string name="node_sort_via_favorite">par Favoris</string> <string name="node_sort_via_favorite">par Favoris</string>
<string name="node_filter_show_ignored">Afficher uniquement les nœuds ignorés</string> <string name="node_filter_show_ignored">Afficher uniquement les nœuds ignorés</string>
<string name="node_filter_exclude_mqtt">Exclure MQTT</string>
<string name="unrecognized">Non reconnu</string> <string name="unrecognized">Non reconnu</string>
<string name="message_status_enroute">En attente d'accusé de réception</string> <string name="message_status_enroute">En attente d'accusé de réception</string>
<string name="message_status_queued">En file d'attente pour l'envoi</string> <string name="message_status_queued">En file d'attente pour l'envoi</string>
<string name="message_status_delivered">Délivré au nœud</string>
<string name="message_status_unknown">Inconnu</string> <string name="message_status_unknown">Inconnu</string>
<string name="message_status_sfpp_routing">Routage via chaîne SF++…</string> <string name="message_status_sfpp_routing">Routage via chaîne SF++…</string>
<string name="message_status_sfpp_confirmed">Confirmé via chaîne SF++</string> <string name="message_status_sfpp_confirmed">Confirmé via chaîne SF++</string>
@ -119,7 +122,8 @@
<string name="config_position_broadcast_smart_minimum_distance_summary">Distance minimale en mètres pour considérer une diffusion de position intelligente.</string> <string name="config_position_broadcast_smart_minimum_distance_summary">Distance minimale en mètres pour considérer une diffusion de position intelligente.</string>
<string name="config_position_gps_update_interval_summary">À quelle fréquence devrions-nous essayer d'obtenir une position GPS (&lt;10sec le GPS est maintenu allumé).</string> <string name="config_position_gps_update_interval_summary">À quelle fréquence devrions-nous essayer d'obtenir une position GPS (&lt;10sec le GPS est maintenu allumé).</string>
<string name="config_position_flags_summary">Champs optionnels à inclure dans les messages de position. Plus il y en a, plus le message est grand, plus cela augmentant le temps d'occupation du réseau et le risque de perte.</string> <string name="config_position_flags_summary">Champs optionnels à inclure dans les messages de position. Plus il y en a, plus le message est grand, plus cela augmentant le temps d'occupation du réseau et le risque de perte.</string>
<string name="config_power_is_power_saving_summary">Sera en veille profonde autant que possible, pour les rôles traqueur et capteur, cela inclura également la radio LoRa. N'utilisez pas ce paramètre si vous voulez utiliser votre appareil avec les applications de téléphone ou si vous utilisez un appareil sans bouton utilisateur.</string> <string name="config_power_is_power_saving_summary">Sera en veille profonde autant que possible, pour les rôles traceurs et capteur, cela inclura également la radio LoRa. N'utilisez pas ce paramètre si vous voulez utiliser votre appareil avec les applications de téléphone ou si vous utilisez un appareil sans bouton utilisateur.</string>
<string name="config_security_public_key">Généré à partir de votre clé publique et envoyé à d'autres nœuds sur le maillage pour leur permettre de calculer une clé secrète partagée.</string>
<string name="config_security_private_key">Utilisée pour créer une clé partagée avec un appareil distant.</string> <string name="config_security_private_key">Utilisée pour créer une clé partagée avec un appareil distant.</string>
<string name="config_security_admin_key">Clé publique autorisée à envoyer des messages dadministration à ce nœud.</string> <string name="config_security_admin_key">Clé publique autorisée à envoyer des messages dadministration à ce nœud.</string>
<string name="config_security_is_managed">L'appareil est géré par un administrateur de maillage, l'utilisateur ne peut accéder à aucun des paramètres de l'appareil.</string> <string name="config_security_is_managed">L'appareil est géré par un administrateur de maillage, l'utilisateur ne peut accéder à aucun des paramètres de l'appareil.</string>
@ -163,18 +167,25 @@
<string name="ip_port">Port :</string> <string name="ip_port">Port :</string>
<string name="connected">Connecté</string> <string name="connected">Connecté</string>
<string name="connection_status">Connexions actuelles :</string> <string name="connection_status">Connexions actuelles :</string>
<string name="wifi_ip">IP WiFi :</string> <string name="wifi_ip">IP du Wifi :</string>
<string name="ethernet_ip">IP Ethernet :</string> <string name="ethernet_ip">IP Ethernet :</string>
<string name="connecting">Connexion en cours</string> <string name="connecting">Connexion en cours</string>
<string name="not_connected">Non connecté</string> <string name="not_connected">Non connecté</string>
<string name="no_device_selected">Aucun appareil sélectionné</string> <string name="no_device_selected">Aucun appareil sélectionné</string>
<string name="unknown_device">Périphérique inconnu</string> <string name="unknown_device">Périphérique inconnu</string>
<string name="no_network_devices_found">Aucun périphérique réseau trouvé</string>
<string name="no_usb_devices_found">Pas de périphérique USB trouvé</string>
<string name="usb">USB</string>
<string name="demo_mode">Mode Démo</string>
<string name="connected_sleeping">Connecté à la radio, mais en mode veille</string> <string name="connected_sleeping">Connecté à la radio, mais en mode veille</string>
<string name="app_too_old">Mise à jour de lapplication requise</string> <string name="app_too_old">Mise à jour de lapplication requise</string>
<string name="must_update">Vous devez mettre à jour cette application sur l'app store (ou Github). Il est trop vieux pour dialoguer avec le micrologiciel de la radio. Veuillez lire nos <a href="https://meshtastic.org/docs/software/android/installation"> docs</a> sur ce sujet.</string> <string name="must_update">Vous devez mettre à jour cette application sur l'app store (ou Github). Il est trop vieux pour dialoguer avec le micrologiciel de la radio. Veuillez lire nos <a href="https://meshtastic.org/docs/software/android/installation"> docs</a> sur ce sujet.</string>
<string name="none">Aucun (désactivé)</string> <string name="none">Aucun (désactivé)</string>
<string name="meshtastic_service_notifications">Notifications de service</string> <string name="meshtastic_service_notifications">Notifications de service</string>
<string name="acknowledgements">Remerciements</string> <string name="acknowledgements">Remerciements</string>
<string name="open_source_libraries">Bibliothèques Open Source</string>
<string name="open_source_description">Meshtastic est construit avec les bibliothèques open source suivantes. Appuyez sur n'importe quelle bibliothèque pour voir sa licence.</string>
<string name="library_count">%1$d Bibliothèques</string>
<string name="channel_invalid">Cette URL de canal est invalide et ne peut pas être utilisée</string> <string name="channel_invalid">Cette URL de canal est invalide et ne peut pas être utilisée</string>
<string name="debug_panel">Panneau de débogage</string> <string name="debug_panel">Panneau de débogage</string>
<string name="debug_decoded_payload">Contenu décodé :</string> <string name="debug_decoded_payload">Contenu décodé :</string>
@ -207,7 +218,21 @@
<string name="match_all">Correspondre à tout | N'importe quel</string> <string name="match_all">Correspondre à tout | N'importe quel</string>
<string name="debug_clear_logs_confirm">Cela supprimera tous les paquets de journaux et les entrées de la base de données de votre appareil - c'est une réinitialisation complète, et est permanent.</string> <string name="debug_clear_logs_confirm">Cela supprimera tous les paquets de journaux et les entrées de la base de données de votre appareil - c'est une réinitialisation complète, et est permanent.</string>
<string name="clear">Effacer</string> <string name="clear">Effacer</string>
<string name="search_emoji">Rechercher des émojis...</string>
<string name="more_reactions">Plus d'actions</string>
<string name="channel_label">Canal</string> <string name="channel_label">Canal</string>
<string name="a11y_label_value">%1$s: %2$s</string>
<string name="a11y_message_from">Message de %1$s: %2$s</string>
<string name="preview_header">Entête</string>
<string name="preview_item">Élément %1$d</string>
<string name="preview_footer">Pied de page</string>
<string name="preview_pill">Exporter le paquet de données TAK</string>
<string name="preview_dot">Point</string>
<string name="preview_text">Texte</string>
<string name="preview_gauge">Jauge</string>
<string name="preview_gradient">Dégradé</string>
<string name="preview_custom_composable_line_one">Ceci est un composable personnalisé</string>
<string name="preview_custom_composable_line_two">Avec plusieurs lignes et styles</string>
<string name="message_delivery_status">Statut d'envoi du message</string> <string name="message_delivery_status">Statut d'envoi du message</string>
<string name="new_messages_below">Nouveaux messages au-dessous</string> <string name="new_messages_below">Nouveaux messages au-dessous</string>
<string name="meshtastic_messages_notifications">Notifications de message</string> <string name="meshtastic_messages_notifications">Notifications de message</string>
@ -228,10 +253,15 @@
<string name="reset_to_defaults">Rétablir les valeurs par défaut</string> <string name="reset_to_defaults">Rétablir les valeurs par défaut</string>
<string name="apply">Appliquer</string> <string name="apply">Appliquer</string>
<string name="theme">Thème</string> <string name="theme">Thème</string>
<string name="contrast">Contraste</string>
<string name="theme_light">Clair</string> <string name="theme_light">Clair</string>
<string name="theme_dark">Sombre</string> <string name="theme_dark">Sombre</string>
<string name="theme_system">Valeur par défaut du système</string> <string name="theme_system">Valeur par défaut du système</string>
<string name="choose_theme">Choisir un thème</string> <string name="choose_theme">Choisir un thème</string>
<string name="choose_contrast">Niveau de contraste</string>
<string name="contrast_standard">Standard</string>
<string name="contrast_medium">Milieu</string>
<string name="contrast_high">Haut</string>
<string name="provide_location_to_mesh">Fournir l'emplacement au maillage</string> <string name="provide_location_to_mesh">Fournir l'emplacement au maillage</string>
<string name="use_homoglyph_characters_encoding">Encodage compact pour Cyrillique</string> <string name="use_homoglyph_characters_encoding">Encodage compact pour Cyrillique</string>
<plurals name="delete_messages"> <plurals name="delete_messages">
@ -275,6 +305,7 @@
<string name="direct_message">Message direct</string> <string name="direct_message">Message direct</string>
<string name="nodedb_reset">Reconfiguration de NodeDB</string> <string name="nodedb_reset">Reconfiguration de NodeDB</string>
<string name="delivery_confirmed">Réception confirmée par le destinataire</string> <string name="delivery_confirmed">Réception confirmée par le destinataire</string>
<string name="delivery_confirmed_reboot_warning">Votre appareil peut se déconnecter et redémarrer lorsque les paramètres sont appliqués.</string>
<string name="error">Erreur</string> <string name="error">Erreur</string>
<string name="unknown_error">Une erreur inconnue s'est produite</string> <string name="unknown_error">Une erreur inconnue s'est produite</string>
<string name="ignore">Ignorer</string> <string name="ignore">Ignorer</string>
@ -317,6 +348,8 @@
<string name="currently">Actuellement :</string> <string name="currently">Actuellement :</string>
<string name="mute_status_always">Toujours muet</string> <string name="mute_status_always">Toujours muet</string>
<string name="mute_status_unmuted">Non muet</string> <string name="mute_status_unmuted">Non muet</string>
<string name="mute_status_muted_for_days">Muet pour %1$d jours, %2$s heures</string>
<string name="mute_status_muted_for_hours">Muet pour %1$s heures</string>
<string name="mute_add">Désactiver les notifications pour '%1$s' ?</string> <string name="mute_add">Désactiver les notifications pour '%1$s' ?</string>
<string name="mute_remove">Réactiver les notifications pour '%1$s' ?</string> <string name="mute_remove">Réactiver les notifications pour '%1$s' ?</string>
<string name="replace">Remplacer</string> <string name="replace">Remplacer</string>
@ -326,7 +359,10 @@
<string name="battery">Batterie</string> <string name="battery">Batterie</string>
<string name="channel_utilization">UtilCanal</string> <string name="channel_utilization">UtilCanal</string>
<string name="air_utilization">UtilAir</string> <string name="air_utilization">UtilAir</string>
<string name="device_metrics_percent_value">%1$s / %2$s%%</string>
<string name="device_metrics_voltage_value">%1$s: %2$s V</string>
<string name="device_metrics_numeric_value">%1$s</string> <string name="device_metrics_numeric_value">%1$s</string>
<string name="device_metrics_label_value">%1$s: %2$s</string>
<string name="temperature">Temp</string> <string name="temperature">Temp</string>
<string name="humidity">Hum</string> <string name="humidity">Hum</string>
<string name="soil_temperature">Temp sol</string> <string name="soil_temperature">Temp sol</string>
@ -347,12 +383,9 @@
<string name="userinfo">Infos utilisateur</string> <string name="userinfo">Infos utilisateur</string>
<string name="meshtastic_new_nodes_notifications">Notifikasyon nouvo nœud</string> <string name="meshtastic_new_nodes_notifications">Notifikasyon nouvo nœud</string>
<string name="snr">SNR</string> <string name="snr">SNR</string>
<string name="snr_definition">Signal-to-Noise Ratio, une mesure utilisée dans les communications pour quantifier le niveau du signal par rapport au niveau du bruit de fond. Dans les systèmes Meshtastic et autres systèmes sans fil, un SNR plus élevé indique un signal plus clair qui peut améliorer la fiabilité et la qualité de la transmission de données.</string>
<string name="rssi">RSSI</string> <string name="rssi">RSSI</string>
<string name="rssi_definition">Indicateur de force du signal reçu, une mesure utilisée pour déterminer le niveau de puissance reçu par l'antenne. Une valeur RSSI plus élevée indique généralement une connexion plus forte et plus stable.</string>
<string name="iaq_definition">(Qualité de l'air intérieur) valeur de l'échelle relative IAQ mesurée par Bosch BME680. Plage de valeur 0500.</string> <string name="iaq_definition">(Qualité de l'air intérieur) valeur de l'échelle relative IAQ mesurée par Bosch BME680. Plage de valeur 0500.</string>
<string name="device_metrics_log">Métriques de lappareil</string> <string name="device_metrics_log">Métriques de lappareil</string>
<string name="node_map">Carte historique des positions</string>
<string name="position_log">Position</string> <string name="position_log">Position</string>
<string name="last_position_update">Dernière mise à jour de position</string> <string name="last_position_update">Dernière mise à jour de position</string>
<string name="env_metrics_log">Métriques d'environnement</string> <string name="env_metrics_log">Métriques d'environnement</string>
@ -381,13 +414,26 @@
<string name="traceroute_duration">Durée : %1$s s</string> <string name="traceroute_duration">Durée : %1$s s</string>
<string name="traceroute_route_towards_dest">Route aller :\n\n</string> <string name="traceroute_route_towards_dest">Route aller :\n\n</string>
<string name="traceroute_route_back_to_us">Route retour :\n\n</string> <string name="traceroute_route_back_to_us">Route retour :\n\n</string>
<string name="traceroute_forward_hops">Saut vers l'avant</string>
<string name="traceroute_return_hops">Saut vers l'arrière</string>
<string name="traceroute_round_trip">Aller/Retour</string>
<string name="traceroute_no_response">Pas de réponse</string> <string name="traceroute_no_response">Pas de réponse</string>
<string name="load_1_min">Charge 1m</string>
<string name="load_5_min">Charge 5m</string>
<string name="load_15_min">Charge 15m</string>
<string name="load_1_min_description">Moyenne de charge du système d'une minute</string>
<string name="load_5_min_description">Moyenne de charge du système de cinq minutes</string>
<string name="load_15_min_description">Moyenne de charge du système de 15 minutes</string>
<string name="free_memory_description">Mémoire système disponible en octets</string>
<string name="one_hour_short">1H</string> <string name="one_hour_short">1H</string>
<string name="twenty_four_hours">24H</string> <string name="twenty_four_hours">24H</string>
<string name="one_week">1S</string> <string name="one_week">1S</string>
<string name="two_weeks">2S</string> <string name="two_weeks">2S</string>
<string name="one_month">1M</string> <string name="one_month">1M</string>
<string name="max">Max</string> <string name="max">Max</string>
<string name="min">Min</string>
<string name="expand_chart">Agrandir le graphique</string>
<string name="collapse_chart">Réduire le graphique</string>
<string name="unknown_age">Age inconnu</string> <string name="unknown_age">Age inconnu</string>
<string name="copy">Copier</string> <string name="copy">Copier</string>
<string name="alert_bell_text">Caractère d'appel !</string> <string name="alert_bell_text">Caractère d'appel !</string>
@ -401,11 +447,17 @@
<string name="channel_1">Canal 1</string> <string name="channel_1">Canal 1</string>
<string name="channel_2">Canal 2</string> <string name="channel_2">Canal 2</string>
<string name="channel_3">Canal 3</string> <string name="channel_3">Canal 3</string>
<string name="channel_4">Canal 4</string>
<string name="channel_5">Canal 5</string>
<string name="channel_6">Canal 6</string>
<string name="channel_7">Canal 7</string>
<string name="channel_8">Canal 8</string>
<string name="current">Actif</string> <string name="current">Actif</string>
<string name="voltage">Tension</string> <string name="voltage">Tension</string>
<string name="are_you_sure">Êtes-vous sûr ?</string> <string name="are_you_sure">Êtes-vous sûr ?</string>
<string name="router_role_confirmation_text"><![CDATA[J'ai lu la <a href="https://meshtastic.org/docs/configuration/radio/device/#roles">Documentation du rôle de l'appareil</a> et le billet de blog sur comment <a href="http://meshtastic.org/blog/choosing-the-right-device-role">Choisir le rôle de l'appareil approprié</a>.]]></string> <string name="router_role_confirmation_text"><![CDATA[J'ai lu la <a href="https://meshtastic.org/docs/configuration/radio/device/#roles">Documentation du rôle de l'appareil</a> et le billet de blog sur comment <a href="http://meshtastic.org/blog/choosing-the-right-device-role">Choisir le rôle de l'appareil approprié</a>.]]></string>
<string name="i_know_what_i_m_doing">Je sais ce que je fais.</string> <string name="i_know_what_i_m_doing">Je sais ce que je fais.</string>
<string name="low_battery_message">La batterie du nœud %1$s est faible (%2$d%)</string>
<string name="meshtastic_low_battery_notifications">Notifications de batterie faible</string> <string name="meshtastic_low_battery_notifications">Notifications de batterie faible</string>
<string name="low_battery_title">Batterie faible : %1$s</string> <string name="low_battery_title">Batterie faible : %1$s</string>
<string name="meshtastic_low_battery_temporary_remote_notifications">Notifications de batterie faible (nœuds favoris)</string> <string name="meshtastic_low_battery_temporary_remote_notifications">Notifications de batterie faible (nœuds favoris)</string>
@ -531,6 +583,9 @@
<string name="output_duration_milliseconds">Durée de sortie (en millisecondes)</string> <string name="output_duration_milliseconds">Durée de sortie (en millisecondes)</string>
<string name="nag_timeout_seconds">Durée de répétition de la sortie (secondes)</string> <string name="nag_timeout_seconds">Durée de répétition de la sortie (secondes)</string>
<string name="ringtone">Sonnerie</string> <string name="ringtone">Sonnerie</string>
<string name="ringtone_imported">Sonnerie importée</string>
<string name="ringtone_file_empty">Le fichier est vide</string>
<string name="ringtone_import_error">Erreur d'importation : %1$s</string>
<string name="play">Lancer</string> <string name="play">Lancer</string>
<string name="use_i2s_as_buzzer">Utiliser l'I2S comme buzzer</string> <string name="use_i2s_as_buzzer">Utiliser l'I2S comme buzzer</string>
<string name="lora_config">LoRa</string> <string name="lora_config">LoRa</string>
@ -554,8 +609,13 @@
<string name="ignore_mqtt">Ignorer MQTT</string> <string name="ignore_mqtt">Ignorer MQTT</string>
<string name="ok_to_mqtt">Transmission des paquets vers MQTT</string> <string name="ok_to_mqtt">Transmission des paquets vers MQTT</string>
<string name="mqtt_config">Configuration MQTT</string> <string name="mqtt_config">Configuration MQTT</string>
<string name="mqtt_status_inactive">Inactif</string>
<string name="mqtt_status_disconnected">Déconnecté</string> <string name="mqtt_status_disconnected">Déconnecté</string>
<string name="mqtt_status_connecting">Connexion…</string>
<string name="mqtt_status_connected">Connecté</string> <string name="mqtt_status_connected">Connecté</string>
<string name="mqtt_status_reconnecting">Reconnexion…</string>
<string name="mqtt_test_connection">Test de la connexion</string>
<string name="mqtt_probe_other_failure">Échec de la connexion</string>
<string name="mqtt_enabled">MQTT activé</string> <string name="mqtt_enabled">MQTT activé</string>
<string name="address">Adresse</string> <string name="address">Adresse</string>
<string name="username">Nom d'utilisateur</string> <string name="username">Nom d'utilisateur</string>
@ -627,6 +687,8 @@
<string name="serial_enabled">Série activée</string> <string name="serial_enabled">Série activée</string>
<string name="echo_enabled">Écho activé</string> <string name="echo_enabled">Écho activé</string>
<string name="serial_baud_rate">Vitesse de transmission série</string> <string name="serial_baud_rate">Vitesse de transmission série</string>
<string name="serial_rx_pin">RX</string>
<string name="serial_tx_pin">Tx</string>
<string name="timeout">Délai d'expiration</string> <string name="timeout">Délai d'expiration</string>
<string name="serial_mode">Mode série</string> <string name="serial_mode">Mode série</string>
<string name="override_console_serial_port">Outrepasser le port série de la console</string> <string name="override_console_serial_port">Outrepasser le port série de la console</string>
@ -661,8 +723,15 @@
<string name="distance">Distance</string> <string name="distance">Distance</string>
<string name="lux">Lux</string> <string name="lux">Lux</string>
<string name="wind">Vent</string> <string name="wind">Vent</string>
<string name="wind_speed">Vitesse du vent</string>
<string name="wind_gust">Rafales de vent</string>
<string name="wind_lull">Vent à la traîne</string>
<string name="wind_direction">Direction du vent</string>
<string name="rainfall_1h">Pluie (1h)</string>
<string name="rainfall_24h">Pluie (24h)</string>
<string name="weight">Poids</string> <string name="weight">Poids</string>
<string name="radiation">Radiation</string> <string name="radiation">Radiation</string>
<string name="one_wire_temperature">Températeur 1-Wire</string>
<string name="store_forward_config"><![CDATA[Paramètres Stocker & Transférer]]></string> <string name="store_forward_config"><![CDATA[Paramètres Stocker & Transférer]]></string>
<string name="indoor_air_quality_iaq">Qualité de l'air intérieur (IAQ)</string> <string name="indoor_air_quality_iaq">Qualité de l'air intérieur (IAQ)</string>
<string name="url">URL</string> <string name="url">URL</string>
@ -679,6 +748,7 @@
<string name="timestamp">Horodatage</string> <string name="timestamp">Horodatage</string>
<string name="heading">En-tête</string> <string name="heading">En-tête</string>
<string name="speed">Vitesse</string> <string name="speed">Vitesse</string>
<string name="speed_kmh">%1$d Km/h</string>
<string name="sats">Sats</string> <string name="sats">Sats</string>
<string name="alt">Alt</string> <string name="alt">Alt</string>
<string name="freq">Fréq</string> <string name="freq">Fréq</string>
@ -744,6 +814,11 @@
<string name="show_waypoints">Afficher les points de repère</string> <string name="show_waypoints">Afficher les points de repère</string>
<string name="show_precision_circle">Afficher les cercles de précision</string> <string name="show_precision_circle">Afficher les cercles de précision</string>
<string name="client_notification">Notification client</string> <string name="client_notification">Notification client</string>
<string name="key_verification_title">Vérification de la clé</string>
<string name="key_verification_request_title">Requête de vérification de clé</string>
<string name="key_verification_final_title">Vérification de la clé terminée</string>
<string name="duplicated_public_key_title">Clé publique dupliquée détectée</string>
<string name="low_entropy_key_title">Clé de chiffrement faible détectée</string>
<string name="compromised_keys">Clés compromises détectées, sélectionnez OK pour régénérer.</string> <string name="compromised_keys">Clés compromises détectées, sélectionnez OK pour régénérer.</string>
<string name="regenerate_private_key">Régénérer la clé privée</string> <string name="regenerate_private_key">Régénérer la clé privée</string>
<string name="regenerate_keys_confirmation">Êtes-vous sûr de vouloir régénérer votre clé privée ?\n\nLes nœuds qui peuvent avoir précédemment échangé des clés avec ce nœud devront supprimer ce nœud et ré-échanger des clés afin de reprendre une communication sécurisée.</string> <string name="regenerate_keys_confirmation">Êtes-vous sûr de vouloir régénérer votre clé privée ?\n\nLes nœuds qui peuvent avoir précédemment échangé des clés avec ce nœud devront supprimer ce nœud et ré-échanger des clés afin de reprendre une communication sécurisée.</string>
@ -796,7 +871,14 @@
<string name="type_a_message">Composer un message</string> <string name="type_a_message">Composer un message</string>
<string name="pax_metrics_log">Métriques de PAX</string> <string name="pax_metrics_log">Métriques de PAX</string>
<string name="pax">PAX</string> <string name="pax">PAX</string>
<string name="pax_total_format">PAX : %1$d</string>
<string name="pax_ble_format">B:%1$d</string>
<string name="pax_wifi_format">W :%1$d</string>
<string name="pax_total_marker">PAX : %1$s</string>
<string name="pax_ble_marker">BLE: %1$s</string>
<string name="pax_wifi_marker">Wi-Fi : %1$s</string>
<string name="no_pax_metrics_logs">Aucune métrique PAX disponible.</string> <string name="no_pax_metrics_logs">Aucune métrique PAX disponible.</string>
<string name="wifi_devices">Approvisionnement Wi-Fi pour mPWRD-OS</string>
<string name="ble_devices">Appareils Bluetooth</string> <string name="ble_devices">Appareils Bluetooth</string>
<string name="connected_device">Périphérique connecté</string> <string name="connected_device">Périphérique connecté</string>
<string name="routing_error_rate_limit_exceeded">Limite de débit dépassée. Veuillez réessayer plus tard.</string> <string name="routing_error_rate_limit_exceeded">Limite de débit dépassée. Veuillez réessayer plus tard.</string>
@ -851,6 +933,8 @@
<string name="map_type_terrain">Terrain</string> <string name="map_type_terrain">Terrain</string>
<string name="map_type_hybrid">Hybride</string> <string name="map_type_hybrid">Hybride</string>
<string name="manage_map_layers">Gérer les calques de la carte</string> <string name="manage_map_layers">Gérer les calques de la carte</string>
<string name="map_layer_formats">Les calques personnalisés prennent en charge les fichiers .kml, .kmz ou GeoJSON.</string>
<string name="no_map_layers_loaded">Aucun calque personnalisé chargé.</string>
<string name="hide_layer">Ajouter un calque</string> <string name="hide_layer">Ajouter un calque</string>
<string name="show_layer">Afficher le calque</string> <string name="show_layer">Afficher le calque</string>
<string name="remove_layer">Supprimer le calque</string> <string name="remove_layer">Supprimer le calque</string>
@ -858,6 +942,10 @@
<string name="nodes_at_this_location">Nœuds à cet emplacement</string> <string name="nodes_at_this_location">Nœuds à cet emplacement</string>
<string name="selected_map_type">Type de carte sélectionné</string> <string name="selected_map_type">Type de carte sélectionné</string>
<string name="manage_custom_tile_sources">Gérer les sources de tuiles personnalisées</string> <string name="manage_custom_tile_sources">Gérer les sources de tuiles personnalisées</string>
<string name="add_custom_tile_source">Ajouter un réseau de tuile personnalisée</string>
<string name="no_custom_tile_sources_found">Aucune source de tuiles personnalisées trouvée.</string>
<string name="edit_custom_tile_source">Modifier le réseau de tuile personnalisée</string>
<string name="delete_custom_tile_source">Supprimer le réseau de tuile personnalisée</string>
<string name="name_cannot_be_empty">Le nom ne peut pas être vide.</string> <string name="name_cannot_be_empty">Le nom ne peut pas être vide.</string>
<string name="provider_name_exists">Le nom du fournisseur existe déjà.</string> <string name="provider_name_exists">Le nom du fournisseur existe déjà.</string>
<string name="url_cannot_be_empty">URL ne peut être vide.</string> <string name="url_cannot_be_empty">URL ne peut être vide.</string>
@ -951,6 +1039,7 @@
<string name="firmware_update_release_notes">Notes de Version</string> <string name="firmware_update_release_notes">Notes de Version</string>
<string name="firmware_update_unknown_error">Une erreur inconnue s'est produite</string> <string name="firmware_update_unknown_error">Une erreur inconnue s'est produite</string>
<string name="firmware_update_node_info_missing">Les informations de l'utilisateur du nœud sont manquantes.</string> <string name="firmware_update_node_info_missing">Les informations de l'utilisateur du nœud sont manquantes.</string>
<string name="firmware_update_battery_low">Batterie trop faible (%1$d%). Veuillez charger votre appareil avant de mettre à jour.</string>
<string name="firmware_update_retrieval_failed">Impossible de récupérer le fichier firmware.</string> <string name="firmware_update_retrieval_failed">Impossible de récupérer le fichier firmware.</string>
<string name="firmware_update_usb_failed">Échec de la mise à jour USB</string> <string name="firmware_update_usb_failed">Échec de la mise à jour USB</string>
<string name="firmware_update_hash_rejected">Intégrité (hash) du firmware rejetée. Veuillez réessayer ou mettre à jours l'appareil via USB.</string> <string name="firmware_update_hash_rejected">Intégrité (hash) du firmware rejetée. Veuillez réessayer ou mettre à jours l'appareil via USB.</string>
@ -1027,8 +1116,10 @@
<string name="bluetooth_feature_config">Configuration</string> <string name="bluetooth_feature_config">Configuration</string>
<string name="bluetooth_feature_config_description">Gérer à distance sans fil les paramètres et les canaux de votre appareil.</string> <string name="bluetooth_feature_config_description">Gérer à distance sans fil les paramètres et les canaux de votre appareil.</string>
<string name="map_style_selection">Sélection du style de carte</string> <string name="map_style_selection">Sélection du style de carte</string>
<string name="local_stats_battery">Batterie : %1$d%</string>
<string name="local_stats_nodes">Nœuds : %1$d en ligne / %2$d au total</string> <string name="local_stats_nodes">Nœuds : %1$d en ligne / %2$d au total</string>
<string name="local_stats_uptime">Temps de disponibilité : %1$s</string> <string name="local_stats_uptime">Temps de disponibilité : %1$s</string>
<string name="local_stats_utilization">ChUtil: %1$s% | AirTX: %2$s%</string>
<string name="local_stats_traffic">Trafic : TX %1$d / RX %2$d (D: %3$d)</string> <string name="local_stats_traffic">Trafic : TX %1$d / RX %2$d (D: %3$d)</string>
<string name="local_stats_relays">Relais : %1$d (annulé: %2$d)</string> <string name="local_stats_relays">Relais : %1$d (annulé: %2$d)</string>
<string name="local_stats_diagnostics_prefix">Diagnostiques : %1$s</string> <string name="local_stats_diagnostics_prefix">Diagnostiques : %1$s</string>
@ -1042,11 +1133,94 @@
<string name="refresh">Actualiser</string> <string name="refresh">Actualiser</string>
<string name="updated">Mis à jour</string> <string name="updated">Mis à jour</string>
<!-- Network Map Layers --> <!-- Network Map Layers -->
<string name="add_network_layer">Ajouter une couche de réseau</string>
<string name="local_mbtiles_file">Fichier local MBTiles</string>
<string name="add_local_mbtiles_file">Ajouter un fichier local MBTiles</string>
<string name="tak">TAK (ATAK)</string>
<string name="tak_config">Configuration TAK</string>
<string name="tak_server_enabled">Activer le serveur TAK local</string>
<string name="tak_server_enabled_desc">Démarre un serveur TCP sur le port 8089 pour les connexions ATAK</string>
<string name="tak_team">Couleur de l'équipe</string>
<string name="tak_role">Rôle Membre</string>
<string name="tak_team_unspecified_color">Non spécifié</string>
<string name="tak_team_white">Blanc</string>
<string name="tak_team_yellow">Jaune</string>
<string name="tak_team_orange">Orange</string>
<string name="tak_team_magenta">Magenta</string>
<string name="tak_team_red">Rouge</string> <string name="tak_team_red">Rouge</string>
<string name="tak_team_maroon">Marron</string>
<string name="tak_team_purple">Pourpre</string>
<string name="tak_team_dark_blue">Bleu foncé</string>
<string name="tak_team_blue">Bleu</string> <string name="tak_team_blue">Bleu</string>
<string name="tak_team_cyan">Cyan</string>
<string name="tak_team_teal">Turquoise</string>
<string name="tak_team_green">Vert</string> <string name="tak_team_green">Vert</string>
<string name="tak_team_dark_green">Vert Foncé</string>
<string name="tak_team_brown">Marron</string>
<string name="tak_role_unspecified">Non spécifié</string>
<string name="tak_role_teammember">Membre de l'équipe</string>
<string name="tak_role_teamlead">Chef d'équipe</string>
<string name="tak_role_hq">Quartier général</string>
<string name="tak_role_sniper">Tireur d'élite</string>
<string name="tak_role_medic">Medic</string>
<string name="tak_role_forwardobserver">Observateur de transfert</string>
<string name="tak_role_rto">Opérateur de radio téléphonie</string>
<string name="tak_role_k9">Doggo (K9)</string>
<string name="traffic_management">Gestion du trafic</string>
<string name="traffic_management_config">Configuration de la gestion du trafic</string>
<string name="traffic_management_enabled">Module activé</string> <string name="traffic_management_enabled">Module activé</string>
<string name="traffic_management_position_dedup">Déduplication de Position</string>
<string name="traffic_management_position_precision">Précision de position (octets)</string>
<string name="traffic_management_position_min_interval">Intervalle de position min (secs)</string>
<string name="traffic_management_nodeinfo_direct_response">Réponse directe de NodeInfo</string>
<string name="traffic_management_nodeinfo_direct_response_max_hops">Max de saut pour une réponse directe</string>
<string name="traffic_management_rate_limit_enabled">Limitation de débit</string>
<string name="traffic_management_rate_limit_window">Fenêtre de limitation de taux (secs)</string>
<string name="traffic_management_rate_limit_max_packets">Paquets maximum dans la fenêtre</string>
<string name="traffic_management_drop_unknown_enabled">Ignorer les paquets inconnus</string>
<string name="traffic_management_unknown_packet_threshold">Seuil de paquets inconnu</string>
<string name="traffic_management_exhaust_hop_telemetry">Télémétrie locale uniquement (Relays)</string>
<string name="traffic_management_exhaust_hop_position">Position locale uniquement (Relays)</string>
<string name="traffic_management_router_preserve_hops">Conserver les sauts du Routeur</string>
<string name="note">Note</string>
<string name="device_storage_ui_title">Stockage de l'appareil &amp; UI (lecture seule)</string>
<string name="device_theme_language">Thème %1$s, Langue %2$s</string>
<string name="files_available">Fichiers disponibles (%1$d ) :</string>
<string name="file_entry">- %1$s (%2$d octets)</string>
<string name="no_files_manifested">Aucun fichier affiché.</string>
<string name="connect">Connecter</string> <string name="connect">Connecter</string>
<string name="done">Terminé</string> <string name="done">Terminé</string>
<string name="wifi_provisioning">Approvisionnement Wi-Fi pour mPWRD-OS</string>
<string name="wifi_provision_description">Fournissez les identifiants Wi-Fi à votre appareil mPWRD-OS via Bluetooth.</string>
<string name="wifi_provision_mpwrd_disclaimer">En savoir plus sur le projet mPWRD-OS\nhttps://github.com/mPWRD-OS</string>
<string name="wifi_provision_scanning_ble">Recherche de l'appareil</string>
<string name="wifi_provision_device_found">Appareil détecté</string>
<string name="wifi_provision_device_found_detail">Prêt à rechercher des réseaux WiFi.</string>
<string name="wifi_provision_scan_networks">Rechercher des réseaux</string>
<string name="wifi_provision_scanning_wifi">Recherche…</string>
<string name="wifi_provision_sending_credentials">Application de la configuration WiFi…</string>
<string name="wifi_provision_no_networks">Aucun réseau trouvé</string>
<string name="wifi_provision_connect_failed">Impossible de se connecter : %1$s</string>
<string name="wifi_provision_scan_failed">Échec de la recherche des réseaux WiFi : %1$s</string>
<string name="wifi_provision_signal_strength">%1$d%</string>
<string name="wifi_provision_available_networks">Réseaux disponibles</string>
<string name="wifi_provision_ssid_label">Nom du réseau (SSID)</string>
<string name="wifi_provision_ssid_placeholder">Saisir ou sélectionnez un réseau</string>
<string name="wifi_provision_status_applied">WiFi configuré avec succès !</string>
<string name="wifi_provision_status_failed">Impossible d'appliquer la configuration WiFi</string>
<string name="desktop_tray_tooltip">Meshtastic application de bureau</string>
<string name="desktop_tray_show">Afficher Meshtastic</string>
<string name="desktop_tray_quit">Quitter</string>
<string name="desktop_notification_title">Meshtastic</string> <string name="desktop_notification_title">Meshtastic</string>
<string name="export_tak_data_package">Exporter le paquet de données TAK</string>
<string name="filter_icon">Filtre</string>
<string name="remove_filter">Supprimer le filtre</string>
<string name="action_show_message_status">Afficher le statut du message</string>
<string name="action_send_reply">Envoyer une réponse</string>
<string name="action_copy_message">Copier le message</string>
<string name="action_select_message">Sélectionner le message</string>
<string name="action_delete_message">Supprimer le message</string>
<string name="action_react_with_emoji">Réagir avec un emoji</string>
<string name="action_select_device">Sélectionner l'appareil</string>
<string name="action_select_network">Sélectionner le réseau</string>
</resources> </resources>

View file

@ -190,10 +190,7 @@
<string name="encryption_pkc">Cóid Poiblí Eochair</string> <string name="encryption_pkc">Cóid Poiblí Eochair</string>
<string name="encryption_error">Mícomhoiriúnacht na heochrach phoiblí</string> <string name="encryption_error">Mícomhoiriúnacht na heochrach phoiblí</string>
<string name="meshtastic_new_nodes_notifications">Fógartha faoi na nodes nua</string> <string name="meshtastic_new_nodes_notifications">Fógartha faoi na nodes nua</string>
<string name="snr_definition">Ráta Sigineal go Torann, tomhas a úsáidtear i gcomhfhreagras chun an leibhéal de shígnéil inmhianaithe agus torann cúlra a mheas. I Meshtastic agus i gcórais gan sreang eile, ciallaíonn SNR níos airde go bhfuil sígneál níos soiléire ann agus ábalta méadú ar chreideamh agus cáilíocht an tarchur sonraí.</string>
<string name="rssi_definition">Táscaire Cumhachta Athnuachana Aithint an Aoise, tomhas a úsáidtear chun leibhéal cumhachta atá faighte ag an antsnáithe a mheas. Léiríonn RSSI níos airde gnóthachtáil níos laige atá i gceangal seasmhach agus níos láidre.</string>
<string name="iaq_definition">(Cáilíocht Aeir Inmheánach) scála ábhartha den luach QAÍ a thomhas ag Bosch BME680. Scála Luach 0500.</string> <string name="iaq_definition">(Cáilíocht Aeir Inmheánach) scála ábhartha den luach QAÍ a thomhas ag Bosch BME680. Scála Luach 0500.</string>
<string name="node_map">Léarscáil an Node</string>
<string name="administration">Rialachas</string> <string name="administration">Rialachas</string>
<string name="remote_admin">Rialú iargúlta</string> <string name="remote_admin">Rialú iargúlta</string>
<string name="bad">Go dona</string> <string name="bad">Go dona</string>
@ -230,4 +227,5 @@
<!-- Compass --> <!-- Compass -->
<!-- Message Filter --> <!-- Message Filter -->
<!-- Network Map Layers --> <!-- Network Map Layers -->
<string name="filter_icon">Scagaire</string>
</resources> </resources>

View file

@ -165,4 +165,5 @@
<!-- Compass --> <!-- Compass -->
<!-- Message Filter --> <!-- Message Filter -->
<!-- Network Map Layers --> <!-- Network Map Layers -->
<string name="filter_icon">Filtro</string>
</resources> </resources>

View file

@ -148,4 +148,5 @@
<!-- Compass --> <!-- Compass -->
<!-- Message Filter --> <!-- Message Filter -->
<!-- Network Map Layers --> <!-- Network Map Layers -->
<string name="filter_icon">פילטר</string>
</resources> </resources>

View file

@ -169,4 +169,5 @@
<!-- Network Map Layers --> <!-- Network Map Layers -->
<string name="tak_team_red">Crveno</string> <string name="tak_team_red">Crveno</string>
<string name="desktop_notification_title">Meshtastic</string> <string name="desktop_notification_title">Meshtastic</string>
<string name="filter_icon">Filtriraj</string>
</resources> </resources>

View file

@ -186,10 +186,7 @@
<string name="encryption_pkc">Chifreman Kle Piblik</string> <string name="encryption_pkc">Chifreman Kle Piblik</string>
<string name="encryption_error">Pa matche kle piblik</string> <string name="encryption_error">Pa matche kle piblik</string>
<string name="meshtastic_new_nodes_notifications">Notifikasyon nouvo nœud</string> <string name="meshtastic_new_nodes_notifications">Notifikasyon nouvo nœud</string>
<string name="snr_definition">Rapò Siynal sou Bri, yon mezi ki itilize nan kominikasyon pou mezire nivo siynal vle a kont nivo bri ki nan anviwònman an. Nan Meshtastic ak lòt sistèm san fil, yon SNR pi wo endike yon siynal pi klè ki ka amelyore fyab ak kalite transmisyon done.</string>
<string name="rssi_definition">Endikatè Fòs Siynal Resevwa, yon mezi ki itilize pou detèmine nivo pouvwa siynal ki resevwa pa antèn nan. Yon RSSI pi wo jeneralman endike yon koneksyon pi fò ak plis estab.</string>
<string name="iaq_definition">(Kalite Lèy Entèryè) echèl relatif valè IAQ jan li mezire pa Bosch BME680. Ranje valè 0500.</string> <string name="iaq_definition">(Kalite Lèy Entèryè) echèl relatif valè IAQ jan li mezire pa Bosch BME680. Ranje valè 0500.</string>
<string name="node_map">Kat Nœud</string>
<string name="administration">Administrasyon</string> <string name="administration">Administrasyon</string>
<string name="remote_admin">Administrasyon Remote</string> <string name="remote_admin">Administrasyon Remote</string>
<string name="bad">Move</string> <string name="bad">Move</string>
@ -218,4 +215,5 @@
<!-- Compass --> <!-- Compass -->
<!-- Message Filter --> <!-- Message Filter -->
<!-- Network Map Layers --> <!-- Network Map Layers -->
<string name="filter_icon">Filtre</string>
</resources> </resources>

View file

@ -316,12 +316,9 @@
<string name="encryption_error">Publikus kulcs nem egyezik</string> <string name="encryption_error">Publikus kulcs nem egyezik</string>
<string name="meshtastic_new_nodes_notifications">Új állomás értesítések</string> <string name="meshtastic_new_nodes_notifications">Új állomás értesítések</string>
<string name="snr">SNR</string> <string name="snr">SNR</string>
<string name="snr_definition">Jelzaj arány (SNR): a kommunikációban a kívánt jel szintjének és a háttérzaj szintjének aránya. A Meshtastic és más vezeték nélküli rendszerek esetében a magasabb SNR tisztább jelet jelent, ami javítja az adatátvitel megbízhatóságát és minőségét.</string>
<string name="rssi">RSSI</string> <string name="rssi">RSSI</string>
<string name="rssi_definition">Vett jelerősség-mutató (RSSI): az antenna által vett jel teljesítményszintjének mérése. A magasabb RSSI általában erősebb, stabilabb kapcsolatot jelez.</string>
<string name="iaq_definition">(Beltéri levegőminőség) relatív IAQ érték a Bosch BME680 szenzor alapján. Értéktartomány: 0500.</string> <string name="iaq_definition">(Beltéri levegőminőség) relatív IAQ érték a Bosch BME680 szenzor alapján. Értéktartomány: 0500.</string>
<string name="device_metrics_log">Eszközmetrikák</string> <string name="device_metrics_log">Eszközmetrikák</string>
<string name="node_map">Állomás Térkép</string>
<string name="position_log">Pozíció</string> <string name="position_log">Pozíció</string>
<string name="last_position_update">Utolsó pozíciófrissítés</string> <string name="last_position_update">Utolsó pozíciófrissítés</string>
<string name="env_metrics_log">Környezeti metrikák</string> <string name="env_metrics_log">Környezeti metrikák</string>
@ -853,4 +850,5 @@
<string name="tak_team_green">Zöld</string> <string name="tak_team_green">Zöld</string>
<string name="connect">Csatlakozás</string> <string name="connect">Csatlakozás</string>
<string name="desktop_notification_title">Meshtastic</string> <string name="desktop_notification_title">Meshtastic</string>
<string name="filter_icon">Filter</string>
</resources> </resources>

View file

@ -355,12 +355,9 @@
<string name="userinfo">Informazioni Utente</string> <string name="userinfo">Informazioni Utente</string>
<string name="meshtastic_new_nodes_notifications">Notifiche di nuovi nodi</string> <string name="meshtastic_new_nodes_notifications">Notifiche di nuovi nodi</string>
<string name="snr">SNR</string> <string name="snr">SNR</string>
<string name="snr_definition">Rapporto segnale-rumore (Signal-to-Noise Ratio), una misura utilizzata nelle comunicazioni per quantificare il livello di un segnale desiderato rispetto al livello di rumore di fondo. In Meshtastic e in altri sistemi wireless, un SNR più elevato indica un segnale più chiaro che può migliorare l'affidabilità e la qualità della trasmissione dei dati.</string>
<string name="rssi">RSSI</string> <string name="rssi">RSSI</string>
<string name="rssi_definition">Indicatore di forza del segnale ricevuto (Received Signal Strength Indicator), una misura utilizzata per determinare il livello di potenza ricevuto dall'antenna. Un valore RSSI più elevato indica generalmente una connessione più forte e più stabile.</string>
<string name="iaq_definition">(Qualità dell'aria interna) scala relativa del valore della qualità dell'aria indoor, misurato da Bosch BME680. Valore Intervallo 0500.</string> <string name="iaq_definition">(Qualità dell'aria interna) scala relativa del valore della qualità dell'aria indoor, misurato da Bosch BME680. Valore Intervallo 0500.</string>
<string name="device_metrics_log">Metriche Dispositivo</string> <string name="device_metrics_log">Metriche Dispositivo</string>
<string name="node_map">Mappa Dei Nodi</string>
<string name="position_log">Posizione</string> <string name="position_log">Posizione</string>
<string name="last_position_update">Aggiornamento ultima posizione</string> <string name="last_position_update">Aggiornamento ultima posizione</string>
<string name="env_metrics_log">Metriche Ambientali</string> <string name="env_metrics_log">Metriche Ambientali</string>
@ -963,4 +960,5 @@
<string name="connect">Connetti</string> <string name="connect">Connetti</string>
<string name="done">Fatto</string> <string name="done">Fatto</string>
<string name="desktop_notification_title">Meshtastic</string> <string name="desktop_notification_title">Meshtastic</string>
<string name="filter_icon">Filtro</string>
</resources> </resources>

View file

@ -275,11 +275,8 @@
<string name="encryption_error">公開キーが一致しません</string> <string name="encryption_error">公開キーが一致しません</string>
<string name="meshtastic_new_nodes_notifications">新しいノードの通知</string> <string name="meshtastic_new_nodes_notifications">新しいノードの通知</string>
<string name="snr">SN比</string> <string name="snr">SN比</string>
<string name="snr_definition">信号対イズ比SN比は、通信において、目的の信号のレベルを背景イズのレベルに対して定量化するために使用される尺度です。Meshtasticや他の無線システムでは、SN比が高いほど信号が鮮明であることを示し、データ伝送の信頼性と品質を向上させることができます。</string>
<string name="rssi">RSSI</string> <string name="rssi">RSSI</string>
<string name="rssi_definition">受信信号強度インジケーターRSSIは、アンテナで受信している電力レベルを測定するための指標です。一般的にRSSI値が高いほど、より強力で安定した接続を示します。</string>
<string name="iaq_definition">(屋内空気品質) 相対スケールIAQ値は、ボッシュBME680によって測定されます。 値の範囲は 0-500。</string> <string name="iaq_definition">(屋内空気品質) 相対スケールIAQ値は、ボッシュBME680によって測定されます。 値の範囲は 0-500。</string>
<string name="node_map">ノードマップ</string>
<string name="position_log">位置</string> <string name="position_log">位置</string>
<string name="administration">管理</string> <string name="administration">管理</string>
<string name="remote_admin">リモート管理</string> <string name="remote_admin">リモート管理</string>
@ -655,4 +652,5 @@
<string name="traffic_management_enabled">モジュール有効</string> <string name="traffic_management_enabled">モジュール有効</string>
<string name="connect">接続</string> <string name="connect">接続</string>
<string name="desktop_notification_title">Meshtastic</string> <string name="desktop_notification_title">Meshtastic</string>
<string name="filter_icon">絞り込み</string>
</resources> </resources>

View file

@ -213,11 +213,8 @@
<string name="encryption_error">공개 키가 일치하지 않습니다</string> <string name="encryption_error">공개 키가 일치하지 않습니다</string>
<string name="meshtastic_new_nodes_notifications">새로운 노드 알림</string> <string name="meshtastic_new_nodes_notifications">새로운 노드 알림</string>
<string name="snr">SNR</string> <string name="snr">SNR</string>
<string name="snr_definition">통신에서 원하는 신호의 수준을 배경 잡음의 수준과 비교하여 정량화하는 데 사용되는 신호 대 잡음비 Signal-to-Noise Ratio, SNR는 Meshtastic와 같은 무선 시스템에서 SNR이 높을수록 더 선명한 신호를 나타내어 데이터 전송의 안정성과 품질을 향상시킬 수 있습니다.</string>
<string name="rssi">RSSI</string> <string name="rssi">RSSI</string>
<string name="rssi_definition">수신 신호 강도 지표 Received Signal Strength Indicator, RSSI는 안테나가 수신하는 신호의 전력 수준을 측정하는 데 사용되는 지표입니다. RSSI 값이 높을수록 일반적으로 더 강력하고 안정적인 연결을 나타냅니다.</string>
<string name="iaq_definition">(실내공기질) Bosch BME680으로 측정한 상대적 척도 IAQ 값. 범위 0500.</string> <string name="iaq_definition">(실내공기질) Bosch BME680으로 측정한 상대적 척도 IAQ 값. 범위 0500.</string>
<string name="node_map">노드 지도</string>
<string name="position_log">위치</string> <string name="position_log">위치</string>
<string name="last_position_update">최근 위치 업데이트</string> <string name="last_position_update">최근 위치 업데이트</string>
<string name="administration">관리</string> <string name="administration">관리</string>
@ -542,4 +539,5 @@
<string name="tak_team_green">초록</string> <string name="tak_team_green">초록</string>
<string name="connect">연결</string> <string name="connect">연결</string>
<string name="desktop_notification_title">Meshtastic</string> <string name="desktop_notification_title">Meshtastic</string>
<string name="filter_icon">필터</string>
</resources> </resources>

View file

@ -190,7 +190,6 @@
<string name="meshtastic_new_nodes_notifications">Naujo įtaiso pranešimas</string> <string name="meshtastic_new_nodes_notifications">Naujo įtaiso pranešimas</string>
<string name="snr">SNR</string> <string name="snr">SNR</string>
<string name="rssi">RSSI</string> <string name="rssi">RSSI</string>
<string name="node_map">Įtaisų žemėlapis</string>
<string name="administration">Administravimas</string> <string name="administration">Administravimas</string>
<string name="remote_admin">Nuotolinis administravimas</string> <string name="remote_admin">Nuotolinis administravimas</string>
<string name="bad">Silpnas</string> <string name="bad">Silpnas</string>
@ -238,4 +237,5 @@
<!-- Message Filter --> <!-- Message Filter -->
<!-- Network Map Layers --> <!-- Network Map Layers -->
<string name="tak_team_red">Raudona</string> <string name="tak_team_red">Raudona</string>
<string name="filter_icon">Filtras</string>
</resources> </resources>

View file

@ -201,11 +201,8 @@
<string name="encryption_error">Publieke sleutel komt niet overeen</string> <string name="encryption_error">Publieke sleutel komt niet overeen</string>
<string name="meshtastic_new_nodes_notifications">Nieuwe node meldingen</string> <string name="meshtastic_new_nodes_notifications">Nieuwe node meldingen</string>
<string name="snr">SNR</string> <string name="snr">SNR</string>
<string name="snr_definition">Signal-to-Noise Ratio, een meeting die wordt gebruikt in de communicatie om het niveau van een gewenst signaal tegenover achtergrondlawaai te kwantificeren. In Meshtastische en andere draadloze systemen geeft een hoger SNR een zuiverder signaal aan dat de betrouwbaarheid en kwaliteit van de gegevensoverdracht kan verbeteren.</string>
<string name="rssi">RSSI</string> <string name="rssi">RSSI</string>
<string name="rssi_definition">Ontvangen Signal Sterkte Indicator, een meting gebruikt om het stroomniveau te bepalen dat de antenne ontvangt. Een hogere RSSI-waarde geeft een sterkere en stabielere verbinding aan.</string>
<string name="iaq_definition">(Binnenluchtkwaliteit) relatieve schaal IAQ waarde gemeten door Bosch BME680. Waarde tussen 0 en 500.</string> <string name="iaq_definition">(Binnenluchtkwaliteit) relatieve schaal IAQ waarde gemeten door Bosch BME680. Waarde tussen 0 en 500.</string>
<string name="node_map">Node Kaart</string>
<string name="position_log">Positie</string> <string name="position_log">Positie</string>
<string name="administration">Beheer</string> <string name="administration">Beheer</string>
<string name="remote_admin">Extern beheer</string> <string name="remote_admin">Extern beheer</string>
@ -418,4 +415,5 @@
<string name="tak_team_blue">Blauw</string> <string name="tak_team_blue">Blauw</string>
<string name="tak_team_green">Groen</string> <string name="tak_team_green">Groen</string>
<string name="connect">Verbinding maken</string> <string name="connect">Verbinding maken</string>
<string name="filter_icon">Filter</string>
</resources> </resources>

View file

@ -194,11 +194,8 @@
<string name="encryption_error">Direktemeldinger bruker den nye offentlige nøkkelinfrastrukturen for kryptering. Krever firmware versjon 2.5 eller høyere.</string> <string name="encryption_error">Direktemeldinger bruker den nye offentlige nøkkelinfrastrukturen for kryptering. Krever firmware versjon 2.5 eller høyere.</string>
<string name="meshtastic_new_nodes_notifications">Varsel om nye noder</string> <string name="meshtastic_new_nodes_notifications">Varsel om nye noder</string>
<string name="snr">SNR</string> <string name="snr">SNR</string>
<string name="snr_definition">Signal-to-Noise Ratio, et mål som brukes i kommunikasjon for å sette nivået av et ønsket signal til bakgrunnstrøynivået. I Meshtastic og andre trådløse systemer tyder et høyere SNR på et klarere signal som kan forbedre påliteligheten og kvaliteten på dataoverføringen.</string>
<string name="rssi">RSSI</string> <string name="rssi">RSSI</string>
<string name="rssi_definition">\"Received Signal Strength Indicator\", en måling som brukes til å bestemme strømnivået som mottas av antennen. Høyere RSSI verdi indikerer generelt en sterkere og mer stabil forbindelse.</string>
<string name="iaq_definition">(Innendørs luftkvalitet) relativ skala IAQ-verdi målt ved Bosch BME680. Verdi 0500.</string> <string name="iaq_definition">(Innendørs luftkvalitet) relativ skala IAQ-verdi målt ved Bosch BME680. Verdi 0500.</string>
<string name="node_map">Nodekart</string>
<string name="administration">Administrasjon</string> <string name="administration">Administrasjon</string>
<string name="remote_admin">Fjernadministrasjon</string> <string name="remote_admin">Fjernadministrasjon</string>
<string name="bad">Dårlig</string> <string name="bad">Dårlig</string>
@ -241,4 +238,5 @@
<!-- Compass --> <!-- Compass -->
<!-- Message Filter --> <!-- Message Filter -->
<!-- Network Map Layers --> <!-- Network Map Layers -->
<string name="filter_icon">Filter</string>
</resources> </resources>

View file

@ -333,12 +333,9 @@
<string name="userinfo">Informacje o użytkowniku</string> <string name="userinfo">Informacje o użytkowniku</string>
<string name="meshtastic_new_nodes_notifications">Powiadomienia o nowych węzłach</string> <string name="meshtastic_new_nodes_notifications">Powiadomienia o nowych węzłach</string>
<string name="snr">SNR:</string> <string name="snr">SNR:</string>
<string name="snr_definition">Współczynnik sygnału do szumu (Signal-to-Noise Ratio) - miara stosowana w komunikacji do określania poziomu pożądanego sygnału w stosunku do poziomu szumu tła. W Meshtastic i innych systemach bezprzewodowych wyższy współczynnik SNR oznacza czystszy sygnał, który może zwiększyć niezawodność i jakość transmisji danych.</string>
<string name="rssi">RSSI:</string> <string name="rssi">RSSI:</string>
<string name="rssi_definition">Received Signal Strength Indicator - miara używana do określenia poziomu mocy odbieranej przez antenę. Wyższa wartość RSSI zazwyczaj oznacza silniejsze i bardziej stabilne połączenie.</string>
<string name="iaq_definition">Jakość powietrza w pomieszczeniach (Indoor Air Quality) - wartość względna w skali IAQ mierzona czujnikiem BME680. Zakres wartości: 0500.</string> <string name="iaq_definition">Jakość powietrza w pomieszczeniach (Indoor Air Quality) - wartość względna w skali IAQ mierzona czujnikiem BME680. Zakres wartości: 0500.</string>
<string name="device_metrics_log">Metryka urządzenia</string> <string name="device_metrics_log">Metryka urządzenia</string>
<string name="node_map">Ślad na mapie</string>
<string name="position_log">Pozycjonowanie</string> <string name="position_log">Pozycjonowanie</string>
<string name="last_position_update">Ostatnia aktualizacja lokalizacji</string> <string name="last_position_update">Ostatnia aktualizacja lokalizacji</string>
<string name="env_metrics_log">Metryki środowiskowe</string> <string name="env_metrics_log">Metryki środowiskowe</string>
@ -752,4 +749,5 @@
<string name="connect">Połącz</string> <string name="connect">Połącz</string>
<string name="done">Wykonano</string> <string name="done">Wykonano</string>
<string name="desktop_notification_title">Meshtastic</string> <string name="desktop_notification_title">Meshtastic</string>
<string name="filter_icon">Filtr</string>
</resources> </resources>

View file

@ -230,11 +230,8 @@
<string name="encryption_error">Chave pública não confere</string> <string name="encryption_error">Chave pública não confere</string>
<string name="meshtastic_new_nodes_notifications">Novas notificações de nó</string> <string name="meshtastic_new_nodes_notifications">Novas notificações de nó</string>
<string name="snr">SNR</string> <string name="snr">SNR</string>
<string name="snr_definition">Relação sinal-para-ruído, uma medida utilizada nas comunicações para quantificar o nível de um sinal desejado para o nível de ruído de fundo. Na Meshtastic e outros sistemas sem fios, uma SNR maior indica um sinal mais claro que pode melhorar a confiabilidade e a qualidade da transmissão de dados.</string>
<string name="rssi">RSSI</string> <string name="rssi">RSSI</string>
<string name="rssi_definition">Indicador de Força de Sinal Recebido, uma medida usada para determinar o nível de potência que está sendo recebida pela antena. Um valor maior de RSSI geralmente indica uma conexão mais forte e mais estável.</string>
<string name="iaq_definition">(Qualidade do ar interior) valor relativo da escala IAQ medido pelo Bosch BME680. Intervalo de Valor de 0500.</string> <string name="iaq_definition">(Qualidade do ar interior) valor relativo da escala IAQ medido pelo Bosch BME680. Intervalo de Valor de 0500.</string>
<string name="node_map">Mapa do nó</string>
<string name="position_log">Posição</string> <string name="position_log">Posição</string>
<string name="last_position_update">Atualização da última posição</string> <string name="last_position_update">Atualização da última posição</string>
<string name="administration">Administração</string> <string name="administration">Administração</string>
@ -668,4 +665,5 @@
<string name="tak_team_green">Verde</string> <string name="tak_team_green">Verde</string>
<string name="done">Concluído</string> <string name="done">Concluído</string>
<string name="desktop_notification_title">Meshtastic</string> <string name="desktop_notification_title">Meshtastic</string>
<string name="filter_icon">Filtro</string>
</resources> </resources>

View file

@ -219,11 +219,8 @@
<string name="encryption_error">Incompatibilidade de chave pública</string> <string name="encryption_error">Incompatibilidade de chave pública</string>
<string name="meshtastic_new_nodes_notifications">Notificações de novos nodes</string> <string name="meshtastic_new_nodes_notifications">Notificações de novos nodes</string>
<string name="snr">SNR</string> <string name="snr">SNR</string>
<string name="snr_definition">Relação sinal-para-ruído, uma medida utilizada nas comunicações para quantificar o nível de um sinal desejado com o nível de ruído de fundo. Em Meshtastic e outros sistemas sem fio. Quanto mais alta for a relação sinal-ruído, menor é o efeito do ruído de fundo sobre a deteção ou medição do sinal.</string>
<string name="rssi">RSSI</string> <string name="rssi">RSSI</string>
<string name="rssi_definition">Indicador de Força de Sinal Recebido, uma medida usada para determinar o nível de energia que está a ser recebido pela antena. Um valor mais elevado de RSSI geralmente indica uma conexão mais forte e mais estável.</string>
<string name="iaq_definition">(Qualidade do ar interior) valor relativo da escala IAQ conforme medida por Bosch BME680. Entre 0500.</string> <string name="iaq_definition">(Qualidade do ar interior) valor relativo da escala IAQ conforme medida por Bosch BME680. Entre 0500.</string>
<string name="node_map">Mapa de nodes</string>
<string name="position_log">Posição</string> <string name="position_log">Posição</string>
<string name="administration">Administração</string> <string name="administration">Administração</string>
<string name="remote_admin">Administração Remota</string> <string name="remote_admin">Administração Remota</string>
@ -518,4 +515,5 @@
<string name="tak_team_green">Verde</string> <string name="tak_team_green">Verde</string>
<string name="connect">Ligar</string> <string name="connect">Ligar</string>
<string name="desktop_notification_title">Nome do nó de alternativo</string> <string name="desktop_notification_title">Nome do nó de alternativo</string>
<string name="filter_icon">Filtrar</string>
</resources> </resources>

View file

@ -376,12 +376,9 @@
<string name="userinfo">Info utilizator</string> <string name="userinfo">Info utilizator</string>
<string name="meshtastic_new_nodes_notifications">Notificări noduri noi</string> <string name="meshtastic_new_nodes_notifications">Notificări noduri noi</string>
<string name="snr">SNR</string> <string name="snr">SNR</string>
<string name="snr_definition">Raportul semnal-zgomot (Signal-to-Noise Ratio), o măsură utilizată în comunicații pentru a cuantifica nivelul unui semnal dorit în raport cu nivelul zgomotului de fond. În Meshtastic și în alte sisteme wireless, un SNR mai mare indică un semnal mai clar, care poate îmbunătăți fiabilitatea și calitatea transmiterii datelor.</string>
<string name="rssi"> RSSI</string> <string name="rssi"> RSSI</string>
<string name="rssi_definition">Indicatorul intensității semnalului recepționat (Received Signal Strength Indicator), o măsurătoare utilizată pentru a determina nivelul de putere recepționat de antenă. O valoare RSSI mai mare indică, în general, o conexiune mai puternică și mai stabilă.</string>
<string name="iaq_definition">(Calitatea aerului interior) valoarea IAQ pe o scară relativă, măsurată cu Bosch BME680. Intervalul valorilor: 0500.</string> <string name="iaq_definition">(Calitatea aerului interior) valoarea IAQ pe o scară relativă, măsurată cu Bosch BME680. Intervalul valorilor: 0500.</string>
<string name="device_metrics_log">Valori dispozitiv</string> <string name="device_metrics_log">Valori dispozitiv</string>
<string name="node_map">Harta nodurilor</string>
<string name="position_log">Poziție</string> <string name="position_log">Poziție</string>
<string name="last_position_update">Ultima actualizare a poziției</string> <string name="last_position_update">Ultima actualizare a poziției</string>
<string name="env_metrics_log">Indicatori de mediu</string> <string name="env_metrics_log">Indicatori de mediu</string>
@ -424,7 +421,6 @@
<string name="one_week">1W</string> <string name="one_week">1W</string>
<string name="two_weeks">2W</string> <string name="two_weeks">2W</string>
<string name="max">Maxim</string> <string name="max">Maxim</string>
<string name="avg">Medie</string>
<string name="expand_chart">Extindeți graficul</string> <string name="expand_chart">Extindeți graficul</string>
<string name="collapse_chart">Restrânge graficul</string> <string name="collapse_chart">Restrânge graficul</string>
<string name="unknown_age">Vârstă necunoscută</string> <string name="unknown_age">Vârstă necunoscută</string>
@ -1171,4 +1167,5 @@
<string name="wifi_provision_status_applied">WiFi configurat cu succes!</string> <string name="wifi_provision_status_applied">WiFi configurat cu succes!</string>
<string name="wifi_provision_status_failed">Nu s-a reușit aplicarea configurației Wi-Fi</string> <string name="wifi_provision_status_failed">Nu s-a reușit aplicarea configurației Wi-Fi</string>
<string name="desktop_notification_title">Meshtastic</string> <string name="desktop_notification_title">Meshtastic</string>
<string name="filter_icon">Filtru</string>
</resources> </resources>

View file

@ -389,12 +389,9 @@
<string name="userinfo">Пользовательская информация</string> <string name="userinfo">Пользовательская информация</string>
<string name="meshtastic_new_nodes_notifications">Уведомления о новых нодах</string> <string name="meshtastic_new_nodes_notifications">Уведомления о новых нодах</string>
<string name="snr">Сигнал/шум</string> <string name="snr">Сигнал/шум</string>
<string name="snr_definition">Соотношение сигнал/шум, мера, используемая в коммуникациях для количественной оценки уровня желаемого сигнала по отношению к уровню фонового шума. В Meshtastic и других беспроводных системах более высокий SNR указывает на более четкий сигнал, который может повысить надежность и качество передачи данных.</string>
<string name="rssi">RSSI</string> <string name="rssi">RSSI</string>
<string name="rssi_definition">Индикатор уровня принимаемого сигнала, измерение, используемое для определения уровня мощности, принимаемой антенной. Более высокое значение RSSI обычно указывает на более сильное и стабильное соединение.</string>
<string name="iaq_definition">(Качество воздуха в помещении) Относительная шкала IAQ, измеренная Bosch BME680. Диапазон значений 0500.</string> <string name="iaq_definition">(Качество воздуха в помещении) Относительная шкала IAQ, измеренная Bosch BME680. Диапазон значений 0500.</string>
<string name="device_metrics_log">Интервал передачи</string> <string name="device_metrics_log">Интервал передачи</string>
<string name="node_map">Карта нод</string>
<string name="position_log">Местоположение</string> <string name="position_log">Местоположение</string>
<string name="last_position_update">Обновление последнего местоположения</string> <string name="last_position_update">Обновление последнего местоположения</string>
<string name="env_metrics_log">Метрики окружения</string> <string name="env_metrics_log">Метрики окружения</string>
@ -443,7 +440,6 @@
<string name="one_month">1М</string> <string name="one_month">1М</string>
<string name="max">Макс</string> <string name="max">Макс</string>
<string name="min">Мин</string> <string name="min">Мин</string>
<string name="avg">Сред</string>
<string name="expand_chart">Развернуть диаграмму</string> <string name="expand_chart">Развернуть диаграмму</string>
<string name="collapse_chart">Свернуть диаграмму</string> <string name="collapse_chart">Свернуть диаграмму</string>
<string name="unknown_age">Неизвестный возраст</string> <string name="unknown_age">Неизвестный возраст</string>
@ -621,8 +617,23 @@
<string name="ignore_mqtt">Игнорировать MQTT</string> <string name="ignore_mqtt">Игнорировать MQTT</string>
<string name="ok_to_mqtt">ОК в MQTT</string> <string name="ok_to_mqtt">ОК в MQTT</string>
<string name="mqtt_config">Настройка MQTT</string> <string name="mqtt_config">Настройка MQTT</string>
<string name="mqtt_status_inactive">Неактивно</string>
<string name="mqtt_status_disconnected">Отключено</string> <string name="mqtt_status_disconnected">Отключено</string>
<string name="mqtt_status_disconnected_with_reason">Отключено — %1$s</string>
<string name="mqtt_status_connecting">Подключение...</string>
<string name="mqtt_status_connected">Подключено</string> <string name="mqtt_status_connected">Подключено</string>
<string name="mqtt_status_reconnecting">Переподключение...</string>
<string name="mqtt_status_reconnecting_with_attempt">Переподключение (попытка %1$d) — %2$s</string>
<string name="mqtt_test_connection">Проверить соединение</string>
<string name="mqtt_probe_running">Проверяем брокер…</string>
<string name="mqtt_probe_success">Доступно. Брокер принял учетные данные.</string>
<string name="mqtt_probe_success_with_info">Доступно (%1$s)</string>
<string name="mqtt_probe_rejected">Брокер отклонен: %1$s</string>
<string name="mqtt_probe_dns_failure">Узел не найден</string>
<string name="mqtt_probe_tcp_failure">Не удается подключиться к брокеру (TCP)</string>
<string name="mqtt_probe_tls_failure">Сбой TLS-рукопожатия</string>
<string name="mqtt_probe_timeout">Тайм-аут после %1$d мс</string>
<string name="mqtt_probe_other_failure">Соединение не удалось</string>
<string name="mqtt_enabled">MQTT включен</string> <string name="mqtt_enabled">MQTT включен</string>
<string name="address">Адрес</string> <string name="address">Адрес</string>
<string name="username">Имя пользователя</string> <string name="username">Имя пользователя</string>
@ -1226,5 +1237,21 @@
<string name="wifi_provision_ssid_placeholder">Введите или выберите сеть</string> <string name="wifi_provision_ssid_placeholder">Введите или выберите сеть</string>
<string name="wifi_provision_status_applied">Wi-Fi успешно настроен!</string> <string name="wifi_provision_status_applied">Wi-Fi успешно настроен!</string>
<string name="wifi_provision_status_failed">Не удалось применить настройку Wi-Fi</string> <string name="wifi_provision_status_failed">Не удалось применить настройку Wi-Fi</string>
<string name="desktop_tray_tooltip">Meshtastic Desktop</string>
<string name="desktop_tray_show">Показать Meshtastic</string>
<string name="desktop_tray_quit">Выход</string>
<string name="desktop_notification_title">Meshtastic</string> <string name="desktop_notification_title">Meshtastic</string>
<string name="export_tak_data_package">Экспорт пакета данных TAK</string>
<string name="clear_time_zone">Очистить часовой пояс</string>
<string name="filter_icon">Фильтр</string>
<string name="remove_filter">Удалить фильтр</string>
<string name="show_iaq_legend">Показать легенду качества воздуха</string>
<string name="action_show_message_status">Показать статус сообщения</string>
<string name="action_send_reply">Отправить ответ</string>
<string name="action_copy_message">Скопировать сообщение</string>
<string name="action_select_message">Выбрать сообщение</string>
<string name="action_delete_message">Удалить сообщение</string>
<string name="action_react_with_emoji">Отреагировать эмодзи</string>
<string name="action_select_device">Выберите устройство</string>
<string name="action_select_network">Выбрать сеть</string>
</resources> </resources>

View file

@ -257,11 +257,8 @@
<string name="encryption_error">Nezhoda verejného kľúča</string> <string name="encryption_error">Nezhoda verejného kľúča</string>
<string name="meshtastic_new_nodes_notifications">Notifikácie nových uzlov</string> <string name="meshtastic_new_nodes_notifications">Notifikácie nových uzlov</string>
<string name="snr">SNR</string> <string name="snr">SNR</string>
<string name="snr_definition">Pomer signálu od šumu (SNR), miera používaná v komunikácii na kvantifikáciu úrovne požadovaného signálu k úrovni hluku pozadia. V Meshtastic a iných bezdrôtových systémoch znamená vyšší SNR jasnejší signál, ktorý môže zvýšiť spoľahlivosť a kvalitu prenosu údajov.</string>
<string name="rssi">RSSI</string> <string name="rssi">RSSI</string>
<string name="rssi_definition">Indikátor sily prijímaného signálu (RSSI), meranie používané na určenie úrovne výkonu prijatého skrz anténu. Vyššia hodnota RSSI vo všeobecnosti znamená silnejšie a stabilnejšie pripojenie.</string>
<string name="iaq_definition">(Kvalita vzduchu v interiéri) relatívna hodnota IAQ meraná prístrojom Bosch BME680. Rozsah hodnôt 0500.</string> <string name="iaq_definition">(Kvalita vzduchu v interiéri) relatívna hodnota IAQ meraná prístrojom Bosch BME680. Rozsah hodnôt 0500.</string>
<string name="node_map">Mapa uzlov</string>
<string name="position_log">Pozícia</string> <string name="position_log">Pozícia</string>
<string name="administration">Administrácia</string> <string name="administration">Administrácia</string>
<string name="remote_admin">Administrácia na diaľku</string> <string name="remote_admin">Administrácia na diaľku</string>
@ -430,4 +427,5 @@
<string name="tak_team_blue">Modrá</string> <string name="tak_team_blue">Modrá</string>
<string name="tak_team_green">Zelená</string> <string name="tak_team_green">Zelená</string>
<string name="desktop_notification_title">Meshtastic</string> <string name="desktop_notification_title">Meshtastic</string>
<string name="filter_icon">Filter</string>
</resources> </resources>

View file

@ -196,11 +196,8 @@
<string name="encryption_error">Neujemanje javnega ključa</string> <string name="encryption_error">Neujemanje javnega ključa</string>
<string name="meshtastic_new_nodes_notifications">Obvestila novih vozlišč</string> <string name="meshtastic_new_nodes_notifications">Obvestila novih vozlišč</string>
<string name="snr">SNR</string> <string name="snr">SNR</string>
<string name="snr_definition">Razmerje med signalom in šumom je merilo, ki se uporablja v komunikacijah za količinsko opredelitev ravni želenega signala glede na raven hrupa v ozadju. V Meshtastic in drugih brezžičnih sistemih višji SNR pomeni jasnejši signal, ki lahko poveča zanesljivost in kakovost prenosa podatkov.</string>
<string name="rssi">RSSI</string> <string name="rssi">RSSI</string>
<string name="rssi_definition">Indikator moči sprejetega signala je meritev, ki se uporablja za določanje ravni moči, ki jo sprejema antena. Višja vrednost RSSI na splošno pomeni močnejšo in stabilnejšo povezavo.</string>
<string name="iaq_definition">(Kakovost zraka v zaprtih prostorih) relativna vrednost IAQ na lestvici, izmerjena z Bosch BME680. Razpon vrednosti 0500.</string> <string name="iaq_definition">(Kakovost zraka v zaprtih prostorih) relativna vrednost IAQ na lestvici, izmerjena z Bosch BME680. Razpon vrednosti 0500.</string>
<string name="node_map">Zemljevid vozlišč</string>
<string name="administration">Administracija</string> <string name="administration">Administracija</string>
<string name="remote_admin">Administracija na daljavo</string> <string name="remote_admin">Administracija na daljavo</string>
<string name="bad">Slab</string> <string name="bad">Slab</string>
@ -245,4 +242,5 @@
<!-- Compass --> <!-- Compass -->
<!-- Message Filter --> <!-- Message Filter -->
<!-- Network Map Layers --> <!-- Network Map Layers -->
<string name="filter_icon">Filter</string>
</resources> </resources>

View file

@ -186,10 +186,7 @@
<string name="encryption_pkc">Kriptimi me Çelës Publik</string> <string name="encryption_pkc">Kriptimi me Çelës Publik</string>
<string name="encryption_error">Përputhje e Gabuar e Çelësit Publik</string> <string name="encryption_error">Përputhje e Gabuar e Çelësit Publik</string>
<string name="meshtastic_new_nodes_notifications">Njoftimet për nyje të reja</string> <string name="meshtastic_new_nodes_notifications">Njoftimet për nyje të reja</string>
<string name="snr_definition">Raporti i Sinjalit në Zhurmë, një masë e përdorur në komunikime për të kuantifikuar nivelin e një sinjali të dëshiruar ndaj nivelit të zhurmës në background. Në Meshtastic dhe sisteme të tjera pa tel, një SNR më i lartë tregon një sinjal më të pastër që mund të rrisë besueshmërinë dhe cilësinë e transmetimit të të dhënave.</string>
<string name="rssi_definition">Indikatori i Fuqisë së Sinjalit të Marrë, një matje e përdorur për të përcaktuar nivelin e energjisë që po merret nga antena. Një vlerë më e lartë RSSI zakonisht tregon një lidhje më të fortë dhe më të qëndrueshme.</string>
<string name="iaq_definition">(Cilësia e Ajrit të Brendshëm) shkalla relative e vlerës IAQ siç matet nga Bosch BME680. Intervali i Vlerave 0500.</string> <string name="iaq_definition">(Cilësia e Ajrit të Brendshëm) shkalla relative e vlerës IAQ siç matet nga Bosch BME680. Intervali i Vlerave 0500.</string>
<string name="node_map">Harta e Nyjës</string>
<string name="administration">Administratë</string> <string name="administration">Administratë</string>
<string name="remote_admin">Administratë e Largët</string> <string name="remote_admin">Administratë e Largët</string>
<string name="bad">I Keq</string> <string name="bad">I Keq</string>
@ -219,4 +216,5 @@
<!-- Compass --> <!-- Compass -->
<!-- Message Filter --> <!-- Message Filter -->
<!-- Network Map Layers --> <!-- Network Map Layers -->
<string name="filter_icon">Filtrimi</string>
</resources> </resources>

View file

@ -241,12 +241,9 @@
<string name="encryption_error">Неусаглашеност јавних кључева</string> <string name="encryption_error">Неусаглашеност јавних кључева</string>
<string name="meshtastic_new_nodes_notifications">Обавештење о новом чвору</string> <string name="meshtastic_new_nodes_notifications">Обавештење о новом чвору</string>
<string name="snr">SNR</string> <string name="snr">SNR</string>
<string name="snr_definition">Однос сигнал/шум SNR је мера која се користи у комуникацијама за квантитативно одређивање нивоа жељеног сигнала у односу на ниво позадинског шума. У Мештастик и другим бежичним системима, већи SNR указује на јаснији сигнал који може побољшати поузданост и квалитет преноса података.</string>
<string name="rssi">RSSI</string> <string name="rssi">RSSI</string>
<string name="rssi_definition">Indikator jačine primljenog signala RSSI, merenje koje se koristi za određivanje nivoa snage koji antena prima. Viša vrednost RSSI generalno ukazuje na jaču i stabilniju vezu.</string>
<string name="iaq_definition">(Kvalitet vazduha u zatvorenom prostoru) relativna skala vrednosti IAQ merena Bosch BME680. Raspon vrednosti 0500.</string> <string name="iaq_definition">(Kvalitet vazduha u zatvorenom prostoru) relativna skala vrednosti IAQ merena Bosch BME680. Raspon vrednosti 0500.</string>
<string name="device_metrics_log">Метрика уређаја</string> <string name="device_metrics_log">Метрика уређаја</string>
<string name="node_map">Mapa čvorova</string>
<string name="position_log">Позиција</string> <string name="position_log">Позиција</string>
<string name="env_metrics_log">Метрике сензора</string> <string name="env_metrics_log">Метрике сензора</string>
<string name="administration">Administracija</string> <string name="administration">Administracija</string>
@ -432,4 +429,5 @@
<string name="bluetooth_permission">Блутут</string> <string name="bluetooth_permission">Блутут</string>
<string name="powered">Напајано</string> <string name="powered">Напајано</string>
<!-- Network Map Layers --> <!-- Network Map Layers -->
<string name="filter_icon">Filter</string>
</resources> </resources>

View file

@ -241,12 +241,9 @@
<string name="encryption_error">Неусаглашеност јавних кључева</string> <string name="encryption_error">Неусаглашеност јавних кључева</string>
<string name="meshtastic_new_nodes_notifications">Обавештења о новим чворовима</string> <string name="meshtastic_new_nodes_notifications">Обавештења о новим чворовима</string>
<string name="snr">SNR</string> <string name="snr">SNR</string>
<string name="snr_definition">Однос сигнал/шум SNR је мера која се користи у комуникацијама за квантитативно одређивање нивоа жељеног сигнала у односу на ниво позадинског шума. У Мештастик и другим бежичним системима, већи SNR указује на јаснији сигнал који може побољшати поузданост и квалитет преноса података.</string>
<string name="rssi">RSSI</string> <string name="rssi">RSSI</string>
<string name="rssi_definition">Индикатор јачине примљеног сигнала RSSI је мера која се користи за одређивање нивоа снаге која се прима преко антене. Виша вредност RSSI генерално указује на јачу и стабилнију везу.</string>
<string name="iaq_definition">Индекс квалитета ваздуха (IAQ) као мера за одређивање квалитета ваздуха унутрашњости, мерен са Bosch BME680. Вредности се крећу у распону од 0 до 500.</string> <string name="iaq_definition">Индекс квалитета ваздуха (IAQ) као мера за одређивање квалитета ваздуха унутрашњости, мерен са Bosch BME680. Вредности се крећу у распону од 0 до 500.</string>
<string name="device_metrics_log">Метрика уређаја</string> <string name="device_metrics_log">Метрика уређаја</string>
<string name="node_map">Мапа чворова</string>
<string name="position_log">Позиција</string> <string name="position_log">Позиција</string>
<string name="env_metrics_log">Метрике сензора</string> <string name="env_metrics_log">Метрике сензора</string>
<string name="administration">Администрација</string> <string name="administration">Администрација</string>
@ -432,4 +429,5 @@
<string name="bluetooth_permission">Блутут</string> <string name="bluetooth_permission">Блутут</string>
<string name="powered">Напајано</string> <string name="powered">Напајано</string>
<!-- Network Map Layers --> <!-- Network Map Layers -->
<string name="filter_icon">Филтер</string>
</resources> </resources>

View file

@ -43,6 +43,7 @@
<string name="unrecognized">Okänd</string> <string name="unrecognized">Okänd</string>
<string name="message_status_enroute">Inväntar kvittens</string> <string name="message_status_enroute">Inväntar kvittens</string>
<string name="message_status_queued">Kvittens köad</string> <string name="message_status_queued">Kvittens köad</string>
<string name="message_status_delivered">Levererad till nät</string>
<string name="message_status_unknown">Okänd</string> <string name="message_status_unknown">Okänd</string>
<string name="routing_error_none">Kvitterad</string> <string name="routing_error_none">Kvitterad</string>
<string name="routing_error_no_route">Ingen rutt</string> <string name="routing_error_no_route">Ingen rutt</string>
@ -339,12 +340,9 @@
<string name="userinfo">Användarinfo</string> <string name="userinfo">Användarinfo</string>
<string name="meshtastic_new_nodes_notifications">Ny nod avisering</string> <string name="meshtastic_new_nodes_notifications">Ny nod avisering</string>
<string name="snr">SNR</string> <string name="snr">SNR</string>
<string name="snr_definition">Signal-to-Noise Ratio, är ett mått som används inom kommunikation för att kvantifiera nivån av en önskad signal mot nivån av bakgrundsbrus. I Meshtastic och andra trådlösa system indikerar en högre SNR en tydligare signal som kan förbättra tillförlitligheten och kvaliteten på dataöverföringen.</string>
<string name="rssi">RSSI</string> <string name="rssi">RSSI</string>
<string name="rssi_definition">Received Signal Strength Indicator, ett mått som används för att avgöra effektnivån som togs emot av antennen. Ett högre RSSI-värde indikerar generellt en starkare och stabilare anslutning.</string>
<string name="iaq_definition">(Indoor Air Quality) relativ skala IAQ värdet mätt med Bosch BME600. Värdeintervall 0-500.</string> <string name="iaq_definition">(Indoor Air Quality) relativ skala IAQ värdet mätt med Bosch BME600. Värdeintervall 0-500.</string>
<string name="device_metrics_log">Enhetens mätvärden</string> <string name="device_metrics_log">Enhetens mätvärden</string>
<string name="node_map">Nod karta</string>
<string name="position_log">Plats</string> <string name="position_log">Plats</string>
<string name="last_position_update">Senaste positionsuppdatering</string> <string name="last_position_update">Senaste positionsuppdatering</string>
<string name="env_metrics_log">Miljövärden</string> <string name="env_metrics_log">Miljövärden</string>
@ -373,6 +371,7 @@
<string name="traceroute_duration">Varaktighet: %1$s s</string> <string name="traceroute_duration">Varaktighet: %1$s s</string>
<string name="traceroute_route_towards_dest">Rutt spårad mot destination:\n\n</string> <string name="traceroute_route_towards_dest">Rutt spårad mot destination:\n\n</string>
<string name="traceroute_route_back_to_us">Rutten spårad tillbaka till oss:\n\n</string> <string name="traceroute_route_back_to_us">Rutten spårad tillbaka till oss:\n\n</string>
<string name="traceroute_no_response">Inget svar</string>
<string name="one_hour_short">1h</string> <string name="one_hour_short">1h</string>
<string name="twenty_four_hours">24T</string> <string name="twenty_four_hours">24T</string>
<string name="one_week">1V</string> <string name="one_week">1V</string>
@ -531,6 +530,8 @@
<string name="mqtt_config">MQTT-konfiguration</string> <string name="mqtt_config">MQTT-konfiguration</string>
<string name="mqtt_status_disconnected">Frånkopplad</string> <string name="mqtt_status_disconnected">Frånkopplad</string>
<string name="mqtt_status_connected">Ansluten</string> <string name="mqtt_status_connected">Ansluten</string>
<string name="mqtt_test_connection">Testa anslutningen</string>
<string name="mqtt_probe_other_failure">Anslutningen misslyckades</string>
<string name="mqtt_enabled">MQTT är aktiverat</string> <string name="mqtt_enabled">MQTT är aktiverat</string>
<string name="address">Adress</string> <string name="address">Adress</string>
<string name="username">Användarnamn</string> <string name="username">Användarnamn</string>
@ -948,4 +949,6 @@
<string name="connect">Anslut</string> <string name="connect">Anslut</string>
<string name="done">Klart</string> <string name="done">Klart</string>
<string name="desktop_notification_title">Meshtastic</string> <string name="desktop_notification_title">Meshtastic</string>
<string name="filter_icon">Filter</string>
<string name="action_select_device">Välj enhet</string>
</resources> </resources>

View file

@ -213,11 +213,8 @@
<string name="encryption_error">Genel Anahtar Uyuşmazlığı</string> <string name="encryption_error">Genel Anahtar Uyuşmazlığı</string>
<string name="meshtastic_new_nodes_notifications">Yeni düğüm bildirimleri</string> <string name="meshtastic_new_nodes_notifications">Yeni düğüm bildirimleri</string>
<string name="snr">SNR</string> <string name="snr">SNR</string>
<string name="snr_definition">Sinyal-Gürültü Oranı, iletişimde istenen bir sinyalin seviyesini arka plan gürültüsü seviyesine mukayese ölçmek için kullanılan bir ölçüdür. Meshtastic ve diğer kablosuz sistemlerde, daha yüksek bir SNR, veri iletiminin güvenilirliğini ve kalitesini artırabilecek daha net bir sinyale işaret eder.</string>
<string name="rssi">RSSI</string> <string name="rssi">RSSI</string>
<string name="rssi_definition">Alınan Sinyal Gücü Göstergesi, anten tarafından alınan güç seviyesini belirlemek için kullanılan bir ölçüdür. Daha yüksek bir RSSI değeri genellikle daha güçlü ve daha istikrarlı bir bağlantıya işaret eder.</string>
<string name="iaq_definition">(İç Hava Kalitesi) Bosch BME680 tarafından ölçülen bağıl ölçekli IAQ değeri. Değer Aralığı 0500.</string> <string name="iaq_definition">(İç Hava Kalitesi) Bosch BME680 tarafından ölçülen bağıl ölçekli IAQ değeri. Değer Aralığı 0500.</string>
<string name="node_map">Düğüm Haritası</string>
<string name="position_log">Konum</string> <string name="position_log">Konum</string>
<string name="administration">Yönetim</string> <string name="administration">Yönetim</string>
<string name="remote_admin">Uzaktan Yönetim</string> <string name="remote_admin">Uzaktan Yönetim</string>
@ -549,4 +546,5 @@
<string name="tak_team_green">Yeşil</string> <string name="tak_team_green">Yeşil</string>
<string name="connect">Bağlan</string> <string name="connect">Bağlan</string>
<string name="desktop_notification_title">Meshtastic</string> <string name="desktop_notification_title">Meshtastic</string>
<string name="filter_icon">Filtre</string>
</resources> </resources>

View file

@ -277,9 +277,7 @@
<string name="meshtastic_new_nodes_notifications">Сповіщення про нові вузли</string> <string name="meshtastic_new_nodes_notifications">Сповіщення про нові вузли</string>
<string name="snr">SNR</string> <string name="snr">SNR</string>
<string name="rssi">RSSI</string> <string name="rssi">RSSI</string>
<string name="rssi_definition">Показник рівня потужності сигналу — вимірювання, що використовується для визначення рівня потужності, що приймається антеною. Вище значення RSSI зазвичай вказує на міцніше та стабільніше з'єднання.</string>
<string name="device_metrics_log">Показники пристрою</string> <string name="device_metrics_log">Показники пристрою</string>
<string name="node_map">Мапа вузлів</string>
<string name="position_log">Місцезнаходження</string> <string name="position_log">Місцезнаходження</string>
<string name="env_metrics_log">Показники довкілля</string> <string name="env_metrics_log">Показники довкілля</string>
<string name="administration">Адміністрування</string> <string name="administration">Адміністрування</string>
@ -403,6 +401,7 @@
<string name="mqtt_config">Налаштування MQTT</string> <string name="mqtt_config">Налаштування MQTT</string>
<string name="mqtt_status_disconnected">Відключено</string> <string name="mqtt_status_disconnected">Відключено</string>
<string name="mqtt_status_connected">Під’єднано</string> <string name="mqtt_status_connected">Під’єднано</string>
<string name="mqtt_test_connection">Перевірка зʼєднання</string>
<string name="mqtt_enabled">MQTT увімкнений</string> <string name="mqtt_enabled">MQTT увімкнений</string>
<string name="address">Адреса</string> <string name="address">Адреса</string>
<string name="username">Ім'я користувача</string> <string name="username">Ім'я користувача</string>
@ -724,4 +723,6 @@
<string name="connect">Під’єднатися</string> <string name="connect">Під’єднатися</string>
<string name="done">Готово</string> <string name="done">Готово</string>
<string name="desktop_notification_title">Meshtastic</string> <string name="desktop_notification_title">Meshtastic</string>
<string name="filter_icon">Фільтри</string>
<string name="action_select_device">Оберіть пристрій</string>
</resources> </resources>

View file

@ -362,12 +362,9 @@
<string name="userinfo">用户信息</string> <string name="userinfo">用户信息</string>
<string name="meshtastic_new_nodes_notifications">新节点通知</string> <string name="meshtastic_new_nodes_notifications">新节点通知</string>
<string name="snr">SNR</string> <string name="snr">SNR</string>
<string name="snr_definition">信噪比Signal-to-Noise Ratio, SNR是一种用于通信领域的测量指标用于量化目标信号与背景噪声的比例。在 Meshtastic 及其他无线系统中,较高的信噪比表示信号更加清晰,从而能够提升数据传输的可靠性和质量。</string>
<string name="rssi">RSSI</string> <string name="rssi">RSSI</string>
<string name="rssi_definition">接收信号强度指示Received Signal Strength Indicator, RSSI是一种用于测量天线接收到的信号功率的指标。较高的 RSSI 值通常表示更强、更稳定的连接。</string>
<string name="iaq_definition">室内空气质量Indoor Air Quality, IAQ由 Bosch BME680 传感器测量的相对标尺 IAQ 值,取值范围为 0500。</string> <string name="iaq_definition">室内空气质量Indoor Air Quality, IAQ由 Bosch BME680 传感器测量的相对标尺 IAQ 值,取值范围为 0500。</string>
<string name="device_metrics_log">设备指标</string> <string name="device_metrics_log">设备指标</string>
<string name="node_map">节点地图</string>
<string name="position_log">定位</string> <string name="position_log">定位</string>
<string name="last_position_update">最后位置更新</string> <string name="last_position_update">最后位置更新</string>
<string name="env_metrics_log">传感器指标</string> <string name="env_metrics_log">传感器指标</string>
@ -570,6 +567,7 @@
<string name="mqtt_config">MQTT设置</string> <string name="mqtt_config">MQTT设置</string>
<string name="mqtt_status_disconnected">已断开连接</string> <string name="mqtt_status_disconnected">已断开连接</string>
<string name="mqtt_status_connected">已连接</string> <string name="mqtt_status_connected">已连接</string>
<string name="mqtt_test_connection">连接测试</string>
<string name="mqtt_enabled">启用MQTT</string> <string name="mqtt_enabled">启用MQTT</string>
<string name="address">地址</string> <string name="address">地址</string>
<string name="username">用户名</string> <string name="username">用户名</string>
@ -1117,4 +1115,6 @@
<string name="connect">连接</string> <string name="connect">连接</string>
<string name="done">完成</string> <string name="done">完成</string>
<string name="desktop_notification_title">Meshtastic</string> <string name="desktop_notification_title">Meshtastic</string>
<string name="filter_icon">搜索节点</string>
<string name="action_select_device">选择设备</string>
</resources> </resources>

View file

@ -222,6 +222,11 @@
<string name="a11y_label_value">%1$s: %2$s</string> <string name="a11y_label_value">%1$s: %2$s</string>
<string name="a11y_message_from">來自 %1$s 的訊息:%2$s</string> <string name="a11y_message_from">來自 %1$s 的訊息:%2$s</string>
<string name="preview_header">標頭</string> <string name="preview_header">標頭</string>
<string name="preview_footer">標尾</string>
<string name="preview_dot">點形</string>
<string name="preview_text">文字</string>
<string name="preview_gauge">儀表板</string>
<string name="preview_gradient">梯度</string>
<string name="preview_custom_composable_line_one">這是一個一個一個可客製化的組合元件</string> <string name="preview_custom_composable_line_one">這是一個一個一個可客製化的組合元件</string>
<string name="preview_custom_composable_line_two">還支援多行文字與多種樣式</string> <string name="preview_custom_composable_line_two">還支援多行文字與多種樣式</string>
<string name="message_delivery_status">訊息傳遞狀態</string> <string name="message_delivery_status">訊息傳遞狀態</string>
@ -373,12 +378,9 @@
<string name="userinfo">使用者資訊</string> <string name="userinfo">使用者資訊</string>
<string name="meshtastic_new_nodes_notifications">新節點通知</string> <string name="meshtastic_new_nodes_notifications">新節點通知</string>
<string name="snr">SNR</string> <string name="snr">SNR</string>
<string name="snr_definition">信噪比SNR用於通訊中量化所需信號與背景噪音水平的指標。在 Meshtastic 及其他無線系統中,信噪比越高表示信號越清晰,可以提高數據傳輸的可靠性和品質。</string>
<string name="rssi">RSSI</string> <string name="rssi">RSSI</string>
<string name="rssi_definition">接收信號強度指示RSSI用於測量天線所接收到信號的功率強度。 RSSI 值越高通常代表連線越強且穩定。</string>
<string name="iaq_definition">(室內空氣品質) 相對尺度 IAQ 值,由 Bosch BME680 測量。值範圍 0500。</string> <string name="iaq_definition">(室內空氣品質) 相對尺度 IAQ 值,由 Bosch BME680 測量。值範圍 0500。</string>
<string name="device_metrics_log">裝置計量資料</string> <string name="device_metrics_log">裝置計量資料</string>
<string name="node_map">節點地圖</string>
<string name="position_log">位置</string> <string name="position_log">位置</string>
<string name="last_position_update">最後位置更新</string> <string name="last_position_update">最後位置更新</string>
<string name="env_metrics_log">環境計量資料</string> <string name="env_metrics_log">環境計量資料</string>
@ -410,6 +412,12 @@
<string name="traceroute_return_hops">回程跳數</string> <string name="traceroute_return_hops">回程跳數</string>
<string name="traceroute_round_trip">來回跳數</string> <string name="traceroute_round_trip">來回跳數</string>
<string name="traceroute_no_response">無回應</string> <string name="traceroute_no_response">無回應</string>
<string name="load_1_min">1分鐘負載</string>
<string name="load_5_min">5分鐘負載</string>
<string name="load_15_min">15分鐘負載</string>
<string name="load_1_min_description">1分鐘系統負載平均值</string>
<string name="load_5_min_description">5分鐘系統負載平均值</string>
<string name="load_15_min_description">15分鐘系統負載平均值</string>
<string name="free_memory_description">可用系統記憶體(位元組)</string> <string name="free_memory_description">可用系統記憶體(位元組)</string>
<string name="one_hour_short">1小時</string> <string name="one_hour_short">1小時</string>
<string name="twenty_four_hours">二十四小時</string> <string name="twenty_four_hours">二十四小時</string>
@ -418,7 +426,6 @@
<string name="one_month">1個月</string> <string name="one_month">1個月</string>
<string name="max">最大值</string> <string name="max">最大值</string>
<string name="min">最小</string> <string name="min">最小</string>
<string name="avg">平均</string>
<string name="expand_chart">展開圖表</string> <string name="expand_chart">展開圖表</string>
<string name="collapse_chart">收起圖表</string> <string name="collapse_chart">收起圖表</string>
<string name="unknown_age">未知年齡</string> <string name="unknown_age">未知年齡</string>
@ -596,8 +603,23 @@
<string name="ignore_mqtt">無視MQTT</string> <string name="ignore_mqtt">無視MQTT</string>
<string name="ok_to_mqtt">允許轉發至 MQTT</string> <string name="ok_to_mqtt">允許轉發至 MQTT</string>
<string name="mqtt_config">MQTT配置</string> <string name="mqtt_config">MQTT配置</string>
<string name="mqtt_status_inactive">已停用</string>
<string name="mqtt_status_disconnected">已中斷連線</string> <string name="mqtt_status_disconnected">已中斷連線</string>
<string name="mqtt_status_disconnected_with_reason">已斷線 — %1$s</string>
<string name="mqtt_status_connecting">正在連接…</string>
<string name="mqtt_status_connected">已連線</string> <string name="mqtt_status_connected">已連線</string>
<string name="mqtt_status_reconnecting">重新連接中…</string>
<string name="mqtt_status_reconnecting_with_attempt">重新連接中(第 %1$d 次嘗試) — %2$s</string>
<string name="mqtt_test_connection">測試連線</string>
<string name="mqtt_probe_running">正在查詢 Broker…</string>
<string name="mqtt_probe_success">可供連線Broker 已驗證並接受憑證。</string>
<string name="mqtt_probe_success_with_info">可供連線(%1$s</string>
<string name="mqtt_probe_rejected">Broker 遭拒:%1$s</string>
<string name="mqtt_probe_dns_failure">找不到伺服器</string>
<string name="mqtt_probe_tcp_failure">無法連線至 Broker 中繼伺服器TCP</string>
<string name="mqtt_probe_tls_failure">TLS 握手失敗</string>
<string name="mqtt_probe_timeout">經過 %1$d 毫秒後逾時</string>
<string name="mqtt_probe_other_failure">測試失敗</string>
<string name="mqtt_enabled">啟用MQTT服務器</string> <string name="mqtt_enabled">啟用MQTT服務器</string>
<string name="address">地址</string> <string name="address">地址</string>
<string name="username">用戶名</string> <string name="username">用戶名</string>
@ -796,6 +818,9 @@
<string name="show_waypoints">顯示路徑</string> <string name="show_waypoints">顯示路徑</string>
<string name="show_precision_circle">顯示定位精準度</string> <string name="show_precision_circle">顯示定位精準度</string>
<string name="client_notification">客户端通知</string> <string name="client_notification">客户端通知</string>
<string name="key_verification_title">金鑰驗證</string>
<string name="key_verification_request_title">金鑰驗證請求</string>
<string name="key_verification_final_title">金鑰驗證已完成</string>
<string name="duplicated_public_key_title">偵測到重複的公鑰</string> <string name="duplicated_public_key_title">偵測到重複的公鑰</string>
<string name="low_entropy_key_title">偵測到加密金鑰強度不足</string> <string name="low_entropy_key_title">偵測到加密金鑰強度不足</string>
<string name="compromised_keys">偵測到金鑰已洩漏,點選確定後重新產生金鑰。</string> <string name="compromised_keys">偵測到金鑰已洩漏,點選確定後重新產生金鑰。</string>
@ -1164,6 +1189,8 @@
<string name="note">注意</string> <string name="note">注意</string>
<string name="device_storage_ui_title">裝置儲存空間與使用者介面(唯讀)</string> <string name="device_storage_ui_title">裝置儲存空間與使用者介面(唯讀)</string>
<string name="device_theme_language">主題 %1$s語言 %2$s</string> <string name="device_theme_language">主題 %1$s語言 %2$s</string>
<string name="files_available">可使用檔案(%1$d</string>
<string name="file_entry">- %1$s%2$d 位元)</string>
<string name="no_files_manifested">未發現任何檔案。</string> <string name="no_files_manifested">未發現任何檔案。</string>
<string name="connect">連線</string> <string name="connect">連線</string>
<string name="done">完成</string> <string name="done">完成</string>
@ -1172,6 +1199,7 @@
<string name="wifi_provision_mpwrd_disclaimer">進一步了解 mPWRD-OS 專案\nhttps://github.com/mPWRD-OS</string> <string name="wifi_provision_mpwrd_disclaimer">進一步了解 mPWRD-OS 專案\nhttps://github.com/mPWRD-OS</string>
<string name="wifi_provision_scanning_ble">正在搜尋裝置…</string> <string name="wifi_provision_scanning_ble">正在搜尋裝置…</string>
<string name="wifi_provision_device_found">找到裝置</string> <string name="wifi_provision_device_found">找到裝置</string>
<string name="wifi_provision_device_found_detail">準備好掃描 Wi-Fi 網路了。</string>
<string name="wifi_provision_scan_networks">搜尋網路</string> <string name="wifi_provision_scan_networks">搜尋網路</string>
<string name="wifi_provision_scanning_wifi">正在搜尋…</string> <string name="wifi_provision_scanning_wifi">正在搜尋…</string>
<string name="wifi_provision_sending_credentials">正在套用 Wi-Fi 設定…</string> <string name="wifi_provision_sending_credentials">正在套用 Wi-Fi 設定…</string>
@ -1184,7 +1212,21 @@
<string name="wifi_provision_ssid_placeholder">手動輸入或選擇一個網路</string> <string name="wifi_provision_ssid_placeholder">手動輸入或選擇一個網路</string>
<string name="wifi_provision_status_applied">Wi-Fi 已設定完成!</string> <string name="wifi_provision_status_applied">Wi-Fi 已設定完成!</string>
<string name="wifi_provision_status_failed">無法套用 Wi-Fi 設定</string> <string name="wifi_provision_status_failed">無法套用 Wi-Fi 設定</string>
<string name="desktop_tray_tooltip">Meshtastic Desktop</string>
<string name="desktop_tray_show">顯示 Meshtastic</string> <string name="desktop_tray_show">顯示 Meshtastic</string>
<string name="desktop_tray_quit">離開</string> <string name="desktop_tray_quit">離開</string>
<string name="desktop_notification_title">Meshtastic</string> <string name="desktop_notification_title">Meshtastic</string>
<string name="export_tak_data_package">匯出 TAK 資料封包</string>
<string name="clear_time_zone">清除時區</string>
<string name="filter_icon">過濾器</string>
<string name="remove_filter">移除篩選條件</string>
<string name="show_iaq_legend">顯示空氣品質圖例</string>
<string name="action_show_message_status">顯示訊息狀態</string>
<string name="action_send_reply">傳送回覆</string>
<string name="action_copy_message">複製訊息</string>
<string name="action_select_message">選擇訊息</string>
<string name="action_delete_message">刪除訊息</string>
<string name="action_react_with_emoji">使用表情符號回應</string>
<string name="action_select_device">選擇裝置</string>
<string name="action_select_network">選擇網路</string>
</resources> </resources>

View file

@ -636,9 +636,21 @@
<string name="mqtt_config">MQTT Config</string> <string name="mqtt_config">MQTT Config</string>
<string name="mqtt_status_inactive">Inactive</string> <string name="mqtt_status_inactive">Inactive</string>
<string name="mqtt_status_disconnected">Disconnected</string> <string name="mqtt_status_disconnected">Disconnected</string>
<string name="mqtt_status_disconnected_with_reason">Disconnected — %1$s</string>
<string name="mqtt_status_connecting">Connecting…</string> <string name="mqtt_status_connecting">Connecting…</string>
<string name="mqtt_status_connected">Connected</string> <string name="mqtt_status_connected">Connected</string>
<string name="mqtt_status_reconnecting">Reconnecting…</string> <string name="mqtt_status_reconnecting">Reconnecting…</string>
<string name="mqtt_status_reconnecting_with_attempt">Reconnecting (attempt %1$d) — %2$s</string>
<string name="mqtt_test_connection">Test connection</string>
<string name="mqtt_probe_running">Probing broker…</string>
<string name="mqtt_probe_success">Reachable. Broker accepted credentials.</string>
<string name="mqtt_probe_success_with_info">Reachable (%1$s)</string>
<string name="mqtt_probe_rejected">Broker rejected: %1$s</string>
<string name="mqtt_probe_dns_failure">Host not found</string>
<string name="mqtt_probe_tcp_failure">Cannot reach broker (TCP)</string>
<string name="mqtt_probe_tls_failure">TLS handshake failed</string>
<string name="mqtt_probe_timeout">Timed out after %1$d ms</string>
<string name="mqtt_probe_other_failure">Connection failed</string>
<string name="mqtt_enabled">MQTT enabled</string> <string name="mqtt_enabled">MQTT enabled</string>
<string name="address">Address</string> <string name="address">Address</string>
<string name="username">Username</string> <string name="username">Username</string>

View file

@ -164,7 +164,7 @@ class NoopMQTTRepository : MQTTRepository {
override fun publish(topic: String, data: ByteArray, retained: Boolean) {} override fun publish(topic: String, data: ByteArray, retained: Boolean) {}
override val connectionState = MutableStateFlow(MqttConnectionState.DISCONNECTED) override val connectionState = MutableStateFlow<MqttConnectionState>(MqttConnectionState.Disconnected.Idle)
} }
// endregion // endregion

View file

@ -1 +1 @@
For detailed release notes, please visit: https://github.com/meshtastic/Meshtastic-Android/releases/ Pour des notes de version détaillées, veuillez visiter : https://github.com/meshtastic/Meshtastic-Android/releases/

View file

@ -60,7 +60,14 @@ class AndroidGetDiscoveredDevicesUseCase(
override fun invoke(showMock: Boolean): Flow<DiscoveredDevices> { override fun invoke(showMock: Boolean): Flow<DiscoveredDevices> {
val nodeDb = nodeRepository.nodeDBbyNum val nodeDb = nodeRepository.nodeDBbyNum
val bondedBleFlow = bluetoothRepository.state.map { ble -> ble.bondedDevices.map { DeviceListEntry.Ble(it) } } // Filter out non-Meshtastic peripherals (headphones, cars, watches, etc.).
// BluetoothAdapter.bondedDevices returns every bonded device on the phone, so we
// must restrict the picker to entries whose advertised name matches the
// Meshtastic firmware pattern (see MeshtasticBleConstants.BLE_NAME_PATTERN).
val bondedBleFlow =
bluetoothRepository.state.map { ble ->
ble.bondedDevices.filter { it.getMeshtasticShortName() != null }.map { DeviceListEntry.Ble(it) }
}
val processedTcpFlow = val processedTcpFlow =
combine(networkRepository.resolvedList, recentAddressesDataSource.recentAddresses) { combine(networkRepository.resolvedList, recentAddressesDataSource.recentAddresses) {

View file

@ -43,10 +43,7 @@ internal fun handleNodeAction(
val route = viewModel.getDirectMessageRoute(menuAction.node, uiState.ourNode) val route = viewModel.getDirectMessageRoute(menuAction.node, uiState.ourNode)
navigateToMessages(route) navigateToMessages(route)
} }
is NodeMenuAction.Remove -> { is NodeMenuAction.Remove -> viewModel.handleNodeMenuAction(menuAction, onNavigateUp)
viewModel.handleNodeMenuAction(menuAction)
onNavigateUp()
}
else -> viewModel.handleNodeMenuAction(menuAction) else -> viewModel.handleNodeMenuAction(menuAction)
} }
} }

View file

@ -89,9 +89,10 @@ class NodeDetailViewModel(
} }
/** Dispatches high-level node management actions like removal, muting, or favoriting. */ /** Dispatches high-level node management actions like removal, muting, or favoriting. */
fun handleNodeMenuAction(action: NodeMenuAction) { fun handleNodeMenuAction(action: NodeMenuAction, onAfterRemove: () -> Unit = {}) {
when (action) { when (action) {
is NodeMenuAction.Remove -> nodeManagementActions.requestRemoveNode(viewModelScope, action.node) is NodeMenuAction.Remove ->
nodeManagementActions.requestRemoveNode(viewModelScope, action.node, onAfterRemove)
is NodeMenuAction.Ignore -> nodeManagementActions.requestIgnoreNode(viewModelScope, action.node) is NodeMenuAction.Ignore -> nodeManagementActions.requestIgnoreNode(viewModelScope, action.node)
is NodeMenuAction.Mute -> nodeManagementActions.requestMuteNode(viewModelScope, action.node) is NodeMenuAction.Mute -> nodeManagementActions.requestMuteNode(viewModelScope, action.node)
is NodeMenuAction.Favorite -> nodeManagementActions.requestFavoriteNode(viewModelScope, action.node) is NodeMenuAction.Favorite -> nodeManagementActions.requestFavoriteNode(viewModelScope, action.node)

View file

@ -50,11 +50,14 @@ constructor(
private val radioController: RadioController, private val radioController: RadioController,
private val alertManager: AlertManager, private val alertManager: AlertManager,
) { ) {
open fun requestRemoveNode(scope: CoroutineScope, node: Node) { open fun requestRemoveNode(scope: CoroutineScope, node: Node, onAfterRemove: () -> Unit = {}) {
alertManager.showAlert( alertManager.showAlert(
titleRes = Res.string.remove, titleRes = Res.string.remove,
messageRes = Res.string.remove_node_text, messageRes = Res.string.remove_node_text,
onConfirm = { removeNode(scope, node.num) }, onConfirm = {
removeNode(scope, node.num)
onAfterRemove()
},
) )
} }

View file

@ -0,0 +1,90 @@
/*
* Copyright (c) 2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.feature.node.detail
import androidx.lifecycle.SavedStateHandle
import dev.mokkery.answering.returns
import dev.mokkery.every
import dev.mokkery.matcher.any
import dev.mokkery.mock
import dev.mokkery.verify
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.test.setMain
import org.meshtastic.core.model.Node
import org.meshtastic.core.repository.ServiceRepository
import org.meshtastic.feature.node.component.NodeMenuAction
import org.meshtastic.feature.node.domain.usecase.GetNodeDetailsUseCase
import org.meshtastic.feature.node.model.NodeDetailAction
import org.meshtastic.proto.User
import kotlin.test.AfterTest
import kotlin.test.BeforeTest
import kotlin.test.Test
import kotlin.test.assertFalse
@OptIn(ExperimentalCoroutinesApi::class)
class HandleNodeActionTest {
private val testDispatcher = UnconfinedTestDispatcher()
private val nodeManagementActions: NodeManagementActions = mock()
private val nodeRequestActions: NodeRequestActions = mock()
private val serviceRepository: ServiceRepository = mock()
private val getNodeDetailsUseCase: GetNodeDetailsUseCase = mock()
@BeforeTest
fun setUp() {
Dispatchers.setMain(testDispatcher)
every { getNodeDetailsUseCase(any()) } returns emptyFlow()
}
@AfterTest
fun tearDown() {
Dispatchers.resetMain()
}
@Test
fun `remove action delegates to viewModel and does not navigate up immediately`() = runTest(testDispatcher) {
val node = Node(num = 1234, user = User(id = "!1234"))
every { nodeManagementActions.requestRemoveNode(any(), any(), any()) } returns Unit
val viewModel = createViewModel()
var navigateUpCalled = false
handleNodeAction(
action = NodeDetailAction.HandleNodeMenuAction(NodeMenuAction.Remove(node)),
uiState = NodeDetailUiState(),
navigateToMessages = {},
onNavigateUp = { navigateUpCalled = true },
onNavigate = {},
viewModel = viewModel,
)
verify { nodeManagementActions.requestRemoveNode(any(), node, any()) }
assertFalse(navigateUpCalled)
}
private fun createViewModel() = NodeDetailViewModel(
savedStateHandle = SavedStateHandle(mapOf("destNum" to 1234)),
nodeManagementActions = nodeManagementActions,
nodeRequestActions = nodeRequestActions,
serviceRepository = serviceRepository,
getNodeDetailsUseCase = getNodeDetailsUseCase,
)
}

View file

@ -30,6 +30,7 @@ import org.meshtastic.core.testing.FakeRadioController
import org.meshtastic.core.ui.util.AlertManager import org.meshtastic.core.ui.util.AlertManager
import org.meshtastic.proto.User import org.meshtastic.proto.User
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertTrue
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
class NodeManagementActionsTest { class NodeManagementActionsTest {
@ -69,4 +70,23 @@ class NodeManagementActionsTest {
) )
} }
} }
@Test
fun requestRemoveNode_invokes_onAfterRemove_when_user_confirms() {
val realAlertManager = AlertManager()
val actionsWithRealAlert =
NodeManagementActions(
nodeRepository = nodeRepository,
serviceRepository = serviceRepository,
radioController = radioController,
alertManager = realAlertManager,
)
val node = Node(num = 123, user = User(long_name = "Test Node"))
var afterRemoveCalled = false
actionsWithRealAlert.requestRemoveNode(testScope, node) { afterRemoveCalled = true }
realAlertManager.currentAlert.value?.onConfirm?.invoke()
assertTrue(afterRemoveCalled)
}
} }

View file

@ -20,14 +20,17 @@ import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import co.touchlab.kermit.Logger import co.touchlab.kermit.Logger
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import org.jetbrains.compose.resources.StringResource import org.jetbrains.compose.resources.StringResource
import org.koin.core.annotation.InjectedParam import org.koin.core.annotation.InjectedParam
import org.koin.core.annotation.KoinViewModel import org.koin.core.annotation.KoinViewModel
@ -44,6 +47,7 @@ import org.meshtastic.core.domain.usecase.settings.ToggleAnalyticsUseCase
import org.meshtastic.core.domain.usecase.settings.ToggleHomoglyphEncodingUseCase import org.meshtastic.core.domain.usecase.settings.ToggleHomoglyphEncodingUseCase
import org.meshtastic.core.model.ConnectionState import org.meshtastic.core.model.ConnectionState
import org.meshtastic.core.model.MqttConnectionState import org.meshtastic.core.model.MqttConnectionState
import org.meshtastic.core.model.MqttProbeStatus
import org.meshtastic.core.model.MyNodeInfo import org.meshtastic.core.model.MyNodeInfo
import org.meshtastic.core.model.Node import org.meshtastic.core.model.Node
import org.meshtastic.core.model.Position import org.meshtastic.core.model.Position
@ -144,6 +148,38 @@ open class RadioConfigViewModel(
/** MQTT proxy connection state for the settings UI. */ /** MQTT proxy connection state for the settings UI. */
val mqttConnectionState: StateFlow<MqttConnectionState> = mqttManager.mqttConnectionState val mqttConnectionState: StateFlow<MqttConnectionState> = mqttManager.mqttConnectionState
private val _mqttProbeStatus = MutableStateFlow<MqttProbeStatus?>(null)
/** Latest result from a [probeMqttConnection] call, or `null` if no probe has been run. */
val mqttProbeStatus: StateFlow<MqttProbeStatus?> = _mqttProbeStatus.asStateFlow()
private var probeJob: Job? = null
/**
* Run a one-shot reachability/credentials probe against an MQTT broker. Cancels any in-flight probe before starting
* a new one. Result is exposed via [mqttProbeStatus].
*/
fun probeMqttConnection(address: String, tlsEnabled: Boolean, username: String?, password: String?) {
probeJob?.cancel()
_mqttProbeStatus.value = MqttProbeStatus.Probing
probeJob =
viewModelScope.launch {
val result =
runCatching { mqttManager.probe(address, tlsEnabled, username, password) }
.getOrElse { e ->
Logger.w(e) { "MQTT probe threw" }
MqttProbeStatus.Other(message = e.message)
}
_mqttProbeStatus.value = result
}
}
/** Clear the latest probe result (e.g. when the user edits the address). */
fun clearMqttProbeStatus() {
probeJob?.cancel()
_mqttProbeStatus.value = null
}
private val destNumFlow = MutableStateFlow(savedStateHandle.get<Int>("destNum")) private val destNumFlow = MutableStateFlow(savedStateHandle.get<Int>("destNum"))
fun initDestNum(id: Int?) { fun initDestNum(id: Int?) {

View file

@ -21,12 +21,15 @@ package org.meshtastic.feature.settings.radio.component
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.Button
import androidx.compose.material3.CardDefaults import androidx.compose.material3.CardDefaults
import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
@ -36,6 +39,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.focus.FocusManager
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.ImeAction
@ -44,6 +48,7 @@ import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import org.jetbrains.compose.resources.stringResource import org.jetbrains.compose.resources.stringResource
import org.meshtastic.core.model.MqttConnectionState import org.meshtastic.core.model.MqttConnectionState
import org.meshtastic.core.model.MqttProbeStatus
import org.meshtastic.core.resources.Res import org.meshtastic.core.resources.Res
import org.meshtastic.core.resources.address import org.meshtastic.core.resources.address
import org.meshtastic.core.resources.default_mqtt_address import org.meshtastic.core.resources.default_mqtt_address
@ -53,11 +58,23 @@ import org.meshtastic.core.resources.map_reporting
import org.meshtastic.core.resources.mqtt import org.meshtastic.core.resources.mqtt
import org.meshtastic.core.resources.mqtt_config import org.meshtastic.core.resources.mqtt_config
import org.meshtastic.core.resources.mqtt_enabled import org.meshtastic.core.resources.mqtt_enabled
import org.meshtastic.core.resources.mqtt_probe_dns_failure
import org.meshtastic.core.resources.mqtt_probe_other_failure
import org.meshtastic.core.resources.mqtt_probe_rejected
import org.meshtastic.core.resources.mqtt_probe_running
import org.meshtastic.core.resources.mqtt_probe_success
import org.meshtastic.core.resources.mqtt_probe_success_with_info
import org.meshtastic.core.resources.mqtt_probe_tcp_failure
import org.meshtastic.core.resources.mqtt_probe_timeout
import org.meshtastic.core.resources.mqtt_probe_tls_failure
import org.meshtastic.core.resources.mqtt_status_connected import org.meshtastic.core.resources.mqtt_status_connected
import org.meshtastic.core.resources.mqtt_status_connecting import org.meshtastic.core.resources.mqtt_status_connecting
import org.meshtastic.core.resources.mqtt_status_disconnected import org.meshtastic.core.resources.mqtt_status_disconnected
import org.meshtastic.core.resources.mqtt_status_disconnected_with_reason
import org.meshtastic.core.resources.mqtt_status_inactive import org.meshtastic.core.resources.mqtt_status_inactive
import org.meshtastic.core.resources.mqtt_status_reconnecting import org.meshtastic.core.resources.mqtt_status_reconnecting
import org.meshtastic.core.resources.mqtt_status_reconnecting_with_attempt
import org.meshtastic.core.resources.mqtt_test_connection
import org.meshtastic.core.resources.password import org.meshtastic.core.resources.password
import org.meshtastic.core.resources.proxy_to_client_enabled import org.meshtastic.core.resources.proxy_to_client_enabled
import org.meshtastic.core.resources.root_topic import org.meshtastic.core.resources.root_topic
@ -75,6 +92,7 @@ fun MQTTConfigScreen(viewModel: RadioConfigViewModel, onBack: () -> Unit) {
val state by viewModel.radioConfigState.collectAsStateWithLifecycle() val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
val destNode by viewModel.destNode.collectAsStateWithLifecycle() val destNode by viewModel.destNode.collectAsStateWithLifecycle()
val mqttProxyState by viewModel.mqttConnectionState.collectAsStateWithLifecycle() val mqttProxyState by viewModel.mqttConnectionState.collectAsStateWithLifecycle()
val probeStatus by viewModel.mqttProbeStatus.collectAsStateWithLifecycle()
val destNum = destNode?.num val destNum = destNode?.num
val mqttConfig = state.moduleConfig.mqtt ?: ModuleConfig.MQTTConfig() val mqttConfig = state.moduleConfig.mqtt ?: ModuleConfig.MQTTConfig()
val formState = rememberConfigState(initialValue = mqttConfig) val formState = rememberConfigState(initialValue = mqttConfig)
@ -119,16 +137,13 @@ fun MQTTConfigScreen(viewModel: RadioConfigViewModel, onBack: () -> Unit) {
containerColor = CardDefaults.cardColors().containerColor, containerColor = CardDefaults.cardColors().containerColor,
) )
HorizontalDivider() HorizontalDivider()
EditTextPreference( MqttAddressAndProbe(
title = stringResource(Res.string.address),
value = formState.value.address,
maxSize = 63, // address max_size:64
enabled = state.connected, enabled = state.connected,
isError = false, formState = formState,
keyboardOptions = probeStatus = probeStatus,
KeyboardOptions.Default.copy(keyboardType = KeyboardType.Text, imeAction = ImeAction.Done), focusManager = focusManager,
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), onProbe = viewModel::probeMqttConnection,
onValueChanged = { formState.value = formState.value.copy(address = it) }, onClearProbe = viewModel::clearMqttProbeStatus,
) )
HorizontalDivider() HorizontalDivider()
EditTextPreference( EditTextPreference(
@ -241,13 +256,26 @@ private val GreenColor = Color(0xFF4CAF50)
private fun MqttStatusRow(state: MqttConnectionState) { private fun MqttStatusRow(state: MqttConnectionState) {
val (label, color) = val (label, color) =
when (state) { when (state) {
MqttConnectionState.INACTIVE -> is MqttConnectionState.Inactive ->
stringResource(Res.string.mqtt_status_inactive) to MaterialTheme.colorScheme.outline stringResource(Res.string.mqtt_status_inactive) to MaterialTheme.colorScheme.outline
MqttConnectionState.DISCONNECTED -> is MqttConnectionState.Disconnected -> {
stringResource(Res.string.mqtt_status_disconnected) to MaterialTheme.colorScheme.error val text =
MqttConnectionState.CONNECTING -> stringResource(Res.string.mqtt_status_connecting) to AmberColor state.reason?.let { stringResource(Res.string.mqtt_status_disconnected_with_reason, it) }
MqttConnectionState.CONNECTED -> stringResource(Res.string.mqtt_status_connected) to GreenColor ?: stringResource(Res.string.mqtt_status_disconnected)
MqttConnectionState.RECONNECTING -> stringResource(Res.string.mqtt_status_reconnecting) to AmberColor text to MaterialTheme.colorScheme.error
}
is MqttConnectionState.Connecting -> stringResource(Res.string.mqtt_status_connecting) to AmberColor
is MqttConnectionState.Connected -> stringResource(Res.string.mqtt_status_connected) to GreenColor
is MqttConnectionState.Reconnecting -> {
val err = state.lastError
val text =
if (err != null) {
stringResource(Res.string.mqtt_status_reconnecting_with_attempt, state.attempt, err)
} else {
stringResource(Res.string.mqtt_status_reconnecting)
}
text to AmberColor
}
} }
Row( Row(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
@ -262,3 +290,87 @@ private fun MqttStatusRow(state: MqttConnectionState) {
) )
} }
} }
@Composable
private fun MqttAddressAndProbe(
enabled: Boolean,
formState: ConfigState<ModuleConfig.MQTTConfig>,
probeStatus: MqttProbeStatus?,
focusManager: FocusManager,
onProbe: (address: String, tlsEnabled: Boolean, username: String, password: String) -> Unit,
onClearProbe: () -> Unit,
) {
EditTextPreference(
title = stringResource(Res.string.address),
value = formState.value.address,
maxSize = 63, // address max_size:64
enabled = enabled,
isError = false,
keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Text, imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
onValueChanged = {
formState.value = formState.value.copy(address = it)
onClearProbe()
},
)
HorizontalDivider()
MqttProbeRow(
enabled = enabled && formState.value.address.isNotBlank(),
status = probeStatus,
onTestClick = {
focusManager.clearFocus()
onProbe(
formState.value.address,
formState.value.tls_enabled,
formState.value.username,
formState.value.password,
)
},
)
}
@Composable
private fun MqttProbeRow(enabled: Boolean, status: MqttProbeStatus?, onTestClick: () -> Unit) {
Column(
modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp, vertical = 8.dp),
verticalArrangement = Arrangement.spacedBy(4.dp),
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(12.dp),
modifier = Modifier.fillMaxWidth(),
) {
Button(onClick = onTestClick, enabled = enabled && status !is MqttProbeStatus.Probing) {
Text(stringResource(Res.string.mqtt_test_connection))
}
val (probeText, probeColor) = status.toLabel() ?: return@Row
Text(text = probeText, style = MaterialTheme.typography.bodySmall, color = probeColor)
}
}
}
@Composable
private fun MqttProbeStatus?.toLabel(): Pair<String, Color>? = when (this) {
null -> null
is MqttProbeStatus.Probing ->
stringResource(Res.string.mqtt_probe_running) to MaterialTheme.colorScheme.onSurfaceVariant
is MqttProbeStatus.Success -> {
val text =
serverInfo?.let { stringResource(Res.string.mqtt_probe_success_with_info, it) }
?: stringResource(Res.string.mqtt_probe_success)
text to GreenColor
}
is MqttProbeStatus.Rejected ->
stringResource(Res.string.mqtt_probe_rejected, reason ?: reasonCode.toString()) to
MaterialTheme.colorScheme.error
is MqttProbeStatus.DnsFailure ->
stringResource(Res.string.mqtt_probe_dns_failure) to MaterialTheme.colorScheme.error
is MqttProbeStatus.TcpFailure ->
stringResource(Res.string.mqtt_probe_tcp_failure) to MaterialTheme.colorScheme.error
is MqttProbeStatus.TlsFailure ->
stringResource(Res.string.mqtt_probe_tls_failure) to MaterialTheme.colorScheme.error
is MqttProbeStatus.Timeout ->
stringResource(Res.string.mqtt_probe_timeout, timeoutMs.toInt()) to MaterialTheme.colorScheme.error
is MqttProbeStatus.Other ->
stringResource(Res.string.mqtt_probe_other_failure) to MaterialTheme.colorScheme.error
}

View file

@ -124,7 +124,7 @@ class RadioConfigViewModelTest {
MutableStateFlow(org.meshtastic.core.model.ConnectionState.Connected) MutableStateFlow(org.meshtastic.core.model.ConnectionState.Connected)
every { mqttManager.mqttConnectionState } returns every { mqttManager.mqttConnectionState } returns
MutableStateFlow(org.meshtastic.core.model.MqttConnectionState.INACTIVE) MutableStateFlow(org.meshtastic.core.model.MqttConnectionState.Inactive)
every { uiPrefs.showQuickChat } returns MutableStateFlow(false) every { uiPrefs.showQuickChat } returns MutableStateFlow(false)

View file

@ -17,22 +17,48 @@
package org.meshtastic.feature.widget package org.meshtastic.feature.widget
import android.content.Context import android.content.Context
import androidx.glance.appwidget.GlanceAppWidgetManager
import androidx.glance.appwidget.updateAll import androidx.glance.appwidget.updateAll
import co.touchlab.kermit.Logger import co.touchlab.kermit.Logger
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.launch
import org.koin.core.annotation.Single import org.koin.core.annotation.Single
import org.meshtastic.core.repository.AppWidgetUpdater import org.meshtastic.core.repository.AppWidgetUpdater
private const val WIDGET_UPDATE_DEBOUNCE_MS = 500L
@Single @Single
class AndroidAppWidgetUpdater(private val context: Context) : AppWidgetUpdater { class AndroidAppWidgetUpdater(private val context: Context, stateProvider: LocalStatsWidgetStateProvider) :
AppWidgetUpdater {
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
init {
// Observe state changes and trigger a widget re-render whenever the data changes.
// Glance compositions are ephemeral — the widget cannot self-update via collectAsState()
// alone, so we must call updateAll() externally to drive re-renders.
@OptIn(FlowPreview::class)
scope.launch {
stateProvider.state
.debounce(WIDGET_UPDATE_DEBOUNCE_MS)
.distinctUntilChanged { old, new -> old.copy(updateTimeMillis = 0) == new.copy(updateTimeMillis = 0) }
.collect { if (hasWidgetInstances()) updateAll() }
}
}
private suspend fun hasWidgetInstances(): Boolean =
GlanceAppWidgetManager(context).getGlanceIds(LocalStatsWidget::class.java).isNotEmpty()
override suspend fun updateAll() { override suspend fun updateAll() {
// Kickstart the widget composition.
// The widget internally uses collectAsState() and its own sampled StateFlow
// to drive updates automatically without excessive IPC and recreation.
@Suppress("TooGenericExceptionCaught") @Suppress("TooGenericExceptionCaught")
try { try {
LocalStatsWidget().updateAll(context) LocalStatsWidget().updateAll(context)
} catch (e: Exception) { } catch (e: Exception) {
co.touchlab.kermit.Logger.e(e) { "Failed to update widgets" } Logger.e(e) { "Failed to update widgets" }
} }
} }
} }

View file

@ -76,8 +76,6 @@ data class LocalStatsWidgetUiState(
val updateTimeMillis: Long = 0, val updateTimeMillis: Long = 0,
) )
private const val WIDGET_SUBSCRIPTION_TIMEOUT_MS = 5_000L
@Single @Single
class LocalStatsWidgetStateProvider(nodeRepository: NodeRepository, serviceRepository: ServiceRepository) { class LocalStatsWidgetStateProvider(nodeRepository: NodeRepository, serviceRepository: ServiceRepository) {
private val scope = CoroutineScope(Dispatchers.Default + SupervisorJob()) private val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())
@ -100,12 +98,7 @@ class LocalStatsWidgetStateProvider(nodeRepository: NodeRepository, serviceRepos
.map { input -> .map { input ->
mapToUiState(input.connectionState, input.totalNodes, input.onlineNodes, input.stats, input.localNode) mapToUiState(input.connectionState, input.totalNodes, input.onlineNodes, input.stats, input.localNode)
} }
.distinctUntilChanged() .stateIn(scope = scope, started = SharingStarted.Eagerly, initialValue = LocalStatsWidgetUiState())
.stateIn(
scope = scope,
started = SharingStarted.WhileSubscribed(WIDGET_SUBSCRIPTION_TIMEOUT_MS),
initialValue = LocalStatsWidgetUiState(),
)
private data class StateInput( private data class StateInput(
val connectionState: ConnectionState, val connectionState: ConnectionState,

View file

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) 2025-2026 Meshtastic LLC
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<resources>
<string name="widget_local_stats_label">Meshtastic</string>
</resources>

View file

@ -16,6 +16,7 @@
~ along with this program. If not, see <https://www.gnu.org/licenses/>. ~ along with this program. If not, see <https://www.gnu.org/licenses/>.
--> -->
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:label="@string/widget_local_stats_label"
android:initialLayout="@layout/glance_default_loading_layout" android:initialLayout="@layout/glance_default_loading_layout"
android:previewLayout="@layout/widget_local_stats_preview" android:previewLayout="@layout/widget_local_stats_preview"
android:minWidth="110dp" android:minWidth="110dp"

View file

@ -35,10 +35,17 @@ turbine = "1.2.1"
# Compose Multiplatform # Compose Multiplatform
compose-multiplatform = "1.11.0-beta02" compose-multiplatform = "1.11.0-beta02"
compose-multiplatform-material3 = "1.11.0-alpha06" compose-multiplatform-material3 = "1.11.0-alpha06"
# AndroidX Compose test/tracing artifacts share a version track with CMP but are resolved # `androidx-compose-bom-aligned` tracks androidx.compose.{runtime,ui} test/tracing
# independently by Maven. Pinning them to their own ref prevents Renovate from bumping the # artifacts that ship in lockstep with CMP. Kept as a separate version ref so Renovate
# CMP plugin version when a new AndroidX Compose pre-release appears. # can bump androidx releases (which often land first) without dragging the
androidx-compose = "1.11.0-rc01" # `org.jetbrains.compose:*` artifacts and Gradle plugin to a version JetBrains
# hasn't published yet (see PR #5180). Should normally match `compose-multiplatform`;
# AndroidCompose.kt's resolutionStrategy force-aligns these groups to the CMP version
# at resolution time regardless of the declared value here.
androidx-compose-bom-aligned = "1.11.0-rc01"
# `androidx-compose-material` (M2) is independent of CMP and pinned separately
# because some third-party libs (maps-compose-widgets, datadog) drag in
# unversioned material transitives.
androidx-compose-material = "1.7.8" androidx-compose-material = "1.7.8"
jetbrains-adaptive = "1.3.0-alpha06" jetbrains-adaptive = "1.3.0-alpha06"
@ -71,9 +78,9 @@ uri-kmp = "0.0.21"
osmdroid-android = "6.1.20" osmdroid-android = "6.1.20"
spotless = "8.4.0" spotless = "8.4.0"
wire = "6.2.0" wire = "6.2.0"
vico = "3.1.0" vico = "3.2.0-next.1"
kable = "0.42.0" kable = "0.42.0"
mqttastic = "0.1.0" mqttastic = "0.2.0"
jmdns = "3.6.3" jmdns = "3.6.3"
qrcode-kotlin = "4.5.0" qrcode-kotlin = "4.5.0"
@ -121,8 +128,8 @@ androidx-work-runtime-ktx = { module = "androidx.work:work-runtime-ktx", version
androidx-work-testing = { module = "androidx.work:work-testing", version = "2.11.2" } androidx-work-testing = { module = "androidx.work:work-testing", version = "2.11.2" }
# AndroidX Compose (explicit versions — BOM removed; CMP is the sole version authority) # AndroidX Compose (explicit versions — BOM removed; CMP is the sole version authority)
androidx-compose-runtime-tracing = { module = "androidx.compose.runtime:runtime-tracing", version.ref = "androidx-compose" } androidx-compose-runtime-tracing = { module = "androidx.compose.runtime:runtime-tracing", version.ref = "androidx-compose-bom-aligned" }
androidx-compose-ui-test-manifest = { module = "androidx.compose.ui:ui-test-manifest", version.ref = "androidx-compose" } # Required by Robolectric Compose tests (registers ComponentActivity) androidx-compose-ui-test-manifest = { module = "androidx.compose.ui:ui-test-manifest", version.ref = "androidx-compose-bom-aligned" } # Required by Robolectric Compose tests (registers ComponentActivity)
# Compose Multiplatform # Compose Multiplatform
compose-multiplatform-animation = { module = "org.jetbrains.compose.animation:animation", version.ref = "compose-multiplatform" } compose-multiplatform-animation = { module = "org.jetbrains.compose.animation:animation", version.ref = "compose-multiplatform" }

View file

@ -83,7 +83,7 @@ dependencyResolutionManagement {
plugins { plugins {
id("org.gradle.toolchains.foojay-resolver") version "1.0.0" id("org.gradle.toolchains.foojay-resolver") version "1.0.0"
id("com.gradle.develocity") version("4.4.0") id("com.gradle.develocity") version("4.4.1")
id("com.gradle.common-custom-user-data-gradle-plugin") version "2.6.0" id("com.gradle.common-custom-user-data-gradle-plugin") version "2.6.0"
} }