Compare commits

..

No commits in common. "main" and "v2.7.14-internal.59" have entirely different histories.

78 changed files with 254 additions and 1163 deletions

View file

@ -56,15 +56,6 @@
"changelogUrl": "https://github.com/meshtastic/protobufs/compare/{{currentDigest}}...{{newDigest}}",
"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",
"matchUpdateTypes": [

1
.gitignore vendored
View file

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

View file

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

View file

@ -59,7 +59,6 @@ import org.meshtastic.app.node.metrics.getTracerouteMapOverlayInsets
import org.meshtastic.app.ui.MainScreen
import org.meshtastic.core.barcode.rememberBarcodeScanner
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.resources.Res
import org.meshtastic.core.resources.channel_invalid
@ -92,8 +91,6 @@ import org.meshtastic.feature.node.metrics.TracerouteMapScreen
class MainActivity : ComponentActivity() {
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
* itself as a LifecycleObserver in its init block.
@ -169,16 +166,6 @@ class MainActivity : ComponentActivity() {
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
private fun AppCompositionLocals(content: @Composable () -> Unit) {
CompositionLocalProvider(
@ -270,11 +257,6 @@ class MainActivity : ComponentActivity() {
UsbManager.ACTION_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()
}

View file

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

View file

@ -26,9 +26,7 @@ import com.juul.kable.UnmetRequirementException
/**
* Classification of a BLE-layer exception for the transport layer to act on.
*
* @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 isPermanent `true` if the condition won't resolve without user intervention (e.g. Bluetooth disabled).
* @property gattStatus the platform GATT status code when available (Android-specific).
* @property message a human-readable description of the failure.
*/
@ -52,9 +50,6 @@ fun Throwable.classifyBleException(): BleExceptionInfo? = when (this) {
is GattRequestRejectedException ->
BleExceptionInfo(isPermanent = false, message = "GATT request rejected (busy)")
is UnmetRequirementException ->
// 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")
BleExceptionInfo(isPermanent = true, message = message ?: "Bluetooth LE unavailable")
else -> null
}

View file

@ -60,7 +60,6 @@ import org.meshtastic.proto.AdminMessage
import org.meshtastic.proto.Config
import org.meshtastic.proto.Telemetry
import org.meshtastic.proto.ToRadio
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds
import kotlin.time.DurationUnit
@ -212,11 +211,11 @@ class MeshConnectionManagerImpl(
}
}
private fun startHandshakeStallGuard(stage: Int, timeout: Duration, action: () -> Unit) {
private fun startHandshakeStallGuard(stage: Int, action: () -> Unit) {
handshakeTimeout?.cancel()
handshakeTimeout =
scope.handledLaunch {
delay(timeout)
delay(HANDSHAKE_TIMEOUT)
if (serviceRepository.connectionState.value is ConnectionState.Connecting) {
// Attempt one retry. Note: the firmware silently drops identical consecutive
// writes (per-connection dedup). If the first want_config_id was received and
@ -292,13 +291,13 @@ class MeshConnectionManagerImpl(
override fun startConfigOnly() {
val action = { packetHandler.sendToRadio(ToRadio(want_config_id = HandshakeConstants.CONFIG_NONCE)) }
startHandshakeStallGuard(1, HANDSHAKE_TIMEOUT_STAGE1, action)
startHandshakeStallGuard(1, action)
action()
}
override fun startNodeInfoOnly() {
val action = { packetHandler.sendToRadio(ToRadio(want_config_id = HandshakeConstants.NODE_INFO_NONCE)) }
startHandshakeStallGuard(2, HANDSHAKE_TIMEOUT_STAGE2, action)
startHandshakeStallGuard(2, action)
action()
}
@ -405,14 +404,7 @@ class MeshConnectionManagerImpl(
*/
private const val PRE_HANDSHAKE_SETTLE_MS = 100L
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
private val HANDSHAKE_TIMEOUT = 30.seconds
// 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

View file

@ -31,17 +31,12 @@ import kotlinx.coroutines.flow.stateIn
import org.koin.core.annotation.Named
import org.koin.core.annotation.Single
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.resolveEndpoint
import org.meshtastic.core.repository.MqttManager
import org.meshtastic.core.repository.PacketHandler
import org.meshtastic.core.repository.ServiceRepository
import org.meshtastic.mqtt.ConnectionState
import org.meshtastic.mqtt.MqttClient
import org.meshtastic.mqtt.MqttException
import org.meshtastic.mqtt.ProbeResult
import org.meshtastic.mqtt.probe
import org.meshtastic.proto.MqttClientProxyMessage
import org.meshtastic.proto.ToRadio
@ -57,9 +52,9 @@ class MqttManagerImpl(
override val mqttConnectionState: StateFlow<MqttConnectionState> =
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) {
if (mqttMessageFlow?.isActive == true) return
@ -107,55 +102,9 @@ class MqttManagerImpl(
}
private fun ConnectionState.toAppState(): MqttConnectionState = when (this) {
is ConnectionState.Connecting -> MqttConnectionState.Connecting
is ConnectionState.Connected -> MqttConnectionState.Connected
is ConnectionState.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)
ConnectionState.DISCONNECTED -> MqttConnectionState.DISCONNECTED
ConnectionState.CONNECTING -> MqttConnectionState.CONNECTING
ConnectionState.CONNECTED -> MqttConnectionState.CONNECTED
ConnectionState.RECONNECTING -> MqttConnectionState.RECONNECTING
}
}

View file

@ -16,41 +16,20 @@
*/
package org.meshtastic.core.model
/**
* 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 {
/** App-level MQTT proxy connection state, decoupled from the MQTT library's internal type. */
enum class MqttConnectionState {
/** The MQTT proxy has not been started (disabled or not yet initialized). */
data object Inactive : MqttConnectionState()
INACTIVE,
/** The MQTT client is not connected to the broker. */
DISCONNECTED,
/** The MQTT client is actively connecting to the broker. */
data object Connecting : MqttConnectionState()
CONNECTING,
/** The MQTT client is connected and subscribed to topics. */
data object Connected : MqttConnectionState()
CONNECTED,
/**
* 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)
}
}
/** The MQTT client lost connection and is attempting to reconnect. */
RECONNECTING,
}

View file

@ -1,52 +0,0 @@
/*
* 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,10 +108,7 @@ class SerialRadioTransport(
"Uptime: ${uptime}ms, " +
"Packets RX: $packetsReceived ($bytesReceived bytes)"
}
// 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)
onDeviceDisconnect(false)
}
},
)

View file

@ -87,11 +87,6 @@ internal class SerialConnectionImpl(
port.open(usbDeviceConnection)
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.rts = true

View file

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

View file

@ -133,11 +133,7 @@ class BleRadioTransport(
@Volatile private var isFullyConnected = false
private var connectionJob: Job? = null
// 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 reconnectPolicy = BleReconnectPolicy()
private val heartbeatSender =
HeartbeatSender(

View file

@ -26,11 +26,10 @@ import kotlin.time.Duration.Companion.seconds
/**
* Encapsulates the BLE reconnection policy with exponential backoff.
*
* The policy tracks consecutive failures and decides whether to retry or signal a transient disconnect (DeviceSleep).
* 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.
* The policy tracks consecutive failures and decides whether to retry, signal a transient disconnect (DeviceSleep), or
* give up permanently.
*
* @param maxFailures maximum consecutive failures before giving up; use [Int.MAX_VALUE] to retry indefinitely
* @param maxFailures maximum consecutive failures before giving up permanently
* @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 minStableConnection minimum time a connection must stay up to be considered "stable"
@ -149,18 +148,7 @@ class BleReconnectPolicy(
companion object {
const val DEFAULT_MAX_FAILURES = 10
const val DEFAULT_FAILURE_THRESHOLD = 3
/**
* 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_SETTLE_DELAY = 1.seconds
val DEFAULT_MIN_STABLE_CONNECTION = 5.seconds
internal val RECONNECT_BASE_DELAY = 5.seconds

View file

@ -37,20 +37,18 @@ abstract class StreamTransport(protected val callback: RadioTransportCallback, p
override suspend fun close() {
Logger.d { "Closing stream for good" }
onDeviceDisconnect(waitForStopped = true, isPermanent = true)
onDeviceDisconnect(true)
}
/**
* Signals the transport callback that the device has disconnected and optionally waits for the transport to stop.
* Notify the transport callback that our device has gone away, but wait for it to come back.
*
* @param waitForStopped if true we should wait for the transport to finish - must be false if called from inside
* transport callbacks
* @param isPermanent true only when the user has explicitly disconnected (e.g. [close] was called). USB unplug, I/O
* 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.
* @param isPermanent true if the device is definitely gone (e.g. USB unplugged), false if it may come back (e.g.
* TCP transient disconnect). Defaults to true for serial subclasses may override with false.
*/
protected open fun onDeviceDisconnect(waitForStopped: Boolean, isPermanent: Boolean = false) {
protected open fun onDeviceDisconnect(waitForStopped: Boolean, isPermanent: Boolean = true) {
callback.onDisconnect(isPermanent = isPermanent)
}

View file

@ -65,6 +65,7 @@ class MQTTRepositoryImpl(
private const val DEFAULT_TOPIC_LEVEL = "/2/e/"
private const val JSON_TOPIC_LEVEL = "/2/json/"
private const val DEFAULT_SERVER_ADDRESS = "mqtt.meshtastic.org"
private const val WEBSOCKET_PATH = "/mqtt"
private const val KEEPALIVE_SECONDS = 30
private const val INITIAL_RECONNECT_DELAY_MS = 1000L
private const val MAX_RECONNECT_DELAY_MS = 30_000L
@ -73,7 +74,7 @@ class MQTTRepositoryImpl(
@Volatile private var client: MqttClient? = null
private val _connectionState = MutableStateFlow<ConnectionState>(ConnectionState.Disconnected.Idle)
private val _connectionState = MutableStateFlow(ConnectionState.DISCONNECTED)
override val connectionState: StateFlow<ConnectionState> = _connectionState.asStateFlow()
@OptIn(ExperimentalSerializationApi::class)
@ -88,7 +89,7 @@ class MQTTRepositoryImpl(
Logger.i { "MQTT Disconnecting" }
val c = client
client = null
_connectionState.value = ConnectionState.Disconnected.Idle
_connectionState.value = ConnectionState.DISCONNECTED
scope.launch { safeCatching { c?.close() }.onFailure { e -> Logger.w(e) { "MQTT clean disconnect failed" } } }
}
@ -101,7 +102,14 @@ class MQTTRepositoryImpl(
val rootTopic = mqttConfig?.root?.ifEmpty { DEFAULT_TOPIC_ROOT } ?: DEFAULT_TOPIC_ROOT
val rawAddress = mqttConfig?.address ?: DEFAULT_SERVER_ADDRESS
val endpoint = resolveEndpoint(rawAddress, mqttConfig?.tls_enabled == true)
val endpoint =
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 =
MqttClient(ownerId) {
@ -218,26 +226,3 @@ 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,7 +22,6 @@ import dev.mokkery.every
import dev.mokkery.matcher.any
import dev.mokkery.mock
import dev.mokkery.verify
import dev.mokkery.verify.VerifyMode
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceTimeBy
@ -96,10 +95,10 @@ class BleRadioTransportTest {
* [RadioInterfaceService.onDisconnect] must be called so the higher layers can react (e.g. start the device-sleep
* timeout in [MeshConnectionManagerImpl]).
*
* Virtual-time breakdown (DEFAULT_FAILURE_THRESHOLD = 3, DEFAULT_SETTLE_DELAY = 3 s): t = 3 000 ms iteration 1
* settle delay elapses, connectAndAwait throws, backoff 5 s starts t = 8 000 ms backoff ends t = 11 000 ms
* iteration 2 settle delay elapses, connectAndAwait throws, backoff 10 s starts t = 21 000 ms backoff ends t = 24
* 000 ms iteration 3 settle delay elapses, connectAndAwait throws onDisconnect called
* Virtual-time breakdown (DEFAULT_FAILURE_THRESHOLD = 3): t = 1 000 ms iteration 1 settle delay elapses,
* connectAndAwait throws, backoff 5 s starts t = 6 000 ms backoff ends t = 7 000 ms iteration 2 settle delay
* elapses, connectAndAwait throws, backoff 10 s starts t = 17 000 ms backoff ends t = 18 000 ms iteration 3
* settle delay elapses, connectAndAwait throws onDisconnect called
*/
@Test
fun `onDisconnect is called after DEFAULT_FAILURE_THRESHOLD consecutive failures`() = runTest {
@ -120,10 +119,10 @@ class BleRadioTransportTest {
)
bleTransport.start()
// Advance through exactly 3 failure iterations (≈24 001 ms virtual time).
// Advance through exactly 3 failure iterations (≈18 001 ms virtual time).
// The 4th iteration's backoff hasn't elapsed yet, so the coroutine is suspended
// and advanceTimeBy returns cleanly.
advanceTimeBy(24_001L)
advanceTimeBy(18_001L)
verify { service.onDisconnect(any(), any()) }
@ -132,17 +131,16 @@ class BleRadioTransportTest {
}
/**
* Reconnect policy must NEVER give up on its own. The transport is only ever instantiated for the user-selected
* 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.
* After [BleReconnectPolicy.DEFAULT_MAX_FAILURES] (10) consecutive failures, the reconnect loop should stop and
* signal a permanent disconnect. This prevents infinite battery drain when the device is genuinely offline.
*
* Time budget for 15 failures with bonded device (no scan): each iteration 3 s settle + immediate throw +
* 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 = 45 s, total 780 s. Use 800_000 ms to cover variance.
* Time budget for 10 failures with bonded device (no scan): Each iteration = 1s settle + connectAndAwait throw +
* backoff Backoffs: 5s, 10s, 20s, 40s, 60s, 60s, 60s, 60s, 60s, (exit at failure 10 before backoff) Total 10×1s
* 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
* variance.
*/
@Test
fun `reconnect loop never gives up - no permanent disconnect from policy`() = runTest {
fun `reconnect loop stops after DEFAULT_MAX_FAILURES with permanent disconnect`() = runTest {
val device = FakeBleDevice(address = address, name = "Test Device")
bluetoothRepository.bond(device)
@ -160,13 +158,11 @@ class BleRadioTransportTest {
)
bleTransport.start()
// Run well past where the legacy policy (maxFailures = 10) would have given up.
advanceTimeBy(800_001L)
// Advance enough time for all 10 failures to occur.
advanceTimeBy(400_001L)
// Transient disconnects (isPermanent = false) are expected once the failure threshold is hit;
// 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()) }
// Should have been called with isPermanent=true at least once (the final call).
verify { service.onDisconnect(isPermanent = true, errorMessage = any()) }
bleTransport.close()
}

View file

@ -18,82 +18,25 @@ package org.meshtastic.core.network.repository
import kotlinx.serialization.json.Json
import org.meshtastic.core.model.MqttJsonPayload
import org.meshtastic.mqtt.MqttEndpoint
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertIs
import kotlin.test.assertTrue
class MQTTRepositoryImplTest {
// region resolveEndpoint — every behavioral branch of address parsing.
@Test
fun `bare host without scheme is wrapped as ws WebSocket on the standard port`() {
val endpoint = resolveEndpoint(rawAddress = "broker.example.com", tlsEnabled = false)
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 ws = assertIs<MqttEndpoint.WebSocket>(endpoint)
assertEquals("ws://broker.example.com/mqtt", ws.url)
val address2 = "mqtt.example.com"
val (host2, port2) = address2.split(":", limit = 2).let { it[0] to (it.getOrNull(1)?.toIntOrNull() ?: 1883) }
assertEquals("mqtt.example.com", host2)
assertEquals(1883, port2)
}
@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
fun `test json payload parsing`() {
val jsonStr =
@ -129,6 +72,4 @@ class MQTTRepositoryImplTest {
assertTrue(jsonStr.contains("\"from\":12345678"))
assertTrue(jsonStr.contains("\"payload\":\"Hello World\""))
}
// endregion
}

View file

@ -78,11 +78,7 @@ open class TcpRadioTransport(
Logger.d { "[$address] Closing TCP transport" }
closing = true
transport.stop()
// 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.
callback.onDisconnect(isPermanent = true)
}
override fun keepAlive() {

View file

@ -129,10 +129,7 @@ private constructor(
// Ignore errors during port close
}
if (isActive) {
// 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)
onDeviceDisconnect(true)
}
}
}
@ -172,10 +169,8 @@ private constructor(
private const val READ_TIMEOUT_MS = 100
/**
* 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. 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.
* Creates and opens a [SerialTransport]. If the port cannot be opened, the transport signals a permanent
* disconnect to the [callback] and returns the (non-connected) instance.
*/
fun open(
portName: String,
@ -188,7 +183,7 @@ private constructor(
if (!transport.startConnection()) {
val errorMessage = diagnoseOpenFailure(portName)
Logger.w { "[$portName] Serial port could not be opened; signalling disconnect. $errorMessage" }
callback.onDisconnect(isPermanent = false, errorMessage = errorMessage)
callback.onDisconnect(isPermanent = true, errorMessage = errorMessage)
}
return transport
}

View file

@ -18,7 +18,6 @@ package org.meshtastic.core.repository
import kotlinx.coroutines.flow.StateFlow
import org.meshtastic.core.model.MqttConnectionState
import org.meshtastic.core.model.MqttProbeStatus
import org.meshtastic.proto.MqttClientProxyMessage
/** Interface for managing MQTT proxy communication. */
@ -34,15 +33,4 @@ interface MqttManager {
/** Handles an MQTT proxy message from the radio. */
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,5 +174,4 @@
<!-- Message Filter -->
<string name="bluetooth_permission">إعدادات بلوتوث</string>
<!-- Network Map Layers -->
<string name="filter_icon">عربي</string>
</resources>

View file

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

View file

@ -324,9 +324,12 @@
<string name="encryption_error_text">Публичният ключ не съвпада със записания ключ. Можете да премахнете възела и да го оставите да обмени ключове отново, но това може да показва по-сериозен проблем със сигурността. Свържете се с потребителя чрез друг надежден канал, за да определите дали промяната на ключа се дължи на фабрично нулиране или друго умишлено действие.</string>
<string name="meshtastic_new_nodes_notifications">Известия за нови възли</string>
<string name="snr">SNR</string>
<string name="snr_definition">Съотношение сигнал/шум, мярка, използвана в комуникациите за количествено определяне на нивото на желания сигнал спрямо нивото на фоновия шум. В Meshtastic и други безжични системи, по-високото съотношение сигнал/шум показва по-ясен сигнал, който може да подобри надеждността и качеството на предаване на данни.</string>
<string name="rssi">RSSI</string>
<string name="rssi_definition">Индикатор за силата на получения сигнал - измерване, използвано за определяне на нивото на получения сигнал, приемано от антената. По-високата стойност на RSSI обикновено показва по-силна и по-стабилна връзка.</string>
<string name="iaq_definition">(Качество на въздуха в помещенията) относителна скала за IAQ, стойностите са измерени с Bosch BME680. Диапазон на стойностите 0500.</string>
<string name="device_metrics_log">Метрики на устройството</string>
<string name="node_map">Карта на възела</string>
<string name="position_log">Позиция</string>
<string name="last_position_update">Последна актуализация на позицията</string>
<string name="env_metrics_log">Показатели на околната среда</string>
@ -366,6 +369,7 @@
<string name="one_month">1М</string>
<string name="max">Макс</string>
<string name="min">Мин</string>
<string name="avg">Ср</string>
<string name="expand_chart">Разгъване на диаграмата</string>
<string name="collapse_chart">Свиване на диаграмата</string>
<string name="unknown_age">Неизвестна възраст</string>
@ -486,17 +490,8 @@
<string name="frequency_slot">Честотен слот</string>
<string name="ignore_mqtt">Игнориране на MQTT</string>
<string name="mqtt_config">Конфигуриране на MQTT</string>
<string name="mqtt_status_inactive">Неактивен</string>
<string name="mqtt_status_disconnected">Прекъсната връзка</string>
<string name="mqtt_status_connecting">Свързване…</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="address">Адрес</string>
<string name="username">Потребителско име</string>
@ -976,7 +971,4 @@
<string name="wifi_provision_status_failed">Прилагането на конфигурацията за WiFi не е успешно</string>
<string name="desktop_tray_quit">Изход</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>

View file

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

View file

@ -331,9 +331,12 @@
<string name="userinfo">Informace o uživateli</string>
<string name="meshtastic_new_nodes_notifications">Oznámení o nových uzlech</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_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="device_metrics_log">Metriky zařízení</string>
<string name="node_map">Mapa uzlu</string>
<string name="position_log">Pozice</string>
<string name="last_position_update">Poslední aktualizace pozice</string>
<string name="env_metrics_log">Metriky prostředí</string>
@ -968,5 +971,4 @@
<string name="connect">Připojit</string>
<string name="done">Hotovo</string>
<string name="desktop_notification_title">Meshtastic</string>
<string name="filter_icon">Filtr</string>
</resources>

View file

@ -383,9 +383,12 @@
<string name="userinfo">Benutzerinfo</string>
<string name="meshtastic_new_nodes_notifications">Benachrichtigung neue Knoten</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_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="device_metrics_log">Gerätedaten</string>
<string name="node_map">Standortkarte Knoten</string>
<string name="position_log">Standort</string>
<string name="last_position_update">Letzte Standortaktualisierung</string>
<string name="env_metrics_log">Umweltdaten</string>
@ -432,6 +435,7 @@
<string name="one_month">1 Monat</string>
<string name="max">Maximal</string>
<string name="min">Minimum</string>
<string name="avg">Durchschnitt</string>
<string name="expand_chart">Diagramm einblenden</string>
<string name="collapse_chart">Diagramm ausblenden</string>
<string name="unknown_age">Alter unbekannt</string>
@ -609,23 +613,8 @@
<string name="ignore_mqtt">MQTT ignorieren</string>
<string name="ok_to_mqtt">OK für MQTT</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_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_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="address">Adresse</string>
<string name="username">Benutzername</string>
@ -1221,21 +1210,5 @@
<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_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="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>

View file

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

View file

@ -300,10 +300,13 @@
<string name="encryption_error">Clave pública no coincide</string>
<string name="meshtastic_new_nodes_notifications">Notificaciones de nuevo nodo</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_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.
Rango de Valores 0 - 500.</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="last_position_update">Última actualización</string>
<string name="env_metrics_log">Métricas de Entorno</string>
@ -836,5 +839,4 @@ Estos datos de ubicación pueden ser utilizados para fines como aparecer en un m
<string name="connect">Conectar</string>
<string name="done">Hecho</string>
<string name="desktop_notification_title">Meshtastic</string>
<string name="filter_icon">Filtro</string>
</resources>

View file

@ -383,9 +383,12 @@
<string name="userinfo">Kasutaja teave</string>
<string name="meshtastic_new_nodes_notifications">Uue sõlme teade</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_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="device_metrics_log">Seadme mõõdikud</string>
<string name="node_map">Sõlmede kaart</string>
<string name="position_log">Asukoht</string>
<string name="last_position_update">Viimase asukoha värskendus</string>
<string name="env_metrics_log">Keskkonnamõõdikud</string>
@ -432,6 +435,7 @@
<string name="one_month">1k</string>
<string name="max">Maksimaalselt</string>
<string name="min">Min</string>
<string name="avg">Keskm</string>
<string name="expand_chart">Laienda diagrammi</string>
<string name="collapse_chart">Ahenda diagrammi</string>
<string name="unknown_age">Tundmatu vanus</string>
@ -609,23 +613,8 @@
<string name="ignore_mqtt">Keela MQTT</string>
<string name="ok_to_mqtt">Ok MQTTi</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_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_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="address">Aadress</string>
<string name="username">Kasutajatunnus</string>
@ -1225,17 +1214,4 @@
<string name="desktop_tray_show">Näita Meshtastic</string>
<string name="desktop_tray_quit">Sule</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>

View file

@ -383,9 +383,12 @@
<string name="userinfo">Käyttäjätiedot</string>
<string name="meshtastic_new_nodes_notifications">Uuden laitteen ilmoitukset</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_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="device_metrics_log">Laitteen mittausloki</string>
<string name="node_map">Laitekartta</string>
<string name="position_log">Sijainti</string>
<string name="last_position_update">Viimeisin sijainnin päivitys</string>
<string name="env_metrics_log">Ympäristöarvot</string>
@ -432,6 +435,7 @@
<string name="one_month">1 kk</string>
<string name="max">Kaikki</string>
<string name="min">Minimi</string>
<string name="avg">Keskiarvo</string>
<string name="expand_chart">Laajenna kaavio</string>
<string name="collapse_chart">Pienennä kaavio</string>
<string name="unknown_age">Tuntematon ikä</string>
@ -609,23 +613,8 @@
<string name="ignore_mqtt">Ohita MQTT</string>
<string name="ok_to_mqtt">MQTT päällä</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_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_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="address">Osoite</string>
<string name="username">Käyttäjänimi</string>
@ -1226,17 +1215,4 @@
<string name="desktop_tray_show">Näytä Meshtastic</string>
<string name="desktop_tray_quit">Lopeta</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>

View file

@ -18,7 +18,6 @@
<resources>
<string name="meshtastic_app_name">Meshtastic</string>
<!-- 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="desc_node_filter_clear">Effacer le filtre de nœud</string>
<string name="node_filter_title">Filtrer par</string>
@ -41,11 +40,9 @@
<string name="internal">Interne</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_exclude_mqtt">Exclure MQTT</string>
<string name="unrecognized">Non reconnu</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_delivered">Délivré au nœud</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_confirmed">Confirmé via chaîne SF++</string>
@ -122,8 +119,7 @@
<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_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 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_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_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_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>
@ -167,25 +163,18 @@
<string name="ip_port">Port :</string>
<string name="connected">Connecté</string>
<string name="connection_status">Connexions actuelles :</string>
<string name="wifi_ip">IP du Wifi :</string>
<string name="wifi_ip">IP WiFi :</string>
<string name="ethernet_ip">IP Ethernet :</string>
<string name="connecting">Connexion en cours</string>
<string name="not_connected">Non connecté</string>
<string name="no_device_selected">Aucun appareil sélectionné</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="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="none">Aucun (désactivé)</string>
<string name="meshtastic_service_notifications">Notifications de service</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="debug_panel">Panneau de débogage</string>
<string name="debug_decoded_payload">Contenu décodé :</string>
@ -218,21 +207,7 @@
<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="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="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="new_messages_below">Nouveaux messages au-dessous</string>
<string name="meshtastic_messages_notifications">Notifications de message</string>
@ -253,15 +228,10 @@
<string name="reset_to_defaults">Rétablir les valeurs par défaut</string>
<string name="apply">Appliquer</string>
<string name="theme">Thème</string>
<string name="contrast">Contraste</string>
<string name="theme_light">Clair</string>
<string name="theme_dark">Sombre</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_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="use_homoglyph_characters_encoding">Encodage compact pour Cyrillique</string>
<plurals name="delete_messages">
@ -305,7 +275,6 @@
<string name="direct_message">Message direct</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_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="unknown_error">Une erreur inconnue s'est produite</string>
<string name="ignore">Ignorer</string>
@ -348,8 +317,6 @@
<string name="currently">Actuellement :</string>
<string name="mute_status_always">Toujours 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_remove">Réactiver les notifications pour '%1$s' ?</string>
<string name="replace">Remplacer</string>
@ -359,10 +326,7 @@
<string name="battery">Batterie</string>
<string name="channel_utilization">UtilCanal</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_label_value">%1$s: %2$s</string>
<string name="temperature">Temp</string>
<string name="humidity">Hum</string>
<string name="soil_temperature">Temp sol</string>
@ -383,9 +347,12 @@
<string name="userinfo">Infos utilisateur</string>
<string name="meshtastic_new_nodes_notifications">Notifikasyon nouvo nœud</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_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="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="last_position_update">Dernière mise à jour de position</string>
<string name="env_metrics_log">Métriques d'environnement</string>
@ -414,26 +381,13 @@
<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_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="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="twenty_four_hours">24H</string>
<string name="one_week">1S</string>
<string name="two_weeks">2S</string>
<string name="one_month">1M</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="copy">Copier</string>
<string name="alert_bell_text">Caractère d'appel !</string>
@ -447,17 +401,11 @@
<string name="channel_1">Canal 1</string>
<string name="channel_2">Canal 2</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="voltage">Tension</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="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="low_battery_title">Batterie faible : %1$s</string>
<string name="meshtastic_low_battery_temporary_remote_notifications">Notifications de batterie faible (nœuds favoris)</string>
@ -583,9 +531,6 @@
<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="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="use_i2s_as_buzzer">Utiliser l'I2S comme buzzer</string>
<string name="lora_config">LoRa</string>
@ -609,13 +554,8 @@
<string name="ignore_mqtt">Ignorer MQTT</string>
<string name="ok_to_mqtt">Transmission des paquets vers 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_connecting">Connexion…</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="address">Adresse</string>
<string name="username">Nom d'utilisateur</string>
@ -687,8 +627,6 @@
<string name="serial_enabled">Série activée</string>
<string name="echo_enabled">Écho activé</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="serial_mode">Mode série</string>
<string name="override_console_serial_port">Outrepasser le port série de la console</string>
@ -723,15 +661,8 @@
<string name="distance">Distance</string>
<string name="lux">Lux</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="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="indoor_air_quality_iaq">Qualité de l'air intérieur (IAQ)</string>
<string name="url">URL</string>
@ -748,7 +679,6 @@
<string name="timestamp">Horodatage</string>
<string name="heading">En-tête</string>
<string name="speed">Vitesse</string>
<string name="speed_kmh">%1$d Km/h</string>
<string name="sats">Sats</string>
<string name="alt">Alt</string>
<string name="freq">Fréq</string>
@ -814,11 +744,6 @@
<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="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="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>
@ -871,14 +796,7 @@
<string name="type_a_message">Composer un message</string>
<string name="pax_metrics_log">Métriques de 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="wifi_devices">Approvisionnement Wi-Fi pour mPWRD-OS</string>
<string name="ble_devices">Appareils Bluetooth</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>
@ -933,8 +851,6 @@
<string name="map_type_terrain">Terrain</string>
<string name="map_type_hybrid">Hybride</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="show_layer">Afficher le calque</string>
<string name="remove_layer">Supprimer le calque</string>
@ -942,10 +858,6 @@
<string name="nodes_at_this_location">Nœuds à cet emplacement</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="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="provider_name_exists">Le nom du fournisseur existe déjà.</string>
<string name="url_cannot_be_empty">URL ne peut être vide.</string>
@ -1039,7 +951,6 @@
<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_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_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>
@ -1116,10 +1027,8 @@
<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="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_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_relays">Relais : %1$d (annulé: %2$d)</string>
<string name="local_stats_diagnostics_prefix">Diagnostiques : %1$s</string>
@ -1133,94 +1042,11 @@
<string name="refresh">Actualiser</string>
<string name="updated">Mis à jour</string>
<!-- 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_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_cyan">Cyan</string>
<string name="tak_team_teal">Turquoise</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_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="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="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>

View file

@ -190,7 +190,10 @@
<string name="encryption_pkc">Cóid Poiblí Eochair</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="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="node_map">Léarscáil an Node</string>
<string name="administration">Rialachas</string>
<string name="remote_admin">Rialú iargúlta</string>
<string name="bad">Go dona</string>
@ -227,5 +230,4 @@
<!-- Compass -->
<!-- Message Filter -->
<!-- Network Map Layers -->
<string name="filter_icon">Scagaire</string>
</resources>

View file

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

View file

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

View file

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

View file

@ -186,7 +186,10 @@
<string name="encryption_pkc">Chifreman 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="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="node_map">Kat Nœud</string>
<string name="administration">Administrasyon</string>
<string name="remote_admin">Administrasyon Remote</string>
<string name="bad">Move</string>
@ -215,5 +218,4 @@
<!-- Compass -->
<!-- Message Filter -->
<!-- Network Map Layers -->
<string name="filter_icon">Filtre</string>
</resources>

View file

@ -316,9 +316,12 @@
<string name="encryption_error">Publikus kulcs nem egyezik</string>
<string name="meshtastic_new_nodes_notifications">Új állomás értesítések</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_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="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="last_position_update">Utolsó pozíciófrissítés</string>
<string name="env_metrics_log">Környezeti metrikák</string>
@ -850,5 +853,4 @@
<string name="tak_team_green">Zöld</string>
<string name="connect">Csatlakozás</string>
<string name="desktop_notification_title">Meshtastic</string>
<string name="filter_icon">Filter</string>
</resources>

View file

@ -355,9 +355,12 @@
<string name="userinfo">Informazioni Utente</string>
<string name="meshtastic_new_nodes_notifications">Notifiche di nuovi nodi</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_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="device_metrics_log">Metriche Dispositivo</string>
<string name="node_map">Mappa Dei Nodi</string>
<string name="position_log">Posizione</string>
<string name="last_position_update">Aggiornamento ultima posizione</string>
<string name="env_metrics_log">Metriche Ambientali</string>
@ -960,5 +963,4 @@
<string name="connect">Connetti</string>
<string name="done">Fatto</string>
<string name="desktop_notification_title">Meshtastic</string>
<string name="filter_icon">Filtro</string>
</resources>

View file

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

View file

@ -213,8 +213,11 @@
<string name="encryption_error">공개 키가 일치하지 않습니다</string>
<string name="meshtastic_new_nodes_notifications">새로운 노드 알림</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_definition">수신 신호 강도 지표 Received Signal Strength Indicator, RSSI는 안테나가 수신하는 신호의 전력 수준을 측정하는 데 사용되는 지표입니다. RSSI 값이 높을수록 일반적으로 더 강력하고 안정적인 연결을 나타냅니다.</string>
<string name="iaq_definition">(실내공기질) Bosch BME680으로 측정한 상대적 척도 IAQ 값. 범위 0500.</string>
<string name="node_map">노드 지도</string>
<string name="position_log">위치</string>
<string name="last_position_update">최근 위치 업데이트</string>
<string name="administration">관리</string>
@ -539,5 +542,4 @@
<string name="tak_team_green">초록</string>
<string name="connect">연결</string>
<string name="desktop_notification_title">Meshtastic</string>
<string name="filter_icon">필터</string>
</resources>

View file

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

View file

@ -201,8 +201,11 @@
<string name="encryption_error">Publieke sleutel komt niet overeen</string>
<string name="meshtastic_new_nodes_notifications">Nieuwe node meldingen</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_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="node_map">Node Kaart</string>
<string name="position_log">Positie</string>
<string name="administration">Beheer</string>
<string name="remote_admin">Extern beheer</string>
@ -415,5 +418,4 @@
<string name="tak_team_blue">Blauw</string>
<string name="tak_team_green">Groen</string>
<string name="connect">Verbinding maken</string>
<string name="filter_icon">Filter</string>
</resources>

View file

@ -194,8 +194,11 @@
<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="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_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="node_map">Nodekart</string>
<string name="administration">Administrasjon</string>
<string name="remote_admin">Fjernadministrasjon</string>
<string name="bad">Dårlig</string>
@ -238,5 +241,4 @@
<!-- Compass -->
<!-- Message Filter -->
<!-- Network Map Layers -->
<string name="filter_icon">Filter</string>
</resources>

View file

@ -333,9 +333,12 @@
<string name="userinfo">Informacje o użytkowniku</string>
<string name="meshtastic_new_nodes_notifications">Powiadomienia o nowych węzłach</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_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="device_metrics_log">Metryka urządzenia</string>
<string name="node_map">Ślad na mapie</string>
<string name="position_log">Pozycjonowanie</string>
<string name="last_position_update">Ostatnia aktualizacja lokalizacji</string>
<string name="env_metrics_log">Metryki środowiskowe</string>
@ -749,5 +752,4 @@
<string name="connect">Połącz</string>
<string name="done">Wykonano</string>
<string name="desktop_notification_title">Meshtastic</string>
<string name="filter_icon">Filtr</string>
</resources>

View file

@ -230,8 +230,11 @@
<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="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_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="node_map">Mapa do nó</string>
<string name="position_log">Posição</string>
<string name="last_position_update">Atualização da última posição</string>
<string name="administration">Administração</string>
@ -665,5 +668,4 @@
<string name="tak_team_green">Verde</string>
<string name="done">Concluído</string>
<string name="desktop_notification_title">Meshtastic</string>
<string name="filter_icon">Filtro</string>
</resources>

View file

@ -219,8 +219,11 @@
<string name="encryption_error">Incompatibilidade de chave pública</string>
<string name="meshtastic_new_nodes_notifications">Notificações de novos nodes</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_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="node_map">Mapa de nodes</string>
<string name="position_log">Posição</string>
<string name="administration">Administração</string>
<string name="remote_admin">Administração Remota</string>
@ -515,5 +518,4 @@
<string name="tak_team_green">Verde</string>
<string name="connect">Ligar</string>
<string name="desktop_notification_title">Nome do nó de alternativo</string>
<string name="filter_icon">Filtrar</string>
</resources>

View file

@ -376,9 +376,12 @@
<string name="userinfo">Info utilizator</string>
<string name="meshtastic_new_nodes_notifications">Notificări noduri noi</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_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="device_metrics_log">Valori dispozitiv</string>
<string name="node_map">Harta nodurilor</string>
<string name="position_log">Poziție</string>
<string name="last_position_update">Ultima actualizare a poziției</string>
<string name="env_metrics_log">Indicatori de mediu</string>
@ -421,6 +424,7 @@
<string name="one_week">1W</string>
<string name="two_weeks">2W</string>
<string name="max">Maxim</string>
<string name="avg">Medie</string>
<string name="expand_chart">Extindeți graficul</string>
<string name="collapse_chart">Restrânge graficul</string>
<string name="unknown_age">Vârstă necunoscută</string>
@ -1167,5 +1171,4 @@
<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="desktop_notification_title">Meshtastic</string>
<string name="filter_icon">Filtru</string>
</resources>

View file

@ -389,9 +389,12 @@
<string name="userinfo">Пользовательская информация</string>
<string name="meshtastic_new_nodes_notifications">Уведомления о новых нодах</string>
<string name="snr">Сигнал/шум</string>
<string name="snr_definition">Соотношение сигнал/шум, мера, используемая в коммуникациях для количественной оценки уровня желаемого сигнала по отношению к уровню фонового шума. В Meshtastic и других беспроводных системах более высокий SNR указывает на более четкий сигнал, который может повысить надежность и качество передачи данных.</string>
<string name="rssi">RSSI</string>
<string name="rssi_definition">Индикатор уровня принимаемого сигнала, измерение, используемое для определения уровня мощности, принимаемой антенной. Более высокое значение RSSI обычно указывает на более сильное и стабильное соединение.</string>
<string name="iaq_definition">(Качество воздуха в помещении) Относительная шкала IAQ, измеренная Bosch BME680. Диапазон значений 0500.</string>
<string name="device_metrics_log">Интервал передачи</string>
<string name="node_map">Карта нод</string>
<string name="position_log">Местоположение</string>
<string name="last_position_update">Обновление последнего местоположения</string>
<string name="env_metrics_log">Метрики окружения</string>
@ -440,6 +443,7 @@
<string name="one_month">1М</string>
<string name="max">Макс</string>
<string name="min">Мин</string>
<string name="avg">Сред</string>
<string name="expand_chart">Развернуть диаграмму</string>
<string name="collapse_chart">Свернуть диаграмму</string>
<string name="unknown_age">Неизвестный возраст</string>
@ -617,23 +621,8 @@
<string name="ignore_mqtt">Игнорировать MQTT</string>
<string name="ok_to_mqtt">ОК в 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_with_reason">Отключено — %1$s</string>
<string name="mqtt_status_connecting">Подключение...</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="address">Адрес</string>
<string name="username">Имя пользователя</string>
@ -1237,21 +1226,5 @@
<string name="wifi_provision_ssid_placeholder">Введите или выберите сеть</string>
<string name="wifi_provision_status_applied">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="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>

View file

@ -257,8 +257,11 @@
<string name="encryption_error">Nezhoda verejného kľúča</string>
<string name="meshtastic_new_nodes_notifications">Notifikácie nových uzlov</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_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="node_map">Mapa uzlov</string>
<string name="position_log">Pozícia</string>
<string name="administration">Administrácia</string>
<string name="remote_admin">Administrácia na diaľku</string>
@ -427,5 +430,4 @@
<string name="tak_team_blue">Modrá</string>
<string name="tak_team_green">Zelená</string>
<string name="desktop_notification_title">Meshtastic</string>
<string name="filter_icon">Filter</string>
</resources>

View file

@ -196,8 +196,11 @@
<string name="encryption_error">Neujemanje javnega ključa</string>
<string name="meshtastic_new_nodes_notifications">Obvestila novih vozlišč</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_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="node_map">Zemljevid vozlišč</string>
<string name="administration">Administracija</string>
<string name="remote_admin">Administracija na daljavo</string>
<string name="bad">Slab</string>
@ -242,5 +245,4 @@
<!-- Compass -->
<!-- Message Filter -->
<!-- Network Map Layers -->
<string name="filter_icon">Filter</string>
</resources>

View file

@ -186,7 +186,10 @@
<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="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="node_map">Harta e Nyjës</string>
<string name="administration">Administratë</string>
<string name="remote_admin">Administratë e Largët</string>
<string name="bad">I Keq</string>
@ -216,5 +219,4 @@
<!-- Compass -->
<!-- Message Filter -->
<!-- Network Map Layers -->
<string name="filter_icon">Filtrimi</string>
</resources>

View file

@ -241,9 +241,12 @@
<string name="encryption_error">Неусаглашеност јавних кључева</string>
<string name="meshtastic_new_nodes_notifications">Обавештење о новом чвору</string>
<string name="snr">SNR</string>
<string name="snr_definition">Однос сигнал/шум SNR је мера која се користи у комуникацијама за квантитативно одређивање нивоа жељеног сигнала у односу на ниво позадинског шума. У Мештастик и другим бежичним системима, већи SNR указује на јаснији сигнал који може побољшати поузданост и квалитет преноса података.</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="device_metrics_log">Метрика уређаја</string>
<string name="node_map">Mapa čvorova</string>
<string name="position_log">Позиција</string>
<string name="env_metrics_log">Метрике сензора</string>
<string name="administration">Administracija</string>
@ -429,5 +432,4 @@
<string name="bluetooth_permission">Блутут</string>
<string name="powered">Напајано</string>
<!-- Network Map Layers -->
<string name="filter_icon">Filter</string>
</resources>

View file

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

View file

@ -43,7 +43,6 @@
<string name="unrecognized">Okänd</string>
<string name="message_status_enroute">Inväntar kvittens</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="routing_error_none">Kvitterad</string>
<string name="routing_error_no_route">Ingen rutt</string>
@ -340,9 +339,12 @@
<string name="userinfo">Användarinfo</string>
<string name="meshtastic_new_nodes_notifications">Ny nod avisering</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_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="device_metrics_log">Enhetens mätvärden</string>
<string name="node_map">Nod karta</string>
<string name="position_log">Plats</string>
<string name="last_position_update">Senaste positionsuppdatering</string>
<string name="env_metrics_log">Miljövärden</string>
@ -371,7 +373,6 @@
<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_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="twenty_four_hours">24T</string>
<string name="one_week">1V</string>
@ -530,8 +531,6 @@
<string name="mqtt_config">MQTT-konfiguration</string>
<string name="mqtt_status_disconnected">Frånkopplad</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="address">Adress</string>
<string name="username">Användarnamn</string>
@ -949,6 +948,4 @@
<string name="connect">Anslut</string>
<string name="done">Klart</string>
<string name="desktop_notification_title">Meshtastic</string>
<string name="filter_icon">Filter</string>
<string name="action_select_device">Välj enhet</string>
</resources>

View file

@ -213,8 +213,11 @@
<string name="encryption_error">Genel Anahtar Uyuşmazlığı</string>
<string name="meshtastic_new_nodes_notifications">Yeni düğüm bildirimleri</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_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="node_map">Düğüm Haritası</string>
<string name="position_log">Konum</string>
<string name="administration">Yönetim</string>
<string name="remote_admin">Uzaktan Yönetim</string>
@ -546,5 +549,4 @@
<string name="tak_team_green">Yeşil</string>
<string name="connect">Bağlan</string>
<string name="desktop_notification_title">Meshtastic</string>
<string name="filter_icon">Filtre</string>
</resources>

View file

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

View file

@ -362,9 +362,12 @@
<string name="userinfo">用户信息</string>
<string name="meshtastic_new_nodes_notifications">新节点通知</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_definition">接收信号强度指示Received Signal Strength Indicator, RSSI是一种用于测量天线接收到的信号功率的指标。较高的 RSSI 值通常表示更强、更稳定的连接。</string>
<string name="iaq_definition">室内空气质量Indoor Air Quality, IAQ由 Bosch BME680 传感器测量的相对标尺 IAQ 值,取值范围为 0500。</string>
<string name="device_metrics_log">设备指标</string>
<string name="node_map">节点地图</string>
<string name="position_log">定位</string>
<string name="last_position_update">最后位置更新</string>
<string name="env_metrics_log">传感器指标</string>
@ -567,7 +570,6 @@
<string name="mqtt_config">MQTT设置</string>
<string name="mqtt_status_disconnected">已断开连接</string>
<string name="mqtt_status_connected">已连接</string>
<string name="mqtt_test_connection">连接测试</string>
<string name="mqtt_enabled">启用MQTT</string>
<string name="address">地址</string>
<string name="username">用户名</string>
@ -1115,6 +1117,4 @@
<string name="connect">连接</string>
<string name="done">完成</string>
<string name="desktop_notification_title">Meshtastic</string>
<string name="filter_icon">搜索节点</string>
<string name="action_select_device">选择设备</string>
</resources>

View file

@ -222,11 +222,6 @@
<string name="a11y_label_value">%1$s: %2$s</string>
<string name="a11y_message_from">來自 %1$s 的訊息:%2$s</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_two">還支援多行文字與多種樣式</string>
<string name="message_delivery_status">訊息傳遞狀態</string>
@ -378,9 +373,12 @@
<string name="userinfo">使用者資訊</string>
<string name="meshtastic_new_nodes_notifications">新節點通知</string>
<string name="snr">SNR</string>
<string name="snr_definition">信噪比SNR用於通訊中量化所需信號與背景噪音水平的指標。在 Meshtastic 及其他無線系統中,信噪比越高表示信號越清晰,可以提高數據傳輸的可靠性和品質。</string>
<string name="rssi">RSSI</string>
<string name="rssi_definition">接收信號強度指示RSSI用於測量天線所接收到信號的功率強度。 RSSI 值越高通常代表連線越強且穩定。</string>
<string name="iaq_definition">(室內空氣品質) 相對尺度 IAQ 值,由 Bosch BME680 測量。值範圍 0500。</string>
<string name="device_metrics_log">裝置計量資料</string>
<string name="node_map">節點地圖</string>
<string name="position_log">位置</string>
<string name="last_position_update">最後位置更新</string>
<string name="env_metrics_log">環境計量資料</string>
@ -412,12 +410,6 @@
<string name="traceroute_return_hops">回程跳數</string>
<string name="traceroute_round_trip">來回跳數</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="one_hour_short">1小時</string>
<string name="twenty_four_hours">二十四小時</string>
@ -426,6 +418,7 @@
<string name="one_month">1個月</string>
<string name="max">最大值</string>
<string name="min">最小</string>
<string name="avg">平均</string>
<string name="expand_chart">展開圖表</string>
<string name="collapse_chart">收起圖表</string>
<string name="unknown_age">未知年齡</string>
@ -603,23 +596,8 @@
<string name="ignore_mqtt">無視MQTT</string>
<string name="ok_to_mqtt">允許轉發至 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_with_reason">已斷線 — %1$s</string>
<string name="mqtt_status_connecting">正在連接…</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="address">地址</string>
<string name="username">用戶名</string>
@ -818,9 +796,6 @@
<string name="show_waypoints">顯示路徑</string>
<string name="show_precision_circle">顯示定位精準度</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="low_entropy_key_title">偵測到加密金鑰強度不足</string>
<string name="compromised_keys">偵測到金鑰已洩漏,點選確定後重新產生金鑰。</string>
@ -1189,8 +1164,6 @@
<string name="note">注意</string>
<string name="device_storage_ui_title">裝置儲存空間與使用者介面(唯讀)</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="connect">連線</string>
<string name="done">完成</string>
@ -1199,7 +1172,6 @@
<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_device_found">找到裝置</string>
<string name="wifi_provision_device_found_detail">準備好掃描 Wi-Fi 網路了。</string>
<string name="wifi_provision_scan_networks">搜尋網路</string>
<string name="wifi_provision_scanning_wifi">正在搜尋…</string>
<string name="wifi_provision_sending_credentials">正在套用 Wi-Fi 設定…</string>
@ -1212,21 +1184,7 @@
<string name="wifi_provision_ssid_placeholder">手動輸入或選擇一個網路</string>
<string name="wifi_provision_status_applied">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="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>

View file

@ -636,21 +636,9 @@
<string name="mqtt_config">MQTT Config</string>
<string name="mqtt_status_inactive">Inactive</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_connected">Connected</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="address">Address</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 val connectionState = MutableStateFlow<MqttConnectionState>(MqttConnectionState.Disconnected.Idle)
override val connectionState = MutableStateFlow(MqttConnectionState.DISCONNECTED)
}
// endregion

View file

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

View file

@ -60,14 +60,7 @@ class AndroidGetDiscoveredDevicesUseCase(
override fun invoke(showMock: Boolean): Flow<DiscoveredDevices> {
val nodeDb = nodeRepository.nodeDBbyNum
// 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 bondedBleFlow = bluetoothRepository.state.map { ble -> ble.bondedDevices.map { DeviceListEntry.Ble(it) } }
val processedTcpFlow =
combine(networkRepository.resolvedList, recentAddressesDataSource.recentAddresses) {

View file

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

View file

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

View file

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

View file

@ -1,90 +0,0 @@
/*
* 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,7 +30,6 @@ import org.meshtastic.core.testing.FakeRadioController
import org.meshtastic.core.ui.util.AlertManager
import org.meshtastic.proto.User
import kotlin.test.Test
import kotlin.test.assertTrue
@OptIn(ExperimentalCoroutinesApi::class)
class NodeManagementActionsTest {
@ -70,23 +69,4 @@ 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,17 +20,14 @@ import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import co.touchlab.kermit.Logger
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import org.jetbrains.compose.resources.StringResource
import org.koin.core.annotation.InjectedParam
import org.koin.core.annotation.KoinViewModel
@ -47,7 +44,6 @@ import org.meshtastic.core.domain.usecase.settings.ToggleAnalyticsUseCase
import org.meshtastic.core.domain.usecase.settings.ToggleHomoglyphEncodingUseCase
import org.meshtastic.core.model.ConnectionState
import org.meshtastic.core.model.MqttConnectionState
import org.meshtastic.core.model.MqttProbeStatus
import org.meshtastic.core.model.MyNodeInfo
import org.meshtastic.core.model.Node
import org.meshtastic.core.model.Position
@ -148,38 +144,6 @@ open class RadioConfigViewModel(
/** MQTT proxy connection state for the settings UI. */
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"))
fun initDestNum(id: Int?) {

View file

@ -21,15 +21,12 @@ package org.meshtastic.feature.settings.radio.component
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.Button
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme
@ -39,7 +36,6 @@ import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.focus.FocusManager
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.text.input.ImeAction
@ -48,7 +44,6 @@ import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import org.jetbrains.compose.resources.stringResource
import org.meshtastic.core.model.MqttConnectionState
import org.meshtastic.core.model.MqttProbeStatus
import org.meshtastic.core.resources.Res
import org.meshtastic.core.resources.address
import org.meshtastic.core.resources.default_mqtt_address
@ -58,23 +53,11 @@ import org.meshtastic.core.resources.map_reporting
import org.meshtastic.core.resources.mqtt
import org.meshtastic.core.resources.mqtt_config
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_connecting
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_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.proxy_to_client_enabled
import org.meshtastic.core.resources.root_topic
@ -92,7 +75,6 @@ fun MQTTConfigScreen(viewModel: RadioConfigViewModel, onBack: () -> Unit) {
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
val destNode by viewModel.destNode.collectAsStateWithLifecycle()
val mqttProxyState by viewModel.mqttConnectionState.collectAsStateWithLifecycle()
val probeStatus by viewModel.mqttProbeStatus.collectAsStateWithLifecycle()
val destNum = destNode?.num
val mqttConfig = state.moduleConfig.mqtt ?: ModuleConfig.MQTTConfig()
val formState = rememberConfigState(initialValue = mqttConfig)
@ -137,13 +119,16 @@ fun MQTTConfigScreen(viewModel: RadioConfigViewModel, onBack: () -> Unit) {
containerColor = CardDefaults.cardColors().containerColor,
)
HorizontalDivider()
MqttAddressAndProbe(
EditTextPreference(
title = stringResource(Res.string.address),
value = formState.value.address,
maxSize = 63, // address max_size:64
enabled = state.connected,
formState = formState,
probeStatus = probeStatus,
focusManager = focusManager,
onProbe = viewModel::probeMqttConnection,
onClearProbe = viewModel::clearMqttProbeStatus,
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) },
)
HorizontalDivider()
EditTextPreference(
@ -256,26 +241,13 @@ private val GreenColor = Color(0xFF4CAF50)
private fun MqttStatusRow(state: MqttConnectionState) {
val (label, color) =
when (state) {
is MqttConnectionState.Inactive ->
MqttConnectionState.INACTIVE ->
stringResource(Res.string.mqtt_status_inactive) to MaterialTheme.colorScheme.outline
is MqttConnectionState.Disconnected -> {
val text =
state.reason?.let { stringResource(Res.string.mqtt_status_disconnected_with_reason, it) }
?: stringResource(Res.string.mqtt_status_disconnected)
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
}
MqttConnectionState.DISCONNECTED ->
stringResource(Res.string.mqtt_status_disconnected) to MaterialTheme.colorScheme.error
MqttConnectionState.CONNECTING -> stringResource(Res.string.mqtt_status_connecting) to AmberColor
MqttConnectionState.CONNECTED -> stringResource(Res.string.mqtt_status_connected) to GreenColor
MqttConnectionState.RECONNECTING -> stringResource(Res.string.mqtt_status_reconnecting) to AmberColor
}
Row(
verticalAlignment = Alignment.CenterVertically,
@ -290,87 +262,3 @@ 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)
every { mqttManager.mqttConnectionState } returns
MutableStateFlow(org.meshtastic.core.model.MqttConnectionState.Inactive)
MutableStateFlow(org.meshtastic.core.model.MqttConnectionState.INACTIVE)
every { uiPrefs.showQuickChat } returns MutableStateFlow(false)

View file

@ -17,48 +17,22 @@
package org.meshtastic.feature.widget
import android.content.Context
import androidx.glance.appwidget.GlanceAppWidgetManager
import androidx.glance.appwidget.updateAll
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.meshtastic.core.repository.AppWidgetUpdater
private const val WIDGET_UPDATE_DEBOUNCE_MS = 500L
@Single
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()
class AndroidAppWidgetUpdater(private val context: Context) : AppWidgetUpdater {
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")
try {
LocalStatsWidget().updateAll(context)
} catch (e: Exception) {
Logger.e(e) { "Failed to update widgets" }
co.touchlab.kermit.Logger.e(e) { "Failed to update widgets" }
}
}
}

View file

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

View file

@ -1,20 +0,0 @@
<?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,7 +16,6 @@
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<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:previewLayout="@layout/widget_local_stats_preview"
android:minWidth="110dp"

View file

@ -35,17 +35,10 @@ turbine = "1.2.1"
# Compose Multiplatform
compose-multiplatform = "1.11.0-beta02"
compose-multiplatform-material3 = "1.11.0-alpha06"
# `androidx-compose-bom-aligned` tracks androidx.compose.{runtime,ui} test/tracing
# artifacts that ship in lockstep with CMP. Kept as a separate version ref so Renovate
# can bump androidx releases (which often land first) without dragging the
# `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 test/tracing artifacts share a version track with CMP but are resolved
# independently by Maven. Pinning them to their own ref prevents Renovate from bumping the
# CMP plugin version when a new AndroidX Compose pre-release appears.
androidx-compose = "1.11.0-rc01"
androidx-compose-material = "1.7.8"
jetbrains-adaptive = "1.3.0-alpha06"
@ -78,9 +71,9 @@ uri-kmp = "0.0.21"
osmdroid-android = "6.1.20"
spotless = "8.4.0"
wire = "6.2.0"
vico = "3.2.0-next.1"
vico = "3.1.0"
kable = "0.42.0"
mqttastic = "0.2.0"
mqttastic = "0.1.0"
jmdns = "3.6.3"
qrcode-kotlin = "4.5.0"
@ -128,8 +121,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 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-bom-aligned" }
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)
androidx-compose-runtime-tracing = { module = "androidx.compose.runtime:runtime-tracing", version.ref = "androidx-compose" }
androidx-compose-ui-test-manifest = { module = "androidx.compose.ui:ui-test-manifest", version.ref = "androidx-compose" } # Required by Robolectric Compose tests (registers ComponentActivity)
# Compose Multiplatform
compose-multiplatform-animation = { module = "org.jetbrains.compose.animation:animation", version.ref = "compose-multiplatform" }

View file

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