feat(wifi): introduce BLE-based WiFi provisioning for nymea-compatible devices (#4968)
Some checks are pending
Dependency Submission / dependency-submission (push) Waiting to run
Main CI (Verify & Build) / validate-and-build (push) Waiting to run
Main Push Changelog / Generate main push changelog (push) Waiting to run

This commit is contained in:
James Rich 2026-04-02 12:31:17 -05:00 committed by GitHub
parent 1fee6c4431
commit 7e041c00e1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
38 changed files with 3326 additions and 50 deletions

View file

@ -40,6 +40,7 @@ import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import org.jetbrains.compose.resources.StringResource
import org.koin.core.annotation.Single
import org.meshtastic.core.common.util.NumberFormatter
import org.meshtastic.core.common.util.nowMillis
import org.meshtastic.core.model.DataPacket
import org.meshtastic.core.model.Message
@ -667,7 +668,7 @@ class MeshServiceNotificationsImpl(
}
private fun createNewNodeSeenNotification(name: String, message: String, nodeNum: Int): Notification {
val title = getString(Res.string.new_node_seen).format(name)
val title = getString(Res.string.new_node_seen, name)
val builder =
commonBuilder(NotificationType.NewNode, createOpenNodeDetailIntent(nodeNum))
.setCategory(Notification.CATEGORY_STATUS)
@ -683,9 +684,9 @@ class MeshServiceNotificationsImpl(
private fun createLowBatteryNotification(node: Node, isRemote: Boolean): Notification {
val type = if (isRemote) NotificationType.LowBatteryRemote else NotificationType.LowBatteryLocal
val title = getString(Res.string.low_battery_title).format(node.user.short_name)
val title = getString(Res.string.low_battery_title, node.user.short_name)
val batteryLevel = node.deviceMetrics.battery_level ?: 0
val message = getString(Res.string.low_battery_message).format(node.user.long_name, batteryLevel)
val message = getString(Res.string.low_battery_message, node.user.long_name, batteryLevel)
return commonBuilder(type, createOpenNodeDetailIntent(node.num))
.setCategory(Notification.CATEGORY_STATUS)
@ -876,44 +877,48 @@ class MeshServiceNotificationsImpl(
if (it > MAX_BATTERY_LEVEL) {
parts.add(BULLET + getString(Res.string.powered))
} else {
parts.add(BULLET + getString(Res.string.local_stats_battery).format(it))
parts.add(BULLET + getString(Res.string.local_stats_battery, it))
}
}
parts.add(BULLET + getString(Res.string.local_stats_nodes).format(num_online_nodes, num_total_nodes))
parts.add(BULLET + getString(Res.string.local_stats_uptime).format(formatUptime(uptime_seconds)))
parts.add(BULLET + getString(Res.string.local_stats_utilization).format(channel_utilization, air_util_tx))
parts.add(BULLET + getString(Res.string.local_stats_nodes, num_online_nodes, num_total_nodes))
parts.add(BULLET + getString(Res.string.local_stats_uptime, formatUptime(uptime_seconds)))
parts.add(
BULLET +
getString(
Res.string.local_stats_utilization,
NumberFormatter.format(channel_utilization.toDouble(), 2),
NumberFormatter.format(air_util_tx.toDouble(), 2),
),
)
if (heap_free_bytes > 0 || heap_total_bytes > 0) {
parts.add(
BULLET +
getString(Res.string.local_stats_heap) +
": " +
getString(Res.string.local_stats_heap_value).format(heap_free_bytes, heap_total_bytes),
getString(Res.string.local_stats_heap_value, heap_free_bytes, heap_total_bytes),
)
}
// Traffic Stats
if (num_packets_tx > 0 || num_packets_rx > 0) {
parts.add(
BULLET + getString(Res.string.local_stats_traffic).format(num_packets_tx, num_packets_rx, num_rx_dupe),
)
parts.add(BULLET + getString(Res.string.local_stats_traffic, num_packets_tx, num_packets_rx, num_rx_dupe))
}
if (num_tx_relay > 0) {
parts.add(BULLET + getString(Res.string.local_stats_relays).format(num_tx_relay, num_tx_relay_canceled))
parts.add(BULLET + getString(Res.string.local_stats_relays, num_tx_relay, num_tx_relay_canceled))
}
// Diagnostic Fields
val diagnosticParts = mutableListOf<String>()
if (noise_floor != 0) diagnosticParts.add(getString(Res.string.local_stats_noise).format(noise_floor))
if (noise_floor != 0) diagnosticParts.add(getString(Res.string.local_stats_noise, noise_floor))
if (num_packets_rx_bad > 0) {
diagnosticParts.add(getString(Res.string.local_stats_bad).format(num_packets_rx_bad))
diagnosticParts.add(getString(Res.string.local_stats_bad, num_packets_rx_bad))
}
if (num_tx_dropped > 0) diagnosticParts.add(getString(Res.string.local_stats_dropped).format(num_tx_dropped))
if (num_tx_dropped > 0) diagnosticParts.add(getString(Res.string.local_stats_dropped, num_tx_dropped))
if (diagnosticParts.isNotEmpty()) {
parts.add(
BULLET +
getString(Res.string.local_stats_diagnostics_prefix).format(diagnosticParts.joinToString(" | ")),
BULLET + getString(Res.string.local_stats_diagnostics_prefix, diagnosticParts.joinToString(" | ")),
)
}
@ -922,12 +927,16 @@ class MeshServiceNotificationsImpl(
private fun DeviceMetrics.formatToString(): String {
val parts = mutableListOf<String>()
battery_level?.let { parts.add(BULLET + getString(Res.string.local_stats_battery).format(it)) }
uptime_seconds?.let { parts.add(BULLET + getString(Res.string.local_stats_uptime).format(formatUptime(it))) }
battery_level?.let { parts.add(BULLET + getString(Res.string.local_stats_battery, it)) }
uptime_seconds?.let { parts.add(BULLET + getString(Res.string.local_stats_uptime, formatUptime(it))) }
if (channel_utilization != null || air_util_tx != null) {
parts.add(
BULLET +
getString(Res.string.local_stats_utilization).format(channel_utilization ?: 0f, air_util_tx ?: 0f),
getString(
Res.string.local_stats_utilization,
NumberFormatter.format((channel_utilization ?: 0f).toDouble(), 2),
NumberFormatter.format((air_util_tx ?: 0f).toDouble(), 2),
),
)
}
return parts.joinToString("\n")