Skip to content
Open
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
ad25342
Bump version
garthvh Oct 18, 2025
69c318a
Message list performance fixes into 2.7.6 (#1475)
garthvh Oct 18, 2025
6705a18
Explicitly set unmessagable, seems unnessary
garthvh Oct 18, 2025
b6b7107
Merge remote-tracking branch 'refs/remotes/origin/2.7.6'
garthvh Oct 18, 2025
9e0a1ff
Add back missing mesh map features
garthvh Oct 20, 2025
16e56e7
Fix: "Retrieving nodes" significantly slower after reconnect extract…
garthvh Oct 20, 2025
4114722
Hide route lines filter from mesh map
garthvh Oct 21, 2025
1d49e02
Merge remote-tracking branch 'refs/remotes/origin/2.7.6'
garthvh Oct 21, 2025
6c3c022
Mesh Map: fuzz imprecise locations so they're distinguishable and cli…
compumike Oct 21, 2025
4aa56b1
Fix bad merge
garthvh Oct 21, 2025
ddb01f5
Update Meshtastic/Extensions/CoreData/UserEntityExtension.swift
garthvh Oct 27, 2025
428b144
Update Meshtastic/Views/Nodes/Helpers/Map/MapContent/MeshMapContent.s…
garthvh Oct 27, 2025
4facf10
Update Meshtastic/Extensions/CoreData/ChannelEntityExtension.swift
garthvh Oct 27, 2025
5ecad21
Update Meshtastic/Extensions/CoreData/MyInfoEntityExtension.swift
garthvh Oct 27, 2025
0c3f1bd
Update Meshtastic/Extensions/CoreData/ChannelEntityExtension.swift
garthvh Oct 27, 2025
3f27e3b
Keep list of previous manual connections (#1484)
jake-b Oct 28, 2025
7668a7a
Show who relayed messages (#1486)
RCGV1 Oct 28, 2025
e7b3583
upsertPositionPacket: don't use future timestamps to set node's lastH…
compumike Oct 28, 2025
92b1646
R1 NEO
garthvh Oct 28, 2025
12a1ca1
Neo
garthvh Oct 28, 2025
ebc84d3
Merge remote-tracking branch 'refs/remotes/origin/2.7.6'
garthvh Oct 28, 2025
58b1204
Update Meshtastic/Views/Settings/AppSettings.swift
garthvh Oct 28, 2025
3b9c0bf
Remove bad if
garthvh Oct 28, 2025
9e8290c
Merge remote-tracking branch 'refs/remotes/origin/2.7.6'
garthvh Oct 28, 2025
247ec49
Git rid of extra environment variable
garthvh Oct 28, 2025
59d106a
Update Meshtastic/Accessory/Transports/TCP/TCPTransport.swift
jake-b Oct 29, 2025
8df7140
MeshMap performance: quick wins (#1490)
compumike Oct 30, 2025
402cb83
NodeMap performance improvements for high # positions history (#1480)
compumike Oct 30, 2025
2ee6cdf
Fix wantRangeTestPackets to correctly follow rangeTestConfig.enabled …
compumike Oct 30, 2025
0fcf4fd
Fix interval drop down formatter
garthvh Oct 31, 2025
b4c749a
Clean up channel qr code functionality.
garthvh Nov 1, 2025
b327f13
perferredPeripheralId fix
jake-b Nov 1, 2025
feb9cf1
Set opt in
garthvh Nov 2, 2025
872c1ef
Retry once 5 second timer. dont throw the error
garthvh Nov 2, 2025
0f90d84
Queue for peripherals
garthvh Nov 6, 2025
ec5dfd5
Fix: hoplimit of dms would always fallback to hops away of the node e…
RCGV1 Nov 6, 2025
b51b5aa
Don't favorite client base
garthvh Nov 18, 2025
6aca186
Update device hardware
garthvh Nov 18, 2025
5762677
Prevent nil environment metrics
garthvh Nov 18, 2025
5707896
Bump datadog sdk
garthvh Nov 24, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 41 additions & 10 deletions Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -1486,8 +1486,8 @@
}
}
},
"⚠️ The configured value: (%@ seconds) is not one of the optimized options." : {
"comment" : "A text warning that the configured update interval is not one of the optimized options.",
"⚠️ The configured value: (%@) is not one of the optimized options." : {
"comment" : "A warning label below the picker, indicating that the selected update interval is not one of the optimized options.",
"isCommentAutoGenerated" : true
},
"🦕 End of life Version 🦖 ☄️" : {
Expand Down Expand Up @@ -3905,10 +3905,6 @@
}
}
},
"Anonymous Usage and Crash data" : {
"comment" : "A description of how the app collects and uses data about its usage and crashes. It emphasizes that this data is anonymous and non-personally identifiable.",
"isCommentAutoGenerated" : true
},
"Any missed messages will be delivered again." : {
"localizations" : {
"it" : {
Expand Down Expand Up @@ -18925,6 +18921,13 @@
}
}
}
},
"Last seen device:" : {
"comment" : "A label displayed next to the last seen device text in the `DeviceConnectRow`.",
"isCommentAutoGenerated" : true
},
"Last seen device: %@" : {

},
"Latitude" : {
"localizations" : {
Expand Down Expand Up @@ -20445,6 +20448,9 @@
}
}
}
},
"Manual Connections" : {

},
"Map Data" : {
"localizations" : {
Expand Down Expand Up @@ -20929,6 +20935,9 @@
}
}
}
},
"Meshtastic does not collect any personal information. We do anonymously collect usage and crash data to improve the app. You can opt out under app settings." : {

},
"Meshtastic Node %@ has shared channels with you" : {
"localizations" : {
Expand Down Expand Up @@ -28470,6 +28479,7 @@
}
},
"Received Ack" : {
"extractionState" : "stale",
"localizations" : {
"de" : {
"stringUnit" : {
Expand Down Expand Up @@ -28532,8 +28542,12 @@
}
}
}
},
"Received Ack: %@" : {

},
"Recipient Ack" : {
"extractionState" : "stale",
"localizations" : {
"de" : {
"stringUnit" : {
Expand Down Expand Up @@ -28596,6 +28610,9 @@
}
}
}
},
"Recipient Ack: %@" : {

},
"Recording route" : {
"localizations" : {
Expand Down Expand Up @@ -28779,6 +28796,16 @@
}
}
},
"Relayed by %d %@" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "Relayed by %1$d %2$@"
}
}
}
},
"Release Notes" : {
"localizations" : {
"it" : {
Expand Down Expand Up @@ -36743,7 +36770,12 @@
}
}
},
"These settings will %@" : {
"comment" : "A paragraph below the title that explains what the user is about to do.",
"isCommentAutoGenerated" : true
},
"These settings will %@ channels. The current LoRa Config will be replaced, if there are substantial changes to the LoRa config the device will reboot" : {
"extractionState" : "stale",
"localizations" : {
"it" : {
"stringUnit" : {
Expand Down Expand Up @@ -40176,6 +40208,9 @@
}
}
}
},
"User Privacy" : {

},
"User Uploaded" : {
"comment" : "Data source label for user uploaded files",
Expand Down Expand Up @@ -41040,10 +41075,6 @@
},
"Waypoints" : {

},
"We anonymously collect usage and crash data to improve the app. This helps us understand how the app is being used and where we can make improvements. The data we collect is non-personally identifiable and cannot be linked to you as an individual. You can opt out of this under app settings." : {
"comment" : "A description of how the app collects and uses user data. Includes a link to the app settings.",
"isCommentAutoGenerated" : true
},
"Weather Conditions" : {
"localizations" : {
Expand Down
12 changes: 8 additions & 4 deletions Meshtastic.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
2344A2B12D68DFF800170A77 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25C49D8F2C471AEA0024FBD1 /* Constants.swift */; };
2346A7192E2FB9A300CB9239 /* SerialConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2346A7182E2FB9A300CB9239 /* SerialConnection.swift */; };
2346A71D2E2FB9C500CB9239 /* SerialTransport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2346A71C2E2FB9C500CB9239 /* SerialTransport.swift */; };
2349A04A2EAE4DA30060A581 /* ManualConnectionList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2349A0492EAE4DA30060A581 /* ManualConnectionList.swift */; };
2373AE132D0A216C0086C749 /* MetricsChartSeries.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2373AE122D0A216C0086C749 /* MetricsChartSeries.swift */; };
2373AE152D0A24930086C749 /* MetricsSeriesList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2373AE142D0A24930086C749 /* MetricsSeriesList.swift */; };
2373AE172D0A26620086C749 /* EnvironmentDefaultSeries.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2373AE162D0A26620086C749 /* EnvironmentDefaultSeries.swift */; };
Expand Down Expand Up @@ -357,6 +358,7 @@
2344A2AE2D6697A700170A77 /* TelemetryEntity+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TelemetryEntity+CoreDataProperties.swift"; sourceTree = "<group>"; };
2346A7182E2FB9A300CB9239 /* SerialConnection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SerialConnection.swift; sourceTree = "<group>"; };
2346A71C2E2FB9C500CB9239 /* SerialTransport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SerialTransport.swift; sourceTree = "<group>"; };
2349A0492EAE4DA30060A581 /* ManualConnectionList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManualConnectionList.swift; sourceTree = "<group>"; };
2373AE122D0A216C0086C749 /* MetricsChartSeries.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetricsChartSeries.swift; sourceTree = "<group>"; };
2373AE142D0A24930086C749 /* MetricsSeriesList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetricsSeriesList.swift; sourceTree = "<group>"; };
2373AE162D0A26620086C749 /* EnvironmentDefaultSeries.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnvironmentDefaultSeries.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -832,6 +834,7 @@
23D316922E5618D2002FA4FB /* AsyncGate.swift */,
23E23F912E392C2B00919073 /* LogRecord+StringRepresentation.swift */,
23D9D9382E50DA97005D1C18 /* ResettableTimer.swift */,
2349A0492EAE4DA30060A581 /* ManualConnectionList.swift */,
);
path = Helpers;
sourceTree = "<group>";
Expand Down Expand Up @@ -1841,6 +1844,7 @@
BCDDFA9A2DBB180D0065189C /* ScrollToBottomButton.swift in Sources */,
DDC4C9FF2A8D982900CE201C /* DetectionSensorConfig.swift in Sources */,
2344A2AF2D6697A700170A77 /* TelemetryEntity+CoreDataClass.swift in Sources */,
2349A04A2EAE4DA30060A581 /* ManualConnectionList.swift in Sources */,
2344A2B02D6697A700170A77 /* TelemetryEntity+CoreDataProperties.swift in Sources */,
D9C983A22B79D1A600BDBE6A /* RequestPositionButton.swift in Sources */,
237AEB8F2E1FE457003B7CE3 /* Transport.swift in Sources */,
Expand Down Expand Up @@ -2094,7 +2098,7 @@
"@executable_path/Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 14.6;
MARKETING_VERSION = 2.7.5;
MARKETING_VERSION = 2.7.6;
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTS_MACCATALYST = YES;
Expand Down Expand Up @@ -2129,7 +2133,7 @@
"@executable_path/Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 14.6;
MARKETING_VERSION = 2.7.5;
MARKETING_VERSION = 2.7.6;
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTS_MACCATALYST = YES;
Expand Down Expand Up @@ -2161,7 +2165,7 @@
"@executable_path/../../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 14.6;
MARKETING_VERSION = 2.7.5;
MARKETING_VERSION = 2.7.6;
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
Expand Down Expand Up @@ -2194,7 +2198,7 @@
"@executable_path/../../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 14.6;
MARKETING_VERSION = 2.7.5;
MARKETING_VERSION = 2.7.6;
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,6 @@ extension AccessoryManager {
Logger.transport.info("[Accessory] Event stream closed")
}
self.activeConnection = (device: device, connection: connection)

if UserDefaults.preferredPeripheralId.count < 1 {
UserDefaults.preferredPeripheralId = device.id.uuidString
}
} catch let error as CBError where error.code == .peerRemovedPairingInformation {
await self.connectionStepper?.cancelCurrentlyExecutingStep(withError: AccessoryError.coreBluetoothError(error), cancelFullProcess: true)
}
Expand Down Expand Up @@ -114,6 +110,10 @@ extension AccessoryManager {
Logger.transport.info("🔗👟 [Connect] Step 5: Send wantConfig (database)")
self.updateState(.retrievingDatabase(nodeCount: 0))
self.allowDisconnect = true

Logger.transport.info("🔗 Saving preferredPeripheralId: \(device.id.uuidString)")
UserDefaults.preferredPeripheralId = device.id.uuidString

try await self.sendWantDatabase()
}

Expand Down Expand Up @@ -168,6 +168,15 @@ extension AccessoryManager {
// We have an active connection
self.updateDevice(deviceId: device.id, key: \.connectionState, value: .connected)
self.updateState(.subscribed)

// If we successfully connected to a manual connection, then save it to the list
// Remember, Device is a value type (struct) so don't use use `device` here, thats
// The value at the instantiation of the connect process. We want the currently
// updated device object in `activeConnection` with its additonal metadata from
// NodeInfo packets.
if let activeDevice = self.activeConnection?.device, activeDevice.isManualConnection {
ManualConnectionList.shared.insert(device: activeDevice)
}
}

// Step 8: Update UI and status to connected
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,13 +106,25 @@ extension AccessoryManager {
return
}

// Check if we're in database retrieval mode to defer saves for performance
let isRetrievingDatabase = if case .retrievingDatabase = self.state { true } else { false }

// TODO: nodeInfoPacket's channel: parameter is not used
if let nodeInfo = nodeInfoPacket(nodeInfo: nodeInfo, channel: 0, context: context) {
if let nodeInfo = nodeInfoPacket(nodeInfo: nodeInfo, channel: 0, context: context, deferSave: isRetrievingDatabase) {
if let activeDevice = activeConnection?.device, activeDevice.num == nodeInfo.num {
if let user = nodeInfo.user {
updateDevice(deviceId: activeDevice.id, key: \.shortName, value: user.shortName ?? "?")
updateDevice(deviceId: activeDevice.id, key: \.longName, value: user.longName ?? "Unknown".localized)
updateDevice(deviceId: activeDevice.id, key: \.hardwareModel, value: user.hwModel)

if activeDevice.isManualConnection {
// We just received a NodeInfo for the currently connected node and this is a
// manual connection. Update the metadata for the device entry in UserDefaults
// with this information for better display later
ManualConnectionList.shared.updateDevice(deviceId: activeDevice.id, key: \.shortName, value: user.shortName)
ManualConnectionList.shared.updateDevice(deviceId: activeDevice.id, key: \.longName, value: user.longName)
ManualConnectionList.shared.updateDevice(deviceId: activeDevice.id, key: \.hardwareModel, value: user.hwModel)
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,12 @@ extension AccessoryManager {
}
}
// Set initial unread message badge states
appState.unreadChannelMessages = fetchedNodeInfo[0].myInfo?.unreadMessages ?? 0
appState.unreadDirectMessages = fetchedNodeInfo[0].user?.unreadMessages ?? 0
}
if fetchedNodeInfo.count == 1 && fetchedNodeInfo[0].rangeTestConfig?.enabled == true {
wantRangeTestPackets = true
}
if fetchedNodeInfo.count == 1 && fetchedNodeInfo[0].storeForwardConfig?.enabled == true {
wantStoreAndForwardPackets = true
appState.unreadChannelMessages = fetchedNodeInfo[0].myInfo?.unreadMessages(context: context) ?? 0
appState.unreadDirectMessages = fetchedNodeInfo[0].user?.unreadMessages(context: context, skipLastMessageCheck: true) ?? 0 // skipLastMessageCheck=true because we don't update lastMessage on our own connected node

// Set wantRangeTestPackets and wantStoreAndForwardPackets
wantRangeTestPackets = fetchedNodeInfo[0].rangeTestConfig?.enabled ?? false
wantStoreAndForwardPackets = fetchedNodeInfo[0].storeForwardConfig?.enabled ?? false
}
} catch {
Logger.data.error("Failed to find a node info for the connected node \(error.localizedDescription, privacy: .public)")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -509,32 +509,32 @@ extension AccessoryManager {
let logString = String.localizedStringWithFormat("Sent a Channel for: %@ Channel Index %d".localized, String(deviceNum), chan.index)
try await send(toRadio, debugDescription: logString)
}

// Save the LoRa Config and the device will reboot
var adminPacket = AdminMessage()
adminPacket.setConfig.lora = channelSet.loraConfig
adminPacket.setConfig.lora.configOkToMqtt = okToMQTT // Preserve users okToMQTT choice
var meshPacket: MeshPacket = MeshPacket()
meshPacket.to = UInt32(deviceNum)
meshPacket.from = UInt32(deviceNum)
meshPacket.id = UInt32.random(in: UInt32(UInt8.max)..<UInt32.max)
meshPacket.priority = MeshPacket.Priority.reliable
meshPacket.wantAck = true
meshPacket.channel = 0
var dataMessage = DataMessage()
guard let adminData: Data = try? adminPacket.serializedData() else {
throw AccessoryError.ioFailed("sendReboot: Unable to serialize Admin packet")
if !addChannels {
// Save the LoRa Config and the device will reboot
var adminPacket = AdminMessage()
adminPacket.setConfig.lora = channelSet.loraConfig
adminPacket.setConfig.lora.configOkToMqtt = okToMQTT // Preserve users okToMQTT choice
var meshPacket: MeshPacket = MeshPacket()
meshPacket.to = UInt32(deviceNum)
meshPacket.from = UInt32(deviceNum)
meshPacket.id = UInt32.random(in: UInt32(UInt8.max)..<UInt32.max)
meshPacket.priority = MeshPacket.Priority.reliable
meshPacket.wantAck = true
meshPacket.channel = 0
var dataMessage = DataMessage()
guard let adminData: Data = try? adminPacket.serializedData() else {
throw AccessoryError.ioFailed("sendReboot: Unable to serialize Admin packet")
}
dataMessage.payload = adminData
dataMessage.portnum = PortNum.adminApp
meshPacket.decoded = dataMessage
var toRadio: ToRadio!
toRadio = ToRadio()
toRadio.packet = meshPacket

let logString = String.localizedStringWithFormat("Sent a LoRa.Config for: %@".localized, String(deviceNum))
try await send(toRadio, debugDescription: logString)
}
dataMessage.payload = adminData
dataMessage.portnum = PortNum.adminApp
meshPacket.decoded = dataMessage
var toRadio: ToRadio!
toRadio = ToRadio()
toRadio.packet = meshPacket

let logString = String.localizedStringWithFormat("Sent a LoRa.Config for: %@".localized, String(deviceNum))
try await send(toRadio, debugDescription: logString)

Logger.transport.debug("[AccessoryManager] sending wantConfig for saveChannelSet")
try await sendWantConfig()
}
Expand Down
Loading