Merge pull request #1215 from meshtastic/2.6.3

2.6.3 Working Changes
This commit is contained in:
Garth Vander Houwen 2025-05-27 08:56:41 -07:00 committed by GitHub
commit 6e5c045226
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
92 changed files with 3234 additions and 2471 deletions

View file

@ -2137,6 +2137,26 @@
}
}
},
"Add Contact" : {
"localizations" : {
"zh-Hant-TW" : {
"stringUnit" : {
"state" : "translated",
"value" : "增加聯絡人"
}
}
}
},
"Add Meshtastic Node %@ as a contact" : {
"localizations" : {
"zh-Hant-TW" : {
"stringUnit" : {
"state" : "translated",
"value" : "將 Meshtastic 節點 %@ 新增為聯絡人"
}
}
}
},
"Add to favorites" : {
"localizations" : {
"de" : {
@ -2284,7 +2304,14 @@
}
},
"Administration Enabled" : {
"localizations" : {
"zh-Hant-TW" : {
"stringUnit" : {
"state" : "translated",
"value" : "管理功能已啟用"
}
}
}
},
"Advanced" : {
"localizations" : {
@ -4043,6 +4070,7 @@
}
},
"Battery Level" : {
"comment" : "VoiceOver label for battery gauge",
"localizations" : {
"de" : {
"stringUnit" : {
@ -4101,6 +4129,7 @@
}
},
"Battery Level %" : {
"comment" : "VoiceOver value for battery level",
"localizations" : {
"it" : {
"stringUnit" : {
@ -4156,40 +4185,6 @@
}
}
},
"biking" : {
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "biken"
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "tour in bicicletta"
}
},
"sr" : {
"stringUnit" : {
"state" : "translated",
"value" : "тура бициклом"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "自行车旅行"
}
},
"zh-Hant-TW" : {
"stringUnit" : {
"state" : "translated",
"value" : "自行車"
}
}
}
},
"Biking" : {
"localizations" : {
"de" : {
@ -5010,6 +5005,9 @@
}
}
},
"Bytes Used" : {
"comment" : "VoiceOver value for bytes used"
},
"Call Sign" : {
"localizations" : {
"it" : {
@ -7202,6 +7200,16 @@
}
}
},
"Contact URL" : {
"localizations" : {
"zh-Hant-TW" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "聯絡人網址"
}
}
}
},
"Contacts (%@)" : {
"localizations" : {
"de" : {
@ -9622,6 +9630,12 @@
}
}
}
},
"Disconnect Node" : {
},
"Disconnect the currently connected node" : {
},
"Dismiss" : {
"localizations" : {
@ -9995,6 +10009,16 @@
}
}
},
"Done" : {
"localizations" : {
"zh-Hant-TW" : {
"stringUnit" : {
"state" : "translated",
"value" : "完成"
}
}
}
},
"Double Tap as Button" : {
"localizations" : {
"it" : {
@ -10193,40 +10217,6 @@
}
}
},
"driving" : {
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "fahren"
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "guida"
}
},
"sr" : {
"stringUnit" : {
"state" : "translated",
"value" : "вожња"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "驾驶"
}
},
"zh-Hant-TW" : {
"stringUnit" : {
"state" : "translated",
"value" : "開車"
}
}
}
},
"Driving" : {
"localizations" : {
"de" : {
@ -13994,6 +13984,9 @@
}
}
}
},
"Hide sidebar" : {
},
"HIGH" : {
"localizations" : {
@ -14029,40 +14022,6 @@
}
}
},
"hiking" : {
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "wandern"
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "escursione"
}
},
"sr" : {
"stringUnit" : {
"state" : "translated",
"value" : "планинарње"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "徒步"
}
},
"zh-Hant-TW" : {
"stringUnit" : {
"state" : "translated",
"value" : "登山"
}
}
}
},
"Hiking" : {
"localizations" : {
"de" : {
@ -17372,9 +17331,6 @@
}
}
}
},
"message" : {
},
"Message" : {
"localizations" : {
@ -17606,6 +17562,9 @@
}
}
},
"Message Size" : {
"comment" : "VoiceOver label for message size"
},
"Message Status Options" : {
"localizations" : {
"it" : {
@ -17707,6 +17666,9 @@
}
}
}
},
"Messaging" : {
},
"Metric" : {
"localizations" : {
@ -21260,34 +21222,6 @@
}
}
},
"overlanding" : {
"localizations" : {
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "overland drive"
}
},
"sr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Вожња преко копна"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "越野"
}
},
"zh-Hant-TW" : {
"stringUnit" : {
"state" : "translated",
"value" : "開車 (overland drive)"
}
}
}
},
"Overlanding" : {
"localizations" : {
"it" : {
@ -21617,6 +21551,12 @@
"state" : "translated",
"value" : "Конфигурација PAX бројача примљена: %@"
}
},
"zh-Hant-TW" : {
"stringUnit" : {
"state" : "translated",
"value" : "PAX 計數器設定已收到: %@"
}
}
}
},
@ -21701,7 +21641,14 @@
}
},
"paxcounter.log" : {
"localizations" : {
"zh-Hant-TW" : {
"stringUnit" : {
"state" : "translated",
"value" : "paxcounter.log"
}
}
}
},
"Perform a factory reset on the node you are connected to" : {
"localizations" : {
@ -22193,6 +22140,58 @@
}
}
},
"Position config received: %@" : {
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Positionskonfiguration empfangen: %@"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Configuration de la position reçue : %@"
}
},
"he" : {
"stringUnit" : {
"state" : "translated",
"value" : "הגדרות מיקום התקבלו: %@"
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "Configurazione della posizione ricevuta: %@"
}
},
"pl" : {
"stringUnit" : {
"state" : "translated",
"value" : "Odebrano konfigurację pozycji: %@"
}
},
"se" : {
"stringUnit" : {
"state" : "translated",
"value" : "Positionskonfiguration mottagen: %@"
}
},
"sr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Конфигурација позиције примљена: %@"
}
},
"zh-Hant-TW" : {
"stringUnit" : {
"state" : "translated",
"value" : "收到位置設定檔: %@"
}
}
}
},
"Position Exchange Failed" : {
"localizations" : {
"it" : {
@ -22467,52 +22466,6 @@
}
}
},
"Positon config received: %@" : {
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Positionskonfiguration empfangen: %@"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Configuration de la position reçue : %@"
}
},
"he" : {
"stringUnit" : {
"state" : "translated",
"value" : "הגדרות מיקום התקבלו: %@"
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "Configurazione della posizione ricevuta: %@"
}
},
"pl" : {
"stringUnit" : {
"state" : "translated",
"value" : "Odebrano konfigurację pozycji: %@"
}
},
"se" : {
"stringUnit" : {
"state" : "translated",
"value" : "Positionskonfiguration mottagen: %@"
}
},
"sr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Конфигурација позиције примљена: %@"
}
}
}
},
"Power" : {
"localizations" : {
"de" : {
@ -24498,7 +24451,14 @@
}
},
"Replying to a message" : {
"localizations" : {
"zh-Hant-TW" : {
"stringUnit" : {
"state" : "translated",
"value" : "正在回覆訊息"
}
}
}
},
"Request Legacy Admin: %@" : {
"localizations" : {
@ -26022,6 +25982,16 @@
}
}
},
"Scan this QR code to add %@ to another device." : {
"localizations" : {
"zh-Hant-TW" : {
"stringUnit" : {
"state" : "translated",
"value" : "掃描這個QR code 以便將 %@ 增加到另一個裝置。"
}
}
}
},
"Screen on for" : {
"localizations" : {
"it" : {
@ -26419,7 +26389,14 @@
}
},
"Select a node from the drop down to manage connected or remote devices." : {
"localizations" : {
"zh-Hant-TW" : {
"stringUnit" : {
"state" : "translated",
"value" : "從下拉選單中選擇一個節點,以管理已連接或遠端的裝置。"
}
}
}
},
"Select a Trace Route" : {
"localizations" : {
@ -28034,6 +28011,16 @@
}
}
},
"Share Contact QR" : {
"localizations" : {
"zh-Hant-TW" : {
"stringUnit" : {
"state" : "translated",
"value" : "分享聯絡人 QR 碼"
}
}
}
},
"Share QR Code" : {
"localizations" : {
"de" : {
@ -28480,7 +28467,7 @@
}
}
},
"Show Waypoints " : {
"Show Waypoints" : {
"localizations" : {
"de" : {
"stringUnit" : {
@ -28820,40 +28807,6 @@
}
}
},
"skiing" : {
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "skitour"
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "tour sciistico"
}
},
"sr" : {
"stringUnit" : {
"state" : "translated",
"value" : "ски тура"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "滑雪之旅"
}
},
"zh-Hant-TW" : {
"stringUnit" : {
"state" : "translated",
"value" : "滑雪"
}
}
}
},
"Skiing" : {
"localizations" : {
"de" : {
@ -29525,6 +29478,12 @@
"state" : "translated",
"value" : "Подсистем"
}
},
"zh-Hant-TW" : {
"stringUnit" : {
"state" : "translated",
"value" : "子系統"
}
}
}
},
@ -29712,6 +29671,16 @@
}
}
},
"Takes a Meshtastic contact URL and saves it to the nodes database" : {
"localizations" : {
"zh-Hant-TW" : {
"stringUnit" : {
"state" : "translated",
"value" : "將 Meshtastic 聯絡人的網址儲存到節點資料庫中。"
}
}
}
},
"Tapback" : {
"localizations" : {
"de" : {
@ -30892,6 +30861,16 @@
}
}
},
"The URL for the node to add" : {
"localizations" : {
"zh-Hant-TW" : {
"stringUnit" : {
"state" : "translated",
"value" : "要新增的節點網址"
}
}
}
},
"There has been no response to a request for device metadata over the admin channel for this node." : {
"localizations" : {
"it" : {
@ -31154,28 +31133,6 @@
}
}
},
"This could take a while. The response will appear in the trace route log for the node it was sent to." : {
"localizations" : {
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "L'operazione potrebbe richiedere un po' di tempo. La risposta apparirà nel registro delle rotte di tracciamento per il nodo a cui è stata inviata."
}
},
"sr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Ово може потрајати. Одговор ће се појавити у евиденцији трасе праћења за чвор којем је послат."
}
},
"zh-Hant-TW" : {
"stringUnit" : {
"state" : "translated",
"value" : "這可能需要一段時間。回應將會顯示在被發送節點的路由追蹤紀錄中。"
}
}
}
},
"This device will send out range test messages on the selected interval." : {
"localizations" : {
"it" : {
@ -31918,6 +31875,16 @@
}
}
},
"Trace Route (in %@s)" : {
"localizations" : {
"zh-Hant-TW" : {
"stringUnit" : {
"state" : "translated",
"value" : "追蹤路由(在 %@ 秒)"
}
}
}
},
"Trace Route Log" : {
"localizations" : {
"it" : {
@ -32099,6 +32066,12 @@
"state" : "translated",
"value" : "追踪器"
}
},
"zh-Hant-TW" : {
"stringUnit" : {
"state" : "translated",
"value" : "追蹤器"
}
}
}
},
@ -32798,6 +32771,28 @@
}
}
},
"unknown" : {
"localizations" : {
"it" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "sconosciuto"
}
},
"sr" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "непознато"
}
},
"zh-Hant-TW" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "未知"
}
}
}
},
"Unknown" : {
"localizations" : {
"fr" : {
@ -32907,6 +32902,12 @@
}
}
}
},
"Unmessagable" : {
},
"Unmonitored" : {
},
"Unset" : {
"localizations" : {
@ -33503,6 +33504,9 @@
}
}
}
},
"Used to identify unmonitored or infrastructure nodes so that messaging is not avaliable to nodes that will never respond." : {
},
"User" : {
"localizations" : {
@ -33879,6 +33883,12 @@
"state" : "new",
"value" : "Version: %1$@ (%2$@)"
}
},
"zh-Hant-TW" : {
"stringUnit" : {
"state" : "translated",
"value" : "版本: %1$@ (%2$@)"
}
}
}
},
@ -34176,40 +34186,6 @@
}
}
},
"walk" : {
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "gehen"
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "passeggiata"
}
},
"sr" : {
"stringUnit" : {
"state" : "translated",
"value" : "шетња"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "步行"
}
},
"zh-Hant-TW" : {
"stringUnit" : {
"state" : "translated",
"value" : "走路"
}
}
}
},
"Walking" : {
"localizations" : {
"de" : {

View file

@ -7,6 +7,8 @@
objects = {
/* Begin PBXBuildFile section */
108FFECB2DD3F43C00BFAA81 /* ShareContactQRDialog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 108FFECA2DD3F43C00BFAA81 /* ShareContactQRDialog.swift */; };
108FFECD2DD4005600BFAA81 /* NodeInfoEntityToNodeInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 108FFECC2DD4005600BFAA81 /* NodeInfoEntityToNodeInfo.swift */; };
231B3F212D087A4C0069A07D /* MetricTableColumn.swift in Sources */ = {isa = PBXBuildFile; fileRef = 231B3F202D087A4C0069A07D /* MetricTableColumn.swift */; };
231B3F222D087A4C0069A07D /* MetricsColumnList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 231B3F1F2D087A4C0069A07D /* MetricsColumnList.swift */; };
231B3F252D087C3C0069A07D /* EnvironmentDefaultColumns.swift in Sources */ = {isa = PBXBuildFile; fileRef = 231B3F242D087C3C0069A07D /* EnvironmentDefaultColumns.swift */; };
@ -28,6 +30,7 @@
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 */; };
237B46962DC8F1C100B22D99 /* RateLimitedButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 237B46952DC8F1C100B22D99 /* RateLimitedButton.swift */; };
251926852C3BA97800249DF5 /* FavoriteNodeButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 251926842C3BA97800249DF5 /* FavoriteNodeButton.swift */; };
251926872C3BAE2200249DF5 /* NodeAlertsButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 251926862C3BAE2200249DF5 /* NodeAlertsButton.swift */; };
2519268A2C3BB1B200249DF5 /* ExchangePositionsButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 251926892C3BB1B200249DF5 /* ExchangePositionsButton.swift */; };
@ -55,12 +58,14 @@
8D3F8A412D44C2A6009EAAA4 /* PowerMetricsLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D3F8A402D44C2A6009EAAA4 /* PowerMetricsLog.swift */; };
B399E8A42B6F486400E4488E /* RetryButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = B399E8A32B6F486400E4488E /* RetryButton.swift */; };
B3E905B12B71F7F300654D07 /* TextMessageField.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3E905B02B71F7F300654D07 /* TextMessageField.swift */; };
BC10380F2DD4334400B00BFA /* AddContactIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC10380E2DD4333C00B00BFA /* AddContactIntent.swift */; };
BC47C2EF2CE0017D008245CA /* MessageNodeIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC47C2EE2CE0017D008245CA /* MessageNodeIntent.swift */; };
BC6B45FF2CB2F98900723CEB /* SaveChannelSettingsIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC6B45FE2CB2F98900723CEB /* SaveChannelSettingsIntent.swift */; };
BCB613812C67290800485544 /* SendWaypointIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCB613802C67290800485544 /* SendWaypointIntent.swift */; };
BCB613832C672A2600485544 /* MessageChannelIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCB613822C672A2600485544 /* MessageChannelIntent.swift */; };
BCB613852C68703800485544 /* NodePositionIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCB613842C68703800485544 /* NodePositionIntent.swift */; };
BCB613872C69A0FB00485544 /* AppIntentErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCB613862C69A0FB00485544 /* AppIntentErrors.swift */; };
BCD93CBA2D9E11A2006C9214 /* DisconnectNodeIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCD93CB92D9E11A2006C9214 /* DisconnectNodeIntent.swift */; };
BCDDFA9A2DBB180D0065189C /* ScrollToBottomButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCDDFA992DBB180D0065189C /* ScrollToBottomButton.swift */; };
BCE2D3C32C7ADF42008E6199 /* ShutDownNodeIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCE2D3C22C7ADF42008E6199 /* ShutDownNodeIntent.swift */; };
BCE2D3C52C7AE369008E6199 /* RestartNodeIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCE2D3C42C7AE369008E6199 /* RestartNodeIntent.swift */; };
@ -273,6 +278,8 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
108FFECA2DD3F43C00BFAA81 /* ShareContactQRDialog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareContactQRDialog.swift; sourceTree = "<group>"; };
108FFECC2DD4005600BFAA81 /* NodeInfoEntityToNodeInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeInfoEntityToNodeInfo.swift; sourceTree = "<group>"; };
231B3F1F2D087A4C0069A07D /* MetricsColumnList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetricsColumnList.swift; sourceTree = "<group>"; };
231B3F202D087A4C0069A07D /* MetricTableColumn.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetricTableColumn.swift; sourceTree = "<group>"; };
231B3F242D087C3C0069A07D /* EnvironmentDefaultColumns.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnvironmentDefaultColumns.swift; sourceTree = "<group>"; };
@ -294,6 +301,7 @@
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>"; };
237B46952DC8F1C100B22D99 /* RateLimitedButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RateLimitedButton.swift; sourceTree = "<group>"; };
251926842C3BA97800249DF5 /* FavoriteNodeButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoriteNodeButton.swift; sourceTree = "<group>"; };
251926862C3BAE2200249DF5 /* NodeAlertsButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeAlertsButton.swift; sourceTree = "<group>"; };
251926892C3BB1B200249DF5 /* ExchangePositionsButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExchangePositionsButton.swift; sourceTree = "<group>"; };
@ -316,6 +324,7 @@
8D3F8A402D44C2A6009EAAA4 /* PowerMetricsLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PowerMetricsLog.swift; sourceTree = "<group>"; };
B399E8A32B6F486400E4488E /* RetryButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RetryButton.swift; sourceTree = "<group>"; };
B3E905B02B71F7F300654D07 /* TextMessageField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextMessageField.swift; sourceTree = "<group>"; };
BC10380E2DD4333C00B00BFA /* AddContactIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddContactIntent.swift; sourceTree = "<group>"; };
BC47C2EE2CE0017D008245CA /* MessageNodeIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageNodeIntent.swift; sourceTree = "<group>"; };
BC5EBA3B2D002A2000C442FF /* MessageNodeIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageNodeIntent.swift; sourceTree = "<group>"; };
BC6B45FE2CB2F98900723CEB /* SaveChannelSettingsIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SaveChannelSettingsIntent.swift; sourceTree = "<group>"; };
@ -323,6 +332,7 @@
BCB613822C672A2600485544 /* MessageChannelIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageChannelIntent.swift; sourceTree = "<group>"; };
BCB613842C68703800485544 /* NodePositionIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodePositionIntent.swift; sourceTree = "<group>"; };
BCB613862C69A0FB00485544 /* AppIntentErrors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppIntentErrors.swift; sourceTree = "<group>"; };
BCD93CB92D9E11A2006C9214 /* DisconnectNodeIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisconnectNodeIntent.swift; sourceTree = "<group>"; };
BCDDFA992DBB180D0065189C /* ScrollToBottomButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollToBottomButton.swift; sourceTree = "<group>"; };
BCE2D3C22C7ADF42008E6199 /* ShutDownNodeIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShutDownNodeIntent.swift; sourceTree = "<group>"; };
BCE2D3C42C7AE369008E6199 /* RestartNodeIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RestartNodeIntent.swift; sourceTree = "<group>"; };
@ -409,6 +419,7 @@
DD6193742862F6E600E59241 /* ExternalNotificationConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExternalNotificationConfig.swift; sourceTree = "<group>"; };
DD6193762862F90F00E59241 /* CannedMessagesConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CannedMessagesConfig.swift; sourceTree = "<group>"; };
DD6193782863875F00E59241 /* SerialConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SerialConfig.swift; sourceTree = "<group>"; };
DD63CB4E2DD4FBEA00AFCAE2 /* MeshtasticDataModelV 51.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 51.xcdatamodel"; sourceTree = "<group>"; };
DD68BAE72C417A74004C01A0 /* MeshtasticDataModelV 40.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 40.xcdatamodel"; sourceTree = "<group>"; };
DD6D5A322CA1178300ED3032 /* TraceRoute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TraceRoute.swift; sourceTree = "<group>"; };
DD6D5A342CA13BA600ED3032 /* MeshtasticDataModelV 45.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 45.xcdatamodel"; sourceTree = "<group>"; };
@ -670,6 +681,7 @@
BCB6137F2C6728E700485544 /* AppIntents */ = {
isa = PBXGroup;
children = (
BC10380E2DD4333C00B00BFA /* AddContactIntent.swift */,
BC5EBA3B2D002A2000C442FF /* MessageNodeIntent.swift */,
BCB613802C67290800485544 /* SendWaypointIntent.swift */,
BCB613822C672A2600485544 /* MessageChannelIntent.swift */,
@ -681,6 +693,7 @@
BCE2D3C62C7B0D0A008E6199 /* ShortcutsProvider.swift */,
BC6B45FE2CB2F98900723CEB /* SaveChannelSettingsIntent.swift */,
BC47C2EE2CE0017D008245CA /* MessageNodeIntent.swift */,
BCD93CB92D9E11A2006C9214 /* DisconnectNodeIntent.swift */,
);
path = AppIntents;
sourceTree = "<group>";
@ -699,6 +712,7 @@
DD007BB12AA59B9A00F5FA12 /* CoreData */ = {
isa = PBXGroup;
children = (
108FFECC2DD4005600BFAA81 /* NodeInfoEntityToNodeInfo.swift */,
2344A2AA2D66973D00170A77 /* ManagedAttributePropertyWrapper.swift */,
DD58C5F12919AD3C00D5BEFB /* ChannelEntityExtension.swift */,
6DEDA55B2A9592F900321D2E /* MessageEntityExtension.swift */,
@ -1041,6 +1055,7 @@
DD5E523D298F5A7D00D21B61 /* Weather */,
DD6F65712C6AB8EC0053C113 /* SecureInput.swift */,
8D3F8A3E2D44BB02009EAAA4 /* PowerMetrics.swift */,
237B46952DC8F1C100B22D99 /* RateLimitedButton.swift */,
);
path = Helpers;
sourceTree = "<group>";
@ -1098,6 +1113,7 @@
DDDB26402AABEF7B003AFCB7 /* Helpers */ = {
isa = PBXGroup;
children = (
108FFECA2DD3F43C00BFAA81 /* ShareContactQRDialog.swift */,
231B3F232D087C020069A07D /* Metrics Columns */,
DDAD49EB2AFAE82500B4425D /* Map */,
DDDB26432AAC0206003AFCB7 /* NodeDetail.swift */,
@ -1393,6 +1409,7 @@
DD4640202AFF10F4002A5ECB /* WaypointForm.swift in Sources */,
233E99C12D849D6000CC3A77 /* DistanceCompactWidget.swift in Sources */,
DD769E0328D18BF1001A3F05 /* DeviceMetricsLog.swift in Sources */,
108FFECD2DD4005600BFAA81 /* NodeInfoEntityToNodeInfo.swift in Sources */,
DDAF8C5326EB1DF10058C060 /* BLEManager.swift in Sources */,
DD15E4F32B8BA56E00654F61 /* PaxCounterConfig.swift in Sources */,
DDDB445229F8ACF900EE2349 /* Date.swift in Sources */,
@ -1404,6 +1421,7 @@
DDC94FCE29CF55310082EA6E /* RtttlConfig.swift in Sources */,
DD964FBD296E6B01007C176F /* EmojiOnlyTextField.swift in Sources */,
DD8169FF272476C700F4AB02 /* LogDocument.swift in Sources */,
BCD93CBA2D9E11A2006C9214 /* DisconnectNodeIntent.swift in Sources */,
DDC94FC129CE063B0082EA6E /* BatteryLevel.swift in Sources */,
231B3F252D087C3C0069A07D /* EnvironmentDefaultColumns.swift in Sources */,
25F5D5BE2C3F6D87008036E3 /* NavigationState.swift in Sources */,
@ -1412,6 +1430,7 @@
DDDB445429F8AD1600EE2349 /* Data.swift in Sources */,
DDDB26462AACC0B7003AFCB7 /* NodeInfoItem.swift in Sources */,
DDE5B4042B2279A700FCDD05 /* TraceRouteLog.swift in Sources */,
237B46962DC8F1C100B22D99 /* RateLimitedButton.swift in Sources */,
DD6193792863875F00E59241 /* SerialConfig.swift in Sources */,
DDDB263F2AABEE20003AFCB7 /* NodeList.swift in Sources */,
DDD5BB0B2C285E45007E03CA /* LogDetail.swift in Sources */,
@ -1482,6 +1501,7 @@
DDA1C48E28DB49D3009933EC /* ChannelRoles.swift in Sources */,
DDD5BB092C285DDC007E03CA /* AppLog.swift in Sources */,
DD8ED9C8289CE4B900B3B0AB /* RoutingError.swift in Sources */,
108FFECB2DD3F43C00BFAA81 /* ShareContactQRDialog.swift in Sources */,
233E99C52D84A0B600CC3A77 /* CompactWidget.swift in Sources */,
DDC1B81A2AB5377B00C71E39 /* MessagesTips.swift in Sources */,
DD964FC62975DBFD007C176F /* QueryCoreData.swift in Sources */,
@ -1539,6 +1559,7 @@
2344A2AB2D66974300170A77 /* ManagedAttributePropertyWrapper.swift in Sources */,
BCB613832C672A2600485544 /* MessageChannelIntent.swift in Sources */,
D93068D52B812B700066FBC8 /* MessageDestination.swift in Sources */,
BC10380F2DD4334400B00BFA /* AddContactIntent.swift in Sources */,
DDA9515E2BC6F56F00CEA535 /* IndoorAirQuality.swift in Sources */,
DDDB444E29F8AB0E00EE2349 /* Int.swift in Sources */,
DD3CC6BC28E366DF00FA9159 /* Meshtastic.xcdatamodeld in Sources */,
@ -1785,7 +1806,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 2.6.2;
MARKETING_VERSION = 2.6.3;
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTS_MACCATALYST = YES;
@ -1818,7 +1839,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 2.6.2;
MARKETING_VERSION = 2.6.3;
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTS_MACCATALYST = YES;
@ -1849,7 +1870,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.6.2;
MARKETING_VERSION = 2.6.3;
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@ -1881,7 +1902,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.6.2;
MARKETING_VERSION = 2.6.3;
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@ -1980,6 +2001,7 @@
DD3CC6BA28E366DF00FA9159 /* Meshtastic.xcdatamodeld */ = {
isa = XCVersionGroup;
children = (
DD63CB4E2DD4FBEA00AFCAE2 /* MeshtasticDataModelV 51.xcdatamodel */,
233E99B32D84969500CC3A77 /* MeshtasticDataModelV 50.xcdatamodel */,
8D3F8A3D2D44B137009EAAA4 /* MeshtasticDataModelV 49.xcdatamodel */,
DDA28B1B2D32C89200EF726F /* MeshtasticDataModelV 48.xcdatamodel */,
@ -2031,7 +2053,7 @@
DD5D0A9A2931AD6B00F7EA61 /* MeshtasticDataModelV2.xcdatamodel */,
DD3CC6BB28E366DF00FA9159 /* MeshtasticDataModel.xcdatamodel */,
);
currentVersion = 233E99B32D84969500CC3A77 /* MeshtasticDataModelV 50.xcdatamodel */;
currentVersion = DD63CB4E2DD4FBEA00AFCAE2 /* MeshtasticDataModelV 51.xcdatamodel */;
name = Meshtastic.xcdatamodeld;
path = Meshtastic/Meshtastic.xcdatamodeld;
sourceTree = "<group>";

View file

@ -0,0 +1,48 @@
//
// AddContactIntent.swift
// Meshtastic
//
// Created by Benjamin Faershtein on 5/13/25.
//
import AppIntents
import MeshtasticProtobufs
struct AddContactIntent: AppIntent {
static var title: LocalizedStringResource = "Add Contact"
static var description: IntentDescription = "Takes a Meshtastic contact URL and saves it to the nodes database"
@Parameter(title: "Contact URL", description: "The URL for the node to add")
var contactUrl: URL
// Define the function that performs the main logic
func perform() async throws -> some IntentResult {
// Ensure the BLE Manager is connected
if !BLEManager.shared.isConnected {
throw AppIntentErrors.AppIntentError.notConnected
}
if contactUrl.absoluteString.lowercased().contains("meshtastic.org/v/#") {
let components = self.contactUrl.absoluteString.components(separatedBy: "#")
// Extract contact information from the URL
if let contactData = components.last {
let decodedString = contactData.base64urlToBase64()
if let decodedData = Data(base64Encoded: decodedString) {
do {
let success = BLEManager.shared.addContactFromURL(base64UrlString: contactData)
if !success {
throw AppIntentErrors.AppIntentError.message("Failed to add contact")
}
} catch {
throw AppIntentErrors.AppIntentError.message("Failed to parse contact data: \(error.localizedDescription)")
}
}
}
// Return a success result
return .result()
} else {
throw AppIntentErrors.AppIntentError.message("The URL is not a valid Meshtastic contact link")
}
}
}

View file

@ -0,0 +1,30 @@
//
// DisconnectNodeIntent.swift
// Meshtastic
//
// Created by Benjamin Faershtein on 4/2/25.
//
import Foundation
import AppIntents
struct DisconnectNodeIntent: AppIntent {
static var title: LocalizedStringResource = "Disconnect Node"
static var description: IntentDescription = "Disconnect the currently connected node"
func perform() async throws -> some IntentResult {
if !BLEManager.shared.isConnected {
throw AppIntentErrors.AppIntentError.notConnected
}
if let connectedPeripheral = BLEManager.shared.connectedPeripheral,
connectedPeripheral.peripheral.state == .connected {
BLEManager.shared.disconnectPeripheral(reconnect: false)
} else {
throw AppIntentErrors.AppIntentError.message("Error disconnecting node")
}
return .result()
}
}

View file

@ -32,5 +32,12 @@ struct ShortcutsProvider: AppShortcutsProvider {
"Send a \(.applicationName) group message"],
shortTitle: "Group Message",
systemImageName: "message")
AppShortcut(intent: DisconnectNodeIntent(),
phrases: ["Disconnect \(.applicationName) node",
"Disconnect my \(.applicationName) node",
"Disconnect from \(.applicationName)",
"Disconnect \(.applicationName)"],
shortTitle: "Disconnect",
systemImageName: "antenna.radiowaves.left.and.right.slash")
}
}

View file

@ -0,0 +1,12 @@
{
"info" : {
"author" : "xcode",
"version" : 1
},
"symbols" : [
{
"filename" : "progress.ring.dashed.svg",
"idiom" : "universal"
}
]
}

View file

@ -0,0 +1,169 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--Generator: Apple Native CoreSVG 341-->
<!DOCTYPE svg
PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 3300 2200">
<!--glyph: "", point size: 100.0, font version: "20.0d10e1", template writer version: "138.0.0"-->
<style>.defaults {-sfsymbols-wiggle-style:clockwise;-sfsymbols-rotates-clockwise:true}
.monochrome-0 {-sfsymbols-variable-threshold:0.93;-sfsymbols-motion-group:12;-sfsymbols-layer-tags:-4ed3e2b5f369b4bf}
.monochrome-1 {-sfsymbols-variable-threshold:0.86;-sfsymbols-motion-group:11;-sfsymbols-layer-tags:-4ed3e2b5f369b4bf}
.monochrome-2 {-sfsymbols-variable-threshold:0.78;-sfsymbols-motion-group:10;-sfsymbols-layer-tags:-4ed3e2b5f369b4bf}
.monochrome-3 {-sfsymbols-variable-threshold:0.7;-sfsymbols-motion-group:9;-sfsymbols-layer-tags:-4ed3e2b5f369b4bf}
.monochrome-4 {-sfsymbols-variable-threshold:0.63;-sfsymbols-motion-group:8;-sfsymbols-layer-tags:-4ed3e2b5f369b4bf}
.monochrome-5 {-sfsymbols-variable-threshold:0.55;-sfsymbols-motion-group:7;-sfsymbols-layer-tags:-4ed3e2b5f369b4bf}
.monochrome-6 {-sfsymbols-variable-threshold:0.47;-sfsymbols-motion-group:6;-sfsymbols-layer-tags:-4ed3e2b5f369b4bf}
.monochrome-7 {-sfsymbols-variable-threshold:0.39;-sfsymbols-motion-group:5;-sfsymbols-layer-tags:-4ed3e2b5f369b4bf}
.monochrome-8 {-sfsymbols-variable-threshold:0.32;-sfsymbols-motion-group:4;-sfsymbols-layer-tags:-4ed3e2b5f369b4bf}
.monochrome-9 {-sfsymbols-variable-threshold:0.24;-sfsymbols-motion-group:3;-sfsymbols-layer-tags:-4ed3e2b5f369b4bf}
.monochrome-10 {-sfsymbols-variable-threshold:0.16;-sfsymbols-motion-group:2;-sfsymbols-layer-tags:-4ed3e2b5f369b4bf}
.monochrome-11 {-sfsymbols-variable-threshold:0.09;-sfsymbols-motion-group:1;-sfsymbols-layer-tags:-4ed3e2b5f369b4bf}
.multicolor-0:tintColor {-sfsymbols-variable-threshold:0.93;-sfsymbols-motion-group:12;-sfsymbols-layer-tags:-4ed3e2b5f369b4bf}
.multicolor-1:tintColor {-sfsymbols-variable-threshold:0.86;-sfsymbols-motion-group:11;-sfsymbols-layer-tags:-4ed3e2b5f369b4bf}
.multicolor-2:tintColor {-sfsymbols-variable-threshold:0.78;-sfsymbols-motion-group:10;-sfsymbols-layer-tags:-4ed3e2b5f369b4bf}
.multicolor-3:tintColor {-sfsymbols-clear-behind:true;-sfsymbols-variable-threshold:0.7;-sfsymbols-motion-group:9;-sfsymbols-layer-tags:-4ed3e2b5f369b4bf}
.multicolor-4:tintColor {-sfsymbols-variable-threshold:0.63;-sfsymbols-motion-group:8;-sfsymbols-layer-tags:-4ed3e2b5f369b4bf}
.multicolor-5:tintColor {-sfsymbols-variable-threshold:0.55;-sfsymbols-motion-group:7;-sfsymbols-layer-tags:-4ed3e2b5f369b4bf}
.multicolor-6:tintColor {-sfsymbols-variable-threshold:0.47;-sfsymbols-motion-group:6;-sfsymbols-layer-tags:-4ed3e2b5f369b4bf}
.multicolor-7:tintColor {-sfsymbols-variable-threshold:0.39;-sfsymbols-motion-group:5;-sfsymbols-layer-tags:-4ed3e2b5f369b4bf}
.multicolor-8:tintColor {-sfsymbols-variable-threshold:0.32;-sfsymbols-motion-group:4;-sfsymbols-layer-tags:-4ed3e2b5f369b4bf}
.multicolor-9:tintColor {-sfsymbols-variable-threshold:0.24;-sfsymbols-motion-group:3;-sfsymbols-layer-tags:-4ed3e2b5f369b4bf}
.multicolor-10:tintColor {-sfsymbols-variable-threshold:0.16;-sfsymbols-motion-group:2;-sfsymbols-layer-tags:-4ed3e2b5f369b4bf}
.multicolor-11:tintColor {-sfsymbols-variable-threshold:0.09;-sfsymbols-motion-group:1;-sfsymbols-layer-tags:-4ed3e2b5f369b4bf}
.hierarchical-0:primary {-sfsymbols-variable-threshold:0.93;-sfsymbols-motion-group:12;-sfsymbols-layer-tags:-4ed3e2b5f369b4bf}
.hierarchical-1:primary {-sfsymbols-variable-threshold:0.86;-sfsymbols-motion-group:11;-sfsymbols-layer-tags:-4ed3e2b5f369b4bf}
.hierarchical-2:primary {-sfsymbols-variable-threshold:0.78;-sfsymbols-motion-group:10;-sfsymbols-layer-tags:-4ed3e2b5f369b4bf}
.hierarchical-3:primary {-sfsymbols-clear-behind:true;-sfsymbols-variable-threshold:0.7;-sfsymbols-motion-group:9;-sfsymbols-layer-tags:-4ed3e2b5f369b4bf}
.hierarchical-4:secondary {-sfsymbols-variable-threshold:0.63;-sfsymbols-motion-group:8;-sfsymbols-layer-tags:-4ed3e2b5f369b4bf}
.hierarchical-5:tertiary {-sfsymbols-variable-threshold:0.55;-sfsymbols-motion-group:7;-sfsymbols-layer-tags:-4ed3e2b5f369b4bf}
.hierarchical-6:primary {-sfsymbols-variable-threshold:0.47;-sfsymbols-motion-group:6;-sfsymbols-layer-tags:-4ed3e2b5f369b4bf}
.hierarchical-7:primary {-sfsymbols-variable-threshold:0.39;-sfsymbols-motion-group:5;-sfsymbols-layer-tags:-4ed3e2b5f369b4bf}
.hierarchical-8:primary {-sfsymbols-variable-threshold:0.32;-sfsymbols-motion-group:4;-sfsymbols-layer-tags:-4ed3e2b5f369b4bf}
.hierarchical-9:primary {-sfsymbols-variable-threshold:0.24;-sfsymbols-motion-group:3;-sfsymbols-layer-tags:-4ed3e2b5f369b4bf}
.hierarchical-10:primary {-sfsymbols-variable-threshold:0.16;-sfsymbols-motion-group:2;-sfsymbols-layer-tags:-4ed3e2b5f369b4bf}
.hierarchical-11:primary {-sfsymbols-variable-threshold:0.09;-sfsymbols-motion-group:1;-sfsymbols-layer-tags:-4ed3e2b5f369b4bf}
.SFSymbolsPreviewWireframe {fill:none;opacity:1.0;stroke:black;stroke-width:0.5}
</style>
<g id="Notes">
<rect height="2200" id="artboard" style="fill:white;opacity:1" width="3300" x="0" y="0"/>
<line style="fill:none;stroke:black;opacity:1;stroke-width:0.5;" x1="263" x2="3036" y1="292" y2="292"/>
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;font-weight:bold;" transform="matrix(1 0 0 1 263 322)">Weight/Scale Variations</text>
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;" transform="matrix(1 0 0 1 559.711 322)">Ultralight</text>
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;" transform="matrix(1 0 0 1 856.422 322)">Thin</text>
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;" transform="matrix(1 0 0 1 1153.13 322)">Light</text>
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;" transform="matrix(1 0 0 1 1449.84 322)">Regular</text>
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;" transform="matrix(1 0 0 1 1746.56 322)">Medium</text>
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;" transform="matrix(1 0 0 1 2043.27 322)">Semibold</text>
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;" transform="matrix(1 0 0 1 2339.98 322)">Bold</text>
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;" transform="matrix(1 0 0 1 2636.69 322)">Heavy</text>
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;" transform="matrix(1 0 0 1 2933.4 322)">Black</text>
<line style="fill:none;stroke:black;opacity:1;stroke-width:0.5;" x1="263" x2="3036" y1="1903" y2="1903"/>
<g transform="matrix(0.2 0 0 0.2 263 1933)">
<path d="m46.2402 4.15039c21.7773 0 39.4531-17.627 39.4531-39.4043s-17.6758-39.4043-39.4531-39.4043c-21.7285 0-39.4043 17.627-39.4043 39.4043s17.6758 39.4043 39.4043 39.4043Zm0-7.42188c-17.6758 0-31.9336-14.3066-31.9336-31.9824s14.2578-31.9824 31.9336-31.9824 31.9824 14.3066 31.9824 31.9824-14.3066 31.9824-31.9824 31.9824Zm-17.9688-31.9824c0 2.14844 1.51367 3.61328 3.75977 3.61328h10.498v10.5957c0 2.19727 1.46484 3.71094 3.61328 3.71094 2.24609 0 3.71094-1.51367 3.71094-3.71094v-10.5957h10.5957c2.19727 0 3.71094-1.46484 3.71094-3.61328 0-2.19727-1.51367-3.71094-3.71094-3.71094h-10.5957v-10.5469c0-2.24609-1.46484-3.75977-3.71094-3.75977-2.14844 0-3.61328 1.51367-3.61328 3.75977v10.5469h-10.498c-2.24609 0-3.75977 1.51367-3.75977 3.71094Z"/>
</g>
<g transform="matrix(0.2 0 0 0.2 281.506 1933)">
<path d="m58.5449 14.5508c27.4902 0 49.8047-22.3145 49.8047-49.8047s-22.3145-49.8047-49.8047-49.8047-49.8047 22.3145-49.8047 49.8047 22.3145 49.8047 49.8047 49.8047Zm0-8.30078c-22.9492 0-41.5039-18.5547-41.5039-41.5039s18.5547-41.5039 41.5039-41.5039 41.5039 18.5547 41.5039 41.5039-18.5547 41.5039-41.5039 41.5039Zm-22.6562-41.5039c0 2.39258 1.66016 4.00391 4.15039 4.00391h14.3555v14.4043c0 2.44141 1.66016 4.15039 4.05273 4.15039 2.44141 0 4.15039-1.66016 4.15039-4.15039v-14.4043h14.4043c2.44141 0 4.15039-1.61133 4.15039-4.00391 0-2.44141-1.70898-4.15039-4.15039-4.15039h-14.4043v-14.3555c0-2.49023-1.70898-4.19922-4.15039-4.19922-2.39258 0-4.05273 1.70898-4.05273 4.19922v14.3555h-14.3555c-2.49023 0-4.15039 1.70898-4.15039 4.15039Z"/>
</g>
<g transform="matrix(0.2 0 0 0.2 304.924 1933)">
<path d="m74.8535 28.3203c35.1074 0 63.623-28.4668 63.623-63.5742s-28.5156-63.623-63.623-63.623-63.5742 28.5156-63.5742 63.623 28.4668 63.5742 63.5742 63.5742Zm0-9.08203c-30.127 0-54.4922-24.3652-54.4922-54.4922s24.3652-54.4922 54.4922-54.4922 54.4922 24.3652 54.4922 54.4922-24.3652 54.4922-54.4922 54.4922Zm-28.8574-54.4922c0 2.58789 1.85547 4.39453 4.58984 4.39453h19.7266v19.7754c0 2.68555 1.85547 4.58984 4.44336 4.58984 2.68555 0 4.54102-1.85547 4.54102-4.58984v-19.7754h19.7754c2.68555 0 4.58984-1.80664 4.58984-4.39453 0-2.73438-1.85547-4.58984-4.58984-4.58984h-19.7754v-19.7266c0-2.73438-1.85547-4.63867-4.54102-4.63867-2.58789 0-4.44336 1.9043-4.44336 4.63867v19.7266h-19.7266c-2.73438 0-4.58984 1.85547-4.58984 4.58984Z"/>
</g>
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;font-weight:bold;" transform="matrix(1 0 0 1 263 1953)">Design Variations</text>
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;" transform="matrix(1 0 0 1 263 1971)">Symbols are supported in up to nine weights and three scales.</text>
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;" transform="matrix(1 0 0 1 263 1989)">For optimal layout with text and other symbols, vertically align</text>
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;" transform="matrix(1 0 0 1 263 2007)">symbols with the adjacent text.</text>
<line style="fill:none;stroke:#00AEEF;stroke-width:0.5;opacity:1.0;" x1="776" x2="776" y1="1919" y2="1933"/>
<g transform="matrix(0.2 0 0 0.2 776 1933)">
<path d="m16.5527 0.78125c2.58789 0 3.85742-0.976562 4.78516-3.71094l6.29883-17.2363h28.8086l6.29883 17.2363c0.927734 2.73438 2.19727 3.71094 4.73633 3.71094 2.58789 0 4.24805-1.5625 4.24805-4.00391 0-0.830078-0.146484-1.61133-0.537109-2.63672l-22.9004-60.9863c-1.12305-2.97852-3.125-4.49219-6.25-4.49219-3.02734 0-5.07812 1.46484-6.15234 4.44336l-22.9004 61.084c-0.390625 1.02539-0.537109 1.80664-0.537109 2.63672 0 2.44141 1.5625 3.95508 4.10156 3.95508Zm13.4766-28.3691 11.8652-32.8613h0.244141l11.8652 32.8613Z"/>
</g>
<line style="fill:none;stroke:#00AEEF;stroke-width:0.5;opacity:1.0;" x1="792.836" x2="792.836" y1="1919" y2="1933"/>
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;font-weight:bold;" transform="matrix(1 0 0 1 776 1953)">Margins</text>
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;" transform="matrix(1 0 0 1 776 1971)">Leading and trailing margins on the left and right side of each symbol</text>
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;" transform="matrix(1 0 0 1 776 1989)">can be adjusted by modifying the x-location of the margin guidelines.</text>
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;" transform="matrix(1 0 0 1 776 2007)">Modifications are automatically applied proportionally to all</text>
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;" transform="matrix(1 0 0 1 776 2025)">scales and weights.</text>
<g transform="matrix(0.2 0 0 0.2 1289 1933)">
<path d="m14.209 9.32617 8.49609 8.54492c4.29688 4.3457 9.22852 4.05273 13.8672-1.07422l53.4668-58.9355-4.83398-4.88281-53.0762 58.3984c-1.75781 2.00195-3.41797 2.49023-5.76172 0.146484l-5.85938-5.81055c-2.34375-2.29492-1.80664-4.00391 0.195312-5.81055l57.373-54.0039-4.88281-4.83398-57.959 54.4434c-4.93164 4.58984-5.32227 9.47266-1.02539 13.8184Zm32.0801-90.9668c-2.09961 2.05078-2.24609 4.93164-1.07422 6.88477 1.17188 1.80664 3.4668 2.97852 6.68945 2.14844 7.32422-1.70898 14.9414-2.00195 22.0703 2.68555l-2.92969 7.27539c-1.70898 4.15039-0.830078 7.08008 1.85547 9.81445l11.4746 11.5723c2.44141 2.44141 4.49219 2.53906 7.32422 2.05078l5.32227-0.976562 3.32031 3.36914-0.195312 2.7832c-0.195312 2.49023 0.439453 4.39453 2.88086 6.78711l3.80859 3.71094c2.39258 2.39258 5.46875 2.53906 7.8125 0.195312l14.5508-14.5996c2.34375-2.34375 2.24609-5.32227-0.146484-7.71484l-3.85742-3.80859c-2.39258-2.39258-4.24805-3.17383-6.64062-2.97852l-2.88086 0.244141-3.22266-3.17383 1.2207-5.61523c0.634766-2.83203-0.146484-5.0293-3.07617-7.95898l-10.9863-10.9375c-16.6992-16.6016-38.8672-16.2109-53.3203-1.75781Zm7.4707 1.85547c12.1582-8.88672 28.6133-7.37305 39.7461 3.75977l12.1582 12.0605c1.17188 1.17188 1.36719 2.09961 1.02539 3.80859l-1.61133 7.42188 7.51953 7.42188 4.93164-0.292969c1.26953-0.0488281 1.66016 0.0488281 2.63672 1.02539l2.88086 2.88086-12.207 12.207-2.88086-2.88086c-0.976562-0.976562-1.12305-1.36719-1.07422-2.68555l0.341797-4.88281-7.4707-7.42188-7.61719 1.26953c-1.61133 0.341797-2.34375 0.195312-3.56445-0.976562l-10.0098-10.0098c-1.26953-1.17188-1.41602-2.00195-0.634766-3.85742l4.39453-10.4492c-7.8125-7.27539-17.9688-10.4004-28.125-7.42188-0.78125 0.195312-1.07422-0.439453-0.439453-0.976562Z"/>
</g>
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;font-weight:bold;" transform="matrix(1 0 0 1 1289 1953)">Exporting</text>
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;" transform="matrix(1 0 0 1 1289 1971)">Symbols should be outlined when exporting to ensure the</text>
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;" transform="matrix(1 0 0 1 1289 1989)">design is preserved when submitting to Xcode.</text>
<text id="template-version" style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:end;" transform="matrix(1 0 0 1 3036 1933)">Template v.6.0</text>
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:end;" transform="matrix(1 0 0 1 3036 1951)">Requires Xcode 16 or greater</text>
<text id="descriptive-name" style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:end;" transform="matrix(1 0 0 1 3036 1969)">Generated from progress.ring.dashed</text>
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:end;" transform="matrix(1 0 0 1 3036 1987)">Typeset at 100.0 points</text>
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;" transform="matrix(1 0 0 1 263 726)">Small</text>
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;" transform="matrix(1 0 0 1 263 1156)">Medium</text>
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;" transform="matrix(1 0 0 1 263 1586)">Large</text>
</g>
<g id="Guides">
<g id="H-reference" style="fill:#27AAE1;stroke:none;" transform="matrix(1 0 0 1 339 696)">
<path d="M0.993654 0L3.63775 0L29.3281-67.1323L30.0303-67.1323L30.0303-70.459L28.1226-70.459ZM11.6885-24.4799L46.9815-24.4799L46.2315-26.7285L12.4385-26.7285ZM55.1196 0L57.7637 0L30.6382-70.459L29.4326-70.459L29.4326-67.1323Z"/>
</g>
<line id="Baseline-S" style="fill:none;stroke:#27AAE1;opacity:1;stroke-width:0.5;" x1="263" x2="3036" y1="696" y2="696"/>
<line id="Capline-S" style="fill:none;stroke:#27AAE1;opacity:1;stroke-width:0.5;" x1="263" x2="3036" y1="625.541" y2="625.541"/>
<g id="H-reference" style="fill:#27AAE1;stroke:none;" transform="matrix(1 0 0 1 339 1126)">
<path d="M0.993654 0L3.63775 0L29.3281-67.1323L30.0303-67.1323L30.0303-70.459L28.1226-70.459ZM11.6885-24.4799L46.9815-24.4799L46.2315-26.7285L12.4385-26.7285ZM55.1196 0L57.7637 0L30.6382-70.459L29.4326-70.459L29.4326-67.1323Z"/>
</g>
<line id="Baseline-M" style="fill:none;stroke:#27AAE1;opacity:1;stroke-width:0.5;" x1="263" x2="3036" y1="1126" y2="1126"/>
<line id="Capline-M" style="fill:none;stroke:#27AAE1;opacity:1;stroke-width:0.5;" x1="263" x2="3036" y1="1055.54" y2="1055.54"/>
<g id="H-reference" style="fill:#27AAE1;stroke:none;" transform="matrix(1 0 0 1 339 1556)">
<path d="M0.993654 0L3.63775 0L29.3281-67.1323L30.0303-67.1323L30.0303-70.459L28.1226-70.459ZM11.6885-24.4799L46.9815-24.4799L46.2315-26.7285L12.4385-26.7285ZM55.1196 0L57.7637 0L30.6382-70.459L29.4326-70.459L29.4326-67.1323Z"/>
</g>
<line id="Baseline-L" style="fill:none;stroke:#27AAE1;opacity:1;stroke-width:0.5;" x1="263" x2="3036" y1="1556" y2="1556"/>
<line id="Capline-L" style="fill:none;stroke:#27AAE1;opacity:1;stroke-width:0.5;" x1="263" x2="3036" y1="1485.54" y2="1485.54"/>
<line id="right-margin-Black-S" style="fill:none;stroke:#00AEEF;stroke-width:0.5;opacity:1.0;" x1="2984.21" x2="2984.21" y1="600.785" y2="720.121"/>
<line id="left-margin-Black-S" style="fill:none;stroke:#00AEEF;stroke-width:0.5;opacity:1.0;" x1="2882.59" x2="2882.59" y1="600.785" y2="720.121"/>
<line id="right-margin-Regular-S" style="fill:none;stroke:#00AEEF;stroke-width:0.5;opacity:1.0;" x1="1499.82" x2="1499.82" y1="600.785" y2="720.121"/>
<line id="left-margin-Regular-S" style="fill:none;stroke:#00AEEF;stroke-width:0.5;opacity:1.0;" x1="1399.87" x2="1399.87" y1="600.785" y2="720.121"/>
<line id="right-margin-Ultralight-S" style="fill:none;stroke:#00AEEF;stroke-width:0.5;opacity:1.0;" x1="607.643" x2="607.643" y1="600.785" y2="720.121"/>
<line id="left-margin-Ultralight-S" style="fill:none;stroke:#00AEEF;stroke-width:0.5;opacity:1.0;" x1="511.779" x2="511.779" y1="600.785" y2="720.121"/>
</g>
<g id="Symbols">
<g id="Black-S" transform="matrix(1 0 0 1 2882.59 696)">
<path class="monochrome-0 multicolor-0:tintColor hierarchical-0:primary SFSymbolsPreviewWireframe" d="M40.5762-61.1816C42.5293-61.9141 44.5801-62.4512 46.7285-62.793C47.9492-62.9883 48.8281-63.916 48.8281-65.1855L48.8281-76.9043C48.8281-78.4668 47.6562-79.5898 46.0938-79.4434C41.4062-78.9551 36.9141-77.7344 32.7637-75.8301C31.3477-75.1953 30.9082-73.6328 31.6895-72.3145L37.5488-62.1582C38.1836-61.084 39.4043-60.6934 40.5762-61.1816Z"/>
<path class="monochrome-1 multicolor-1:tintColor hierarchical-1:primary SFSymbolsPreviewWireframe" d="M29.0039-52.5879C30.3223-54.248 31.7871-55.7129 33.4473-57.0312C34.4727-57.8125 34.7656-59.082 34.1309-60.1562L28.2715-70.3125C27.4902-71.6797 25.8789-72.0703 24.6094-71.1426C20.8984-68.4082 17.627-65.1367 14.8926-61.4258C13.9648-60.1074 14.3555-58.5449 15.7227-57.7637L25.8789-51.9043C26.9531-51.2695 28.2227-51.5625 29.0039-52.5879Z"/>
<path class="monochrome-2 multicolor-2:tintColor hierarchical-2:primary SFSymbolsPreviewWireframe" d="M23.291-39.3555C23.584-41.4551 24.1211-43.5059 24.9023-45.4102C25.3906-46.6309 25-47.8516 23.877-48.4863L13.7207-54.3457C12.4023-55.127 10.8398-54.6875 10.2051-53.2715C8.30078-49.1211 7.08008-44.6289 6.5918-39.9414C6.44531-38.3789 7.56836-37.207 9.13086-37.207L20.8984-37.207C22.168-37.207 23.0957-38.0859 23.291-39.3555Z"/>
<path class="monochrome-3 multicolor-3:tintColor hierarchical-3:primary SFSymbolsPreviewWireframe" d="M24.8535-25.0977C24.1211-27.002 23.584-29.0039 23.291-31.1523C23.0957-32.4219 22.168-33.3008 20.8984-33.3008L9.13086-33.3008C7.56836-33.3008 6.44531-32.1289 6.5918-30.5664C7.08008-25.8789 8.30078-21.3867 10.2051-17.2363C10.8398-15.8203 12.4023-15.3809 13.7207-16.1621L23.877-22.0215C25-22.6562 25.3418-23.877 24.8535-25.0977Z"/>
<path class="monochrome-4 multicolor-4:tintColor hierarchical-4:secondary SFSymbolsPreviewWireframe" d="M33.4473-13.4766C31.8359-14.7949 30.3223-16.2598 29.0039-17.9199C28.2227-18.9453 26.9531-19.2383 25.8789-18.6035L15.7227-12.7441C14.3555-11.9629 13.9648-10.4004 14.8926-9.08203C17.627-5.37109 20.8984-2.05078 24.6094 0.634766C25.8789 1.5625 27.4902 1.12305 28.2715-0.195312L34.1309-10.3516C34.7656-11.4258 34.4727-12.6953 33.4473-13.4766Z"/>
<path class="monochrome-5 multicolor-5:tintColor hierarchical-5:tertiary SFSymbolsPreviewWireframe" d="M46.7285-7.71484C44.5801-8.05664 42.5293-8.59375 40.5762-9.32617C39.4043-9.81445 38.1836-9.42383 37.5488-8.34961L31.6895 1.80664C30.9082 3.125 31.3477 4.6875 32.7637 5.32227C36.9141 7.22656 41.4062 8.44727 46.0938 8.93555C47.6562 9.08203 48.8281 7.95898 48.8281 6.39648L48.8281-5.32227C48.8281-6.5918 47.9492-7.51953 46.7285-7.71484Z"/>
<path class="monochrome-6 multicolor-6:tintColor hierarchical-6:primary SFSymbolsPreviewWireframe" d="M61.0352-9.32617C59.082-8.59375 57.0312-8.05664 54.8828-7.71484C53.6133-7.51953 52.7344-6.5918 52.7344-5.32227L52.7344 6.39648C52.7344 7.95898 53.9062 9.08203 55.4688 8.93555C60.1562 8.44727 64.6484 7.22656 68.8477 5.32227C70.2637 4.6875 70.7031 3.07617 69.9219 1.75781L64.0625-8.34961C63.4277-9.42383 62.207-9.81445 61.0352-9.32617Z"/>
<path class="monochrome-7 multicolor-7:tintColor hierarchical-7:primary SFSymbolsPreviewWireframe" d="M72.6074-17.9199C71.2891-16.2598 69.8242-14.7949 68.1641-13.4766C67.1387-12.6953 66.8457-11.4258 67.4805-10.3516L73.3398-0.195312C74.1211 1.17188 75.7324 1.5625 77.002 0.634766C80.7129-2.09961 83.9844-5.37109 86.7188-9.13086C87.5977-10.4004 87.207-11.9629 85.8887-12.7441L75.7324-18.6035C74.6582-19.2383 73.3887-18.9453 72.6074-17.9199Z"/>
<path class="monochrome-8 multicolor-8:tintColor hierarchical-8:primary SFSymbolsPreviewWireframe" d="M78.3203-31.1523C78.0273-29.0039 77.4902-27.002 76.709-25.0488C76.2207-23.877 76.6113-22.6562 77.6855-22.0215L87.8418-16.1621C89.1602-15.3809 90.7715-15.8203 91.4062-17.2852C93.2617-21.3867 94.4824-25.8789 94.9707-30.5664C95.1172-32.1289 93.9453-33.3008 92.4316-33.3008L80.7129-33.3008C79.4434-33.3008 78.5156-32.4219 78.3203-31.1523Z"/>
<path class="monochrome-9 multicolor-9:tintColor hierarchical-9:primary SFSymbolsPreviewWireframe" d="M76.709-45.459C77.4902-43.5059 78.0273-41.5039 78.3203-39.3555C78.5156-38.0859 79.4434-37.207 80.7129-37.207L92.4316-37.207C93.9453-37.207 95.1172-38.3789 94.9707-39.9414C94.5312-44.5801 93.2617-49.0723 91.4062-53.2227C90.7715-54.6875 89.1602-55.127 87.8418-54.3457L77.6855-48.4863C76.6113-47.8516 76.2207-46.6309 76.709-45.459Z"/>
<path class="monochrome-10 multicolor-10:tintColor hierarchical-10:primary SFSymbolsPreviewWireframe" d="M68.1641-57.0312C69.7754-55.7129 71.2891-54.248 72.6074-52.5879C73.3887-51.5625 74.6582-51.2695 75.7324-51.9043L85.8887-57.7637C87.207-58.5449 87.5977-60.1074 86.7188-61.377C83.9844-65.1367 80.7129-68.457 77.002-71.1426C75.7324-72.0703 74.1211-71.6309 73.3398-70.3125L67.4805-60.1562C66.8457-59.082 67.1387-57.8125 68.1641-57.0312Z"/>
<path class="monochrome-11 multicolor-11:tintColor hierarchical-11:primary SFSymbolsPreviewWireframe" d="M54.834-62.793C57.0312-62.4512 59.082-61.9141 61.0352-61.1816C62.207-60.6934 63.4277-61.084 64.0625-62.1582L69.9219-72.3145C70.7031-73.6328 70.2637-75.1953 68.8477-75.8301C64.6973-77.7344 60.2051-78.9551 55.4688-79.4434C53.9062-79.5898 52.7344-78.4668 52.7344-76.9043L52.7344-65.1855C52.7344-63.916 53.6133-62.9883 54.834-62.793Z"/>
</g>
<g id="Regular-S" transform="matrix(1 0 0 1 1399.87 696)">
<path class="monochrome-0 multicolor-0:tintColor hierarchical-0:primary SFSymbolsPreviewWireframe" d="M38.623-61.4746C41.1621-62.5977 43.8477-63.3301 46.7285-63.6719C47.5098-63.7207 48.0469-64.3066 48.0469-65.0879L48.0469-76.709C48.0469-77.6367 47.3633-78.3691 46.4355-78.2715C41.1621-77.832 36.1816-76.3672 31.5918-74.2188C30.7617-73.8281 30.5176-72.8516 30.957-72.0703L36.6699-62.1094C37.1094-61.4258 37.8906-61.1816 38.623-61.4746Z"/>
<path class="monochrome-1 multicolor-1:tintColor hierarchical-1:primary SFSymbolsPreviewWireframe" d="M27.0508-52.2949C28.7109-54.541 30.6641-56.543 32.959-58.2031C33.5938-58.6914 33.7402-59.4727 33.3496-60.1562L27.5879-70.1172C27.1484-70.8984 26.1719-71.1426 25.3906-70.6055C21.1914-67.627 17.5781-64.0137 14.6484-59.8145C14.1113-59.0332 14.3066-58.0566 15.1367-57.5684L25.1465-51.8555C25.8301-51.4648 26.5625-51.6113 27.0508-52.2949Z"/>
<path class="monochrome-2 multicolor-2:tintColor hierarchical-2:primary SFSymbolsPreviewWireframe" d="M21.582-38.5254C21.9238-41.3574 22.6562-44.0918 23.7305-46.582C24.0723-47.3145 23.8281-48.0957 23.1445-48.4863L13.1348-54.248C12.3047-54.7363 11.377-54.4434 10.9863-53.6133C8.83789-49.0234 7.42188-44.0918 6.98242-38.8184C6.93359-37.8906 7.56836-37.207 8.54492-37.207L20.166-37.207C20.9473-37.207 21.5332-37.7441 21.582-38.5254Z"/>
<path class="monochrome-3 multicolor-3:tintColor hierarchical-3:primary SFSymbolsPreviewWireframe" d="M23.7305-23.877C22.6562-26.3672 21.9238-29.1016 21.582-31.9824C21.5332-32.7637 20.9473-33.3008 20.166-33.3008L8.49609-33.3008C7.56836-33.3008 6.88477-32.6172 6.98242-31.6895C7.42188-26.3672 8.83789-21.3867 11.0352-16.7969C11.4258-15.9668 12.3047-15.6738 13.1348-16.1621L23.1445-21.9727C23.8281-22.3633 24.0723-23.1445 23.7305-23.877Z"/>
<path class="monochrome-4 multicolor-4:tintColor hierarchical-4:secondary SFSymbolsPreviewWireframe" d="M32.959-12.2559C30.7129-13.916 28.7109-15.8691 27.0508-18.1641C26.5625-18.7988 25.7812-18.9941 25.0977-18.5547L15.1367-12.8906C14.3066-12.4023 14.1113-11.4258 14.5996-10.6445C17.5781-6.44531 21.1914-2.83203 25.4395 0.146484C26.2207 0.683594 27.1973 0.439453 27.6367-0.341797L33.3496-10.3516C33.7402-11.0352 33.5938-11.7676 32.959-12.2559Z"/>
<path class="monochrome-5 multicolor-5:tintColor hierarchical-5:tertiary SFSymbolsPreviewWireframe" d="M46.7285-6.78711C43.8965-7.12891 41.1621-7.86133 38.6719-8.93555C37.9395-9.27734 37.1582-9.0332 36.7188-8.34961L31.0059 1.61133C30.5664 2.39258 30.8105 3.36914 31.6406 3.75977C36.1816 5.9082 41.1621 7.37305 46.4355 7.8125C47.3633 7.86133 48.0469 7.17773 48.0469 6.25L48.0469-5.37109C48.0469-6.15234 47.5098-6.73828 46.7285-6.78711Z"/>
<path class="monochrome-6 multicolor-6:tintColor hierarchical-6:primary SFSymbolsPreviewWireframe" d="M61.377-8.93555C58.8867-7.86133 56.1523-7.12891 53.2715-6.78711C52.4414-6.73828 51.9043-6.15234 51.9043-5.37109L51.9043 6.25C51.9043 7.22656 52.5879 7.91016 53.5645 7.8125C58.7891 7.37305 63.8672 5.9082 68.4082 3.75977C69.2383 3.36914 69.4824 2.39258 69.043 1.61133L63.2812-8.34961C62.8906-9.0332 62.1094-9.27734 61.377-8.93555Z"/>
<path class="monochrome-7 multicolor-7:tintColor hierarchical-7:primary SFSymbolsPreviewWireframe" d="M72.998-18.1641C71.3379-15.918 69.2871-13.9648 67.041-12.2559C66.4062-11.7676 66.2109-10.9863 66.6016-10.3516L72.3633-0.390625C72.8027 0.390625 73.7793 0.683594 74.5605 0.146484C78.8086-2.83203 82.4219-6.49414 85.3516-10.6445C85.8887-11.4258 85.6445-12.4023 84.8633-12.8906L74.9023-18.6035C74.2676-19.043 73.4863-18.7988 72.998-18.1641Z"/>
<path class="monochrome-8 multicolor-8:tintColor hierarchical-8:primary SFSymbolsPreviewWireframe" d="M78.418-31.9824C78.0762-29.1016 77.3438-26.3672 76.2207-23.877C75.9277-23.1445 76.1719-22.4121 76.8555-22.0215L86.8164-16.2598C87.6465-15.7715 88.623-16.0156 89.0137-16.8945C91.1621-21.4844 92.5293-26.416 92.9688-31.6895C93.0176-32.6172 92.3828-33.3008 91.4551-33.3008L79.8828-33.3008C79.1016-33.3008 78.4668-32.7637 78.418-31.9824Z"/>
<path class="monochrome-9 multicolor-9:tintColor hierarchical-9:primary SFSymbolsPreviewWireframe" d="M76.2207-46.6309C77.2949-44.0918 78.0762-41.3574 78.418-38.5254C78.4668-37.7441 79.1016-37.207 79.8828-37.207L91.4551-37.207C92.3828-37.207 93.0176-37.8906 92.9688-38.8184C92.5293-44.043 91.1621-49.0723 89.0137-53.6133C88.623-54.4434 87.5977-54.7363 86.8164-54.248L76.8066-48.4375C76.1719-48.0469 75.9277-47.3145 76.2207-46.6309Z"/>
<path class="monochrome-10 multicolor-10:tintColor hierarchical-10:primary SFSymbolsPreviewWireframe" d="M67.041-58.2031C69.2871-56.543 71.2891-54.5898 72.998-52.2949C73.4863-51.6602 74.2676-51.416 74.9023-51.8555L84.8633-57.5684C85.6934-58.0566 85.8887-59.0332 85.4004-59.8145C82.4219-64.0137 78.7598-67.6758 74.5605-70.6055C73.7793-71.1426 72.8027-70.8984 72.3633-70.1172L66.6016-60.1562C66.2109-59.4727 66.3574-58.6914 67.041-58.2031Z"/>
<path class="monochrome-11 multicolor-11:tintColor hierarchical-11:primary SFSymbolsPreviewWireframe" d="M53.2715-63.6719C56.1523-63.3789 58.8379-62.5977 61.377-61.4746C62.1094-61.1816 62.8906-61.4258 63.2812-62.1094L69.043-72.0703C69.4824-72.8516 69.1895-73.8281 68.3594-74.2188C63.7695-76.3672 58.8379-77.832 53.5645-78.2715C52.6367-78.3203 51.9043-77.6367 51.9043-76.709L51.9043-65.0879C51.9043-64.3066 52.4414-63.7207 53.2715-63.6719Z"/>
</g>
<g id="Ultralight-S" transform="matrix(1 0 0 1 511.779 696)">
<path class="monochrome-0 multicolor-0:tintColor hierarchical-0:primary SFSymbolsPreviewWireframe" d="M36.5342-61.1113C39.1187-62.2344 41.895-63.0122 44.7759-63.354C45.6934-63.4482 46.3213-64.1704 46.3213-65.0425L46.3213-74.4839C46.3213-75.5479 45.5469-76.3257 44.4829-76.228C39.4365-75.834 34.6831-74.5054 30.3657-72.4024C29.3994-71.9663 29.1099-70.8989 29.6401-69.9815L34.354-61.7915C34.7935-61.0171 35.6655-60.773 36.5342-61.1113Z"/>
<path class="monochrome-1 multicolor-1:tintColor hierarchical-1:primary SFSymbolsPreviewWireframe" d="M25.0982-51.9771C26.8037-54.2686 28.8477-56.3159 31.1426-58.0669C31.9136-58.6006 32.1055-59.5181 31.6694-60.2925L26.9522-68.437C26.4219-69.3545 25.3545-69.5986 24.4824-69.0161C20.4194-66.2192 16.9424-62.6514 14.1489-58.6338C13.5664-57.7617 13.8071-56.6943 14.728-56.1607L22.876-51.4468C23.6504-50.9653 24.5645-51.2026 25.0982-51.9771Z"/>
<path class="monochrome-2 multicolor-2:tintColor hierarchical-2:primary SFSymbolsPreviewWireframe" d="M19.7656-38.3438C20.062-41.2666 20.8399-44.001 22.0049-46.582C22.3467-47.4053 22.1026-48.2773 21.2827-48.7588L13.1348-53.4761C12.2139-54.0098 11.1499-53.6714 10.7139-52.7051C8.70166-48.3877 7.37647-43.6831 6.93701-38.6821C6.88818-37.6182 7.65918-36.7983 8.72656-36.7983L18.1226-36.7983C19.0401-36.7983 19.6714-37.4263 19.7656-38.3438Z"/>
<path class="monochrome-3 multicolor-3:tintColor hierarchical-3:primary SFSymbolsPreviewWireframe" d="M22.0049-23.8315C20.8853-26.4126 20.1528-29.1924 19.811-32.1187C19.7168-33.0361 18.9946-33.6641 18.1226-33.6641L8.67773-33.6641C7.61377-33.6641 6.83936-32.8896 6.93701-31.8257C7.33106-26.7759 8.65625-22.0225 10.7173-17.6597C11.1533-16.6934 12.2593-16.355 13.1802-16.8887L21.3281-21.6548C22.148-22.1362 22.3921-23.0083 22.0049-23.8315Z"/>
<path class="monochrome-4 multicolor-4:tintColor hierarchical-4:secondary SFSymbolsPreviewWireframe" d="M31.188-12.3921C28.8965-14.0977 26.8491-16.1416 25.1436-18.4365C24.6099-19.1621 23.7378-19.4028 22.918-18.918L14.7734-14.2529C13.8525-13.7192 13.5664-12.6518 14.1455-11.7798C16.9424-7.7622 20.4648-4.28514 24.4858-1.44286C25.3579-0.81493 26.4253-1.05907 26.9556-2.02196L31.6694-10.1699C32.1055-10.9443 31.9136-11.8584 31.188-12.3921Z"/>
<path class="monochrome-5 multicolor-5:tintColor hierarchical-5:tertiary SFSymbolsPreviewWireframe" d="M44.7759-7.10498C41.8985-7.40137 39.1641-8.1792 36.583-9.29883C35.7598-9.68603 34.8423-9.39648 34.4029-8.62207L29.689-0.432113C29.1587 0.485367 29.4483 1.55275 30.4146 1.98878C34.6831 4.0464 39.4365 5.37502 44.4375 5.81447C45.5015 5.8633 46.3213 5.08888 46.3213 4.02492L46.3213-5.4165C46.3213-6.28857 45.6934-7.01074 44.7759-7.10498Z"/>
<path class="monochrome-6 multicolor-6:tintColor hierarchical-6:primary SFSymbolsPreviewWireframe" d="M59.2881-9.34424C56.7525-8.22461 53.9727-7.44677 51.0464-7.10498C50.1255-7.01074 49.4976-6.28857 49.4976-5.4165L49.4976 4.07033C49.4976 5.13771 50.272 5.91213 51.3394 5.81447C56.3823 5.42043 61.1426 4.09181 65.502 1.98878C66.4683 1.55275 66.7578 0.485367 66.2276-0.432113L61.4649-8.62207C61.0288-9.39648 60.1113-9.68603 59.2881-9.34424Z"/>
<path class="monochrome-7 multicolor-7:tintColor hierarchical-7:primary SFSymbolsPreviewWireframe" d="M70.7276-18.4819C69.022-16.1904 66.9712-14.1465 64.6343-12.3921C63.9087-11.8584 63.7134-10.9409 64.1494-10.1699L68.9121-2.02538C69.4424-1.06249 70.5098-0.81493 71.3819-1.44286C75.4483-4.23974 78.9253-7.76561 81.7188-11.7798C82.3013-12.6518 82.0572-13.7192 81.1397-14.2529L72.9497-18.9668C72.1333-19.4517 71.2612-19.2075 70.7276-18.4819Z"/>
<path class="monochrome-8 multicolor-8:tintColor hierarchical-8:primary SFSymbolsPreviewWireframe" d="M76.0567-32.1187C75.7603-29.1924 74.9824-26.4126 73.814-23.8315C73.4756-23.0083 73.7197-22.1397 74.5396-21.6582L82.6841-16.9409C83.605-16.4072 84.6724-16.6968 85.1084-17.6665C87.166-21.9839 88.4878-26.7339 88.9273-31.7803C88.9761-32.8442 88.2051-33.6641 87.1411-33.6641L77.7031-33.6641C76.7857-33.6641 76.1509-33.0361 76.0567-32.1187Z"/>
<path class="monochrome-9 multicolor-9:tintColor hierarchical-9:primary SFSymbolsPreviewWireframe" d="M73.814-46.5854C74.9336-44.001 75.7149-41.2212 76.0112-38.3438C76.1509-37.4263 76.7857-36.7983 77.7031-36.7983L87.1866-36.7983C88.2505-36.7983 89.0215-37.5728 88.9273-38.6367C88.5332-43.6797 87.2115-48.4365 85.1084-52.7959C84.6724-53.7622 83.6016-54.0552 82.6841-53.5215L74.4907-48.7554C73.6743-48.2739 73.4756-47.4507 73.814-46.5854Z"/>
<path class="monochrome-10 multicolor-10:tintColor hierarchical-10:primary SFSymbolsPreviewWireframe" d="M64.6797-58.0215C66.9712-56.3159 69.0186-54.272 70.7276-51.9771C71.2612-51.2515 72.1333-51.0073 72.9497-51.4922L81.0943-56.1607C82.0152-56.6943 82.3013-57.7617 81.7222-58.6338C78.9707-62.6514 75.4449-66.1318 71.4273-68.9707C70.5552-69.5986 69.4424-69.3545 68.9121-68.3916L64.1948-60.2471C63.7588-59.4727 63.9507-58.5552 64.6797-58.0215Z"/>
<path class="monochrome-11 multicolor-11:tintColor hierarchical-11:primary SFSymbolsPreviewWireframe" d="M51.0464-63.354C53.9727-63.061 56.7036-62.2798 59.2881-61.1113C60.1113-60.773 61.0288-61.0625 61.4649-61.8369L66.2276-69.9815C66.7578-70.8989 66.4195-71.9663 65.4531-72.4024C61.1358-74.46 56.3858-75.7886 51.3848-76.228C50.3208-76.2769 49.4976-75.5025 49.4976-74.4385L49.4976-65.0425C49.4976-64.1704 50.1255-63.4482 51.0464-63.354Z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 33 KiB

View file

@ -37,17 +37,17 @@ enum ActivityType: Int, CaseIterable, Identifiable {
var fileNameString: String {
switch self {
case .walking:
return "walk".localized
return "Walking".localized.lowercased()
case .hiking:
return "hiking".localized
return "Hiking".localized.lowercased()
case .biking:
return "biking".localized
return "Biking".localized.lowercased()
case .driving:
return "driving".localized
return "Driving".localized.lowercased()
case .overlanding:
return "overlanding".localized
return "Overlanding".localized.lowercased()
case .skiing:
return "skiing".localized
return "Skiing".localized.lowercased()
}
}
}

View file

@ -31,4 +31,11 @@ extension MessageEntity {
return (try? context.fetch(fetchRequest)) ?? [MessageEntity]()
}
func displayTimestamp(aboveMessage: MessageEntity?) -> Bool {
if let aboveMessage = aboveMessage {
return aboveMessage.timestamp.addingTimeInterval(3600) < timestamp // 60 minutes
}
return false // First message will have no timestamp
}
}

View file

@ -0,0 +1,30 @@
// NodeInfoEntityToNodeInfo.swift
// Meshtastic
//
// Utility to convert NodeInfoEntity (Core Data) to NodeInfo (protobuf)
import Foundation
import MeshtasticProtobufs
extension NodeInfoEntity {
func toProto() -> NodeInfo {
var userProto = User()
if let user = self.user {
userProto.id = user.userId ?? ""
userProto.longName = user.longName ?? ""
userProto.shortName = user.shortName ?? ""
userProto.hwModel = HardwareModel(rawValue: Int(user.hwModelId)) ?? .unset
userProto.isLicensed = user.isLicensed
if userProto.hasIsUnmessagable == true {
userProto.isUnmessagable = user.unmessagable
}
userProto.role = Config.DeviceConfig.Role(rawValue: Int(user.role)) ?? .client
userProto.publicKey = user.publicKey?.subdata(in: 0..<user.publicKey!.count) ?? Data()
}
var node = NodeInfo()
node.num = UInt32(self.num)
node.user = userProto
// Add more fields as needed
return node
}
}

View file

@ -115,6 +115,17 @@ extension String {
.joined()
}
/// Formats a short name like "P130" to read as "Node P 130" for VoiceOver
/// This ensures proper pronunciation of alphanumeric node IDs
func formatNodeNameForVoiceOver() -> String {
let spaced = self.replacingOccurrences(
of: #"([A-Za-z])([0-9]+)"#,
with: "$1 $2",
options: .regularExpression
)
return "Node".localized + " " + spaced
}
// Adds variation selectors to prefer the graphical form of emoji.
// Looks ahead to make sure that the variation selector is not already applied.
var addingVariationSelectors: String {

View file

@ -57,6 +57,7 @@ extension UserDefaults {
case enableMapTraffic
case enableMapPointsOfInterest
case enableOfflineMaps
case enableMapShowFavorites
case mapTileServer
case enableOverlayServer
case mapOverlayServer
@ -119,6 +120,9 @@ extension UserDefaults {
@UserDefault(.enableMapPointsOfInterest, defaultValue: false)
static var enableMapPointsOfInterest: Bool
@UserDefault(.enableMapShowFavorites, defaultValue: false)
static var enableMapShowFavorites: Bool
@UserDefault(.enableDetectionNotifications, defaultValue: false)
static var enableDetectionNotifications: Bool

View file

@ -177,20 +177,23 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
// Disconnect Connected Peripheral
func disconnectPeripheral(reconnect: Bool = true) {
guard let connectedPeripheral = connectedPeripheral else { return }
if mqttProxyConnected {
mqttManager.mqttClientProxy?.disconnect()
// Ensure all operations run on the main thread
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
guard let connectedPeripheral = self.connectedPeripheral else { return }
if self.mqttProxyConnected {
self.mqttManager.mqttClientProxy?.disconnect()
}
self.automaticallyReconnect = reconnect
self.centralManager?.cancelPeripheralConnection(connectedPeripheral.peripheral)
self.FROMRADIO_characteristic = nil
self.isConnected = false
self.isSubscribed = false
self.invalidVersion = false
self.connectedVersion = "0.0.0"
self.stopScanning()
self.startScanning()
}
automaticallyReconnect = reconnect
centralManager?.cancelPeripheralConnection(connectedPeripheral.peripheral)
FROMRADIO_characteristic = nil
isConnected = false
isSubscribed = false
invalidVersion = false
connectedVersion = "0.0.0"
stopScanning()
startScanning()
}
// Called each time a peripheral is connected
@ -940,7 +943,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
subtitle: "TR received back from \(destinationHop.name ?? "unknown")",
content: "Hops from: \(tr.hopsTowards), Hops back: \(tr.hopsBack)\n\(tr.routeText ?? "Unknown".localized)\n\(tr.routeBackText ?? "Unknown".localized)",
target: "nodes",
path: "meshtastic:///nodes?nodenum=\(connectedNode.user?.num ?? 0)"
path: "meshtastic:///nodes?nodenum=\(tr.node?.num ?? 0)"
)
]
manager.schedule()
@ -1120,6 +1123,19 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
if newMessage.toUser?.pkiEncrypted ?? false {
meshPacket.pkiEncrypted = true
meshPacket.publicKey = newMessage.toUser?.publicKey ?? Data()
// Auto Favorite nodes you DM so they don't roll out of the nodedb
if !(newMessage.toUser?.userNode?.favorite ?? true) {
newMessage.toUser?.userNode?.favorite = true
do {
try context.save()
Logger.data.info("💾 Auto favorited node bases on sending a message \(self.connectedPeripheral.num.toHex(), privacy: .public) to \(toUserNum.toHex(), privacy: .public)")
_ = self.setFavoriteNode(node: (newMessage.toUser?.userNode)!, connectedNodeNum: fromUserNum)
} catch {
context.rollback()
let nsError = error as NSError
Logger.data.error("Unresolved Core Data error when auto favoriting in Send Message Function. Error: \(nsError, privacy: .public)")
}
}
}
meshPacket.id = UInt32(newMessage.messageId)
if toUserNum > 0 {
@ -1757,6 +1773,70 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
return false
}
public func addContactFromURL(base64UrlString: String) -> Bool {
if isConnected {
let decodedString = base64UrlString.base64urlToBase64()
if let decodedData = Data(base64Encoded: decodedString) {
do {
let contact: SharedContact = try SharedContact(serializedBytes: decodedData)
var adminPacket = AdminMessage()
adminPacket.addContact = contact
var meshPacket: MeshPacket = MeshPacket()
meshPacket.to = UInt32(connectedPeripheral.num)
meshPacket.from = UInt32(connectedPeripheral.num)
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 {
return false
}
dataMessage.payload = adminData
dataMessage.portnum = PortNum.adminApp
meshPacket.decoded = dataMessage
var toRadio: ToRadio!
toRadio = ToRadio()
toRadio.packet = meshPacket
guard let binaryData: Data = try? toRadio.serializedData() else {
return false
}
// Create a NodeInfo (User) packet for the newly added contact
var dataNodeMessage = DataMessage()
if let nodeInfoData = try? contact.user.serializedData() {
dataNodeMessage.payload = nodeInfoData
dataNodeMessage.portnum = PortNum.nodeinfoApp
var nodeMeshPacket = MeshPacket()
nodeMeshPacket.id = UInt32.random(in: UInt32(UInt8.max)..<UInt32.max)
nodeMeshPacket.to = UInt32.max
nodeMeshPacket.from = UInt32(contact.nodeNum)
nodeMeshPacket.decoded = dataNodeMessage
// Update local database with the new node info
upsertNodeInfoPacket(packet: nodeMeshPacket, context: context)
}
if connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected {
self.connectedPeripheral.peripheral.writeValue(binaryData, for: self.TORADIO_characteristic, type: .withResponse)
let logString = String.localizedStringWithFormat("Added contact %@ to device".localized, contact.user.longName)
Logger.mesh.info("📻 \(logString, privacy: .public)")
}
if self.connectedPeripheral != nil {
self.sendWantConfig()
return true
}
} catch {
Logger.data.error("Failed to decode contact data: \(error.localizedDescription, privacy: .public)")
return false
}
}
}
return false
}
public func saveUser(config: User, fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Int64 {
var adminPacket = AdminMessage()
adminPacket.setOwner = config

View file

@ -70,8 +70,8 @@ class LocalNotificationManager {
if notification.critical {
content.sound = UNNotificationSound.defaultCritical
}
let request = UNNotificationRequest(identifier: notification.id, content: content, trigger: nil)
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false)
let request = UNNotificationRequest(identifier: notification.id, content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request) { error in
if let error {

View file

@ -109,15 +109,42 @@ import OSLog
} else {
locationsArray = [location]
}
UserDefaults.standard.set(location.coordinate.latitude, forKey: "lastKnownLatitude")
UserDefaults.standard.set(location.coordinate.longitude, forKey: "lastKnownLongitude")
UserDefaults.standard.set(Date().timeIntervalSince1970, forKey: "lastKnownLocationTimestamp")
return true
}
static let DefaultLocation = CLLocationCoordinate2D(latitude: 37.3346, longitude: -122.0090)
static var currentLocation: CLLocationCoordinate2D {
guard let location = shared.manager.location else {
if let location = shared.manager.location {
return location.coordinate
} else {
// Check authorization status
let status = shared.manager.authorizationStatus
switch status {
case .notDetermined:
Logger.services.info("📍 [App] Location permission not determined, requesting authorization")
shared.manager.requestWhenInUseAuthorization()
case .denied, .restricted:
Logger.services.warning("📍 [App] Location access denied or restricted. Please enable location services in Settings to get accurate positioning!")
shared.manager.requestWhenInUseAuthorization()
default:
break
}
// Fallback 1: Last known location from UserDefaults (if within 4 hours)
if let lat = UserDefaults.standard.object(forKey: "lastKnownLatitude") as? Double,
let lon = UserDefaults.standard.object(forKey: "lastKnownLongitude") as? Double,
let timestamp = UserDefaults.standard.object(forKey: "lastKnownLocationTimestamp") as? Double,
lat >= -90 && lat <= 90,
lon >= -180 && lon <= 180,
Date().timeIntervalSince1970 - timestamp <= 14_400 { // 4 hours in seconds
Logger.services.info("📍 [App] Falling back to last known location (age: \(Int(Date().timeIntervalSince1970 - timestamp)) seconds)")
return CLLocationCoordinate2D(latitude: lat, longitude: lon)
}
// Fallback 2: Default location
Logger.services.warning("📍 [App] No Location and no last known location, something is really wrong. Teleporting user to Apple Park")
return DefaultLocation
}
return location.coordinate
}
static var satsInView: Int {

View file

@ -317,6 +317,17 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje
newUser.pkiEncrypted = true
newUser.publicKey = nodeInfo.user.publicKey
}
/// For nodes that have the optional isUnmessagable boolean use that, otherwise excluded roles that are unmessagable by default
if nodeInfo.user.hasIsUnmessagable {
newUser.unmessagable = nodeInfo.user.isUnmessagable
} else {
let roles = [2, 4, 5, 6, 7, 10, 11]
let containsRole = roles.contains(Int(newUser.role))
if containsRole {
newUser.unmessagable = true
} else {
newUser.unmessagable = false
}}
newNode.user = newUser
} else if nodeInfo.num > Constants.minimumNodeNum {
let newUser = createUser(num: Int64(nodeInfo.num), context: context)
@ -380,19 +391,31 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje
fetchedNode[0].user?.pkiEncrypted = true
fetchedNode[0].user?.publicKey = nodeInfo.user.publicKey
}
fetchedNode[0].user!.userId = nodeInfo.user.id
fetchedNode[0].user!.num = Int64(nodeInfo.num)
fetchedNode[0].user!.numString = String(nodeInfo.num)
fetchedNode[0].user!.longName = nodeInfo.user.longName
fetchedNode[0].user!.shortName = nodeInfo.user.shortName
fetchedNode[0].user!.isLicensed = nodeInfo.user.isLicensed
fetchedNode[0].user!.role = Int32(nodeInfo.user.role.rawValue)
fetchedNode[0].user!.hwModel = String(describing: nodeInfo.user.hwModel).uppercased()
fetchedNode[0].user!.hwModelId = Int32(nodeInfo.user.hwModel.rawValue)
fetchedNode[0].user?.userId = nodeInfo.user.id
fetchedNode[0].user?.num = Int64(nodeInfo.num)
fetchedNode[0].user?.numString = String(nodeInfo.num)
fetchedNode[0].user?.longName = nodeInfo.user.longName
fetchedNode[0].user?.shortName = nodeInfo.user.shortName
fetchedNode[0].user?.isLicensed = nodeInfo.user.isLicensed
fetchedNode[0].user?.role = Int32(nodeInfo.user.role.rawValue)
fetchedNode[0].user?.hwModel = String(describing: nodeInfo.user.hwModel).uppercased()
fetchedNode[0].user?.hwModelId = Int32(nodeInfo.user.hwModel.rawValue)
/// For nodes that have the optional isUnmessagable boolean use that, otherwise excluded roles that are unmessagable by default
if nodeInfo.user.hasIsUnmessagable {
fetchedNode[0].user?.unmessagable = nodeInfo.user.isUnmessagable
} else {
let roles = [-1, 2, 4, 5, 6, 7, 10, 11]
let containsRole = roles.contains(Int(fetchedNode[0].user?.role ?? -1))
if containsRole {
fetchedNode[0].user?.unmessagable = true
} else {
fetchedNode[0].user?.unmessagable = false
}
}
Task {
Api().loadDeviceHardwareData { (hw) in
let dh = hw.first(where: { $0.hwModel == fetchedNode[0].user!.hwModelId })
fetchedNode[0].user!.hwDisplayName = dh?.displayName
fetchedNode[0].user?.hwDisplayName = dh?.displayName
}
}
} else {
@ -1040,17 +1063,29 @@ func waypointPacket (packet: MeshPacket, context: NSManagedObjectContext) {
let logString = String.localizedStringWithFormat("Waypoint Packet received from node: %@".localized, String(packet.from))
Logger.mesh.info("📍 \(logString, privacy: .public)")
let fetchWaypointRequest = WaypointEntity.fetchRequest()
fetchWaypointRequest.predicate = NSPredicate(format: "id == %lld", Int64(packet.id))
do {
if let waypointMessage = try? Waypoint(serializedBytes: packet.decoded.payload) {
let fetchedWaypoint = try context.fetch(fetchWaypointRequest)
if fetchedWaypoint.isEmpty {
let waypoint = WaypointEntity(context: context)
// Fetch waypoint by waypointMessage.id, not packet.id
let fetchWaypointRequest = WaypointEntity.fetchRequest()
fetchWaypointRequest.predicate = NSPredicate(format: "id == %lld", Int64(waypointMessage.id))
waypoint.id = Int64(packet.id)
let fetchedWaypoint = try context.fetch(fetchWaypointRequest)
// Fetch the node info to get the short name
var nodeShortName: String = "?"
let fetchNodeRequest = NodeInfoEntity.fetchRequest()
fetchNodeRequest.predicate = NSPredicate(format: "num == %lld", Int64(packet.from))
do {
let fetchedNode = try context.fetch(fetchNodeRequest)
if let node = fetchedNode.first, let user = node.user {
nodeShortName = user.shortName ?? node.user?.userId ?? String(packet.from.toHex())
}
} catch {
Logger.data.error("Failed to fetch NodeInfoEntity for node \(packet.from.toHex(), privacy: .public): \(error)")
}
if fetchedWaypoint.isEmpty {
// Create a new waypoint
let waypoint = WaypointEntity(context: context)
waypoint.id = Int64(waypointMessage.id) // Use waypointMessage.id
waypoint.name = waypointMessage.name
waypoint.longDescription = waypointMessage.description_p
waypoint.latitudeI = waypointMessage.latitudeI
@ -1073,7 +1108,7 @@ func waypointPacket (packet: MeshPacket, context: NSManagedObjectContext) {
manager.notifications = [
Notification(
id: ("notification.id.\(waypoint.id)"),
title: "New Waypoint Received",
title: "New Waypoint From \(nodeShortName)",
subtitle: "\(icon) \(waypoint.name ?? "Dropped Pin")",
content: "\(waypoint.longDescription ?? "\(latitude), \(longitude)")",
target: "map",
@ -1088,26 +1123,42 @@ func waypointPacket (packet: MeshPacket, context: NSManagedObjectContext) {
Logger.data.error("Error Saving WaypointEntity from WAYPOINT_APP \(nsError, privacy: .public)")
}
} else {
fetchedWaypoint[0].id = Int64(packet.id)
fetchedWaypoint[0].name = waypointMessage.name
fetchedWaypoint[0].longDescription = waypointMessage.description_p
fetchedWaypoint[0].latitudeI = waypointMessage.latitudeI
fetchedWaypoint[0].longitudeI = waypointMessage.longitudeI
fetchedWaypoint[0].icon = Int64(waypointMessage.icon)
fetchedWaypoint[0].locked = Int64(waypointMessage.lockedTo)
if waypointMessage.expire >= 1 {
fetchedWaypoint[0].expire = Date(timeIntervalSince1970: TimeInterval(Int64(waypointMessage.expire)))
} else {
fetchedWaypoint[0].expire = nil
}
fetchedWaypoint[0].lastUpdated = Date()
do {
try context.save()
Logger.data.info("💾 Updated Node Waypoint App Packet For: \(fetchedWaypoint[0].id, privacy: .public)")
} catch {
context.rollback()
let nsError = error as NSError
Logger.data.error("Error Saving WaypointEntity from WAYPOINT_APP \(nsError, privacy: .public)")
// Update existing waypoint
let existingWaypoint = fetchedWaypoint[0]
if existingWaypoint.locked == 0 || existingWaypoint.locked == packet.from {
let currentTime = Int64(Date().timeIntervalSince1970)
if waypointMessage.expire > 0 && waypointMessage.expire <= currentTime {
context.delete(existingWaypoint)
do {
try context.save()
Logger.data.info("💾 Deleted a waypoint")
} catch {
context.rollback()
let nsError = error as NSError
Logger.data.error("Error Saving WaypointEntity from WAYPOINT_APP \(nsError, privacy: .public)")
}
} else {
existingWaypoint.name = waypointMessage.name
existingWaypoint.longDescription = waypointMessage.description_p
existingWaypoint.latitudeI = waypointMessage.latitudeI
existingWaypoint.longitudeI = waypointMessage.longitudeI
existingWaypoint.icon = Int64(waypointMessage.icon)
existingWaypoint.locked = Int64(waypointMessage.lockedTo)
if waypointMessage.expire >= 1 {
existingWaypoint.expire = Date(timeIntervalSince1970: TimeInterval(Int64(waypointMessage.expire)))
} else {
existingWaypoint.expire = nil
}
existingWaypoint.lastUpdated = Date()
do {
try context.save()
Logger.data.info("💾 Updated Node Waypoint App Packet For: \(existingWaypoint.id, privacy: .public)")
} catch {
context.rollback()
let nsError = error as NSError
Logger.data.error("Error Saving WaypointEntity from WAYPOINT_APP \(nsError, privacy: .public)")
}
}
}
}
}

View file

@ -41,31 +41,29 @@ class MqttClientProxyManager {
if let host = host {
let port = defaultServerPort
let username = node.mqttConfig?.username
let password = node.mqttConfig?.password
let root = node.mqttConfig?.root?.count ?? 0 > 0 ? node.mqttConfig?.root : "msh"
let prefix = root!
topic = prefix + "/2/e" + "/#"
// Require opt in to map report terms to connect
if node.mqttConfig?.mapReportingEnabled ?? false && UserDefaults.mapReportingOptIn || !(node.mqttConfig?.mapReportingEnabled ?? false) {
connect(host: host, port: port, useSsl: useSsl, username: username, password: password, topic: topic)
connect(host: host, port: port, useSsl: useSsl, topic: topic, node: node)
} else {
delegate?.onMqttError(message: "MQTT Map Reporting Terms need to be accepted.")
}
}
}
func connect(host: String, port: Int, useSsl: Bool, username: String?, password: String?, topic: String?) {
func connect(host: String, port: Int, useSsl: Bool, topic: String?, node: NodeInfoEntity) {
guard !host.isEmpty else {
delegate?.onMqttDisconnected()
return
}
let clientId = "MeshtasticAppleMqttProxy-" + String(ProcessInfo().processIdentifier)
let clientId = "MeshtasticAppleMqttProxy-" + (node.user?.userId ?? String(ProcessInfo().processIdentifier))
mqttClientProxy = CocoaMQTT(clientID: clientId, host: host, port: UInt16(port))
if let mqttClient = mqttClientProxy {
mqttClient.enableSSL = useSsl
mqttClient.allowUntrustCACertificate = true
mqttClient.username = username
mqttClient.password = password
mqttClient.username = node.mqttConfig?.username
mqttClient.password = node.mqttConfig?.password
mqttClient.keepAlive = 60
mqttClient.cleanSession = true
if debugLog {

View file

@ -7,6 +7,7 @@
<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:meshtastic.org/e/*</string>
<string>applinks:meshtastic.org/v/*</string>
</array>
<key>com.apple.developer.weatherkit</key>
<true/>

View file

@ -3,6 +3,6 @@
<plist version="1.0">
<dict>
<key>_XCCurrentVersionName</key>
<string>MeshtasticDataModelV 50.xcdatamodel</string>
<string>MeshtasticDataModelV 51.xcdatamodel</string>
</dict>
</plist>

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="23605" systemVersion="24D81" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="23788" systemVersion="24D81" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<entity name="AmbientLightingConfigEntity" representedClassName="AmbientLightingConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="blue" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="current" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>

View file

@ -0,0 +1,505 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="23788" systemVersion="24D81" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<entity name="AmbientLightingConfigEntity" representedClassName="AmbientLightingConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="blue" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="current" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="green" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="ledState" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="red" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<relationship name="ambientLightingConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="ambientLightingConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="BluetoothConfigEntity" representedClassName="BluetoothConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="deviceLoggingEnabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="fixedPin" optional="YES" attributeType="Integer 32" defaultValueString="123456" usesScalarValueType="YES"/>
<attribute name="mode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<relationship name="bluetoothConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="bluetoothConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="CannedMessageConfigEntity" representedClassName="CannedMessageConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="inputbrokerEventCcw" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="inputbrokerEventCw" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="inputbrokerEventPress" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="inputbrokerPinA" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="inputbrokerPinB" optional="YES" attributeType="Integer 32" usesScalarValueType="YES"/>
<attribute name="inputbrokerPinPress" optional="YES" attributeType="Integer 32" usesScalarValueType="YES"/>
<attribute name="messages" optional="YES" attributeType="String" minValueString="0" maxValueString="198"/>
<attribute name="rotary1Enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="sendBell" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="updown1Enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<relationship name="cannedMessagesConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="cannedMessageConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="ChannelEntity" representedClassName="ChannelEntity" syncable="YES" codeGenerationType="class">
<attribute name="downlinkEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="id" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="index" attributeType="Integer 32" minValueString="0" maxValueString="13" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="mute" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="name" optional="YES" attributeType="String"/>
<attribute name="positionPrecision" optional="YES" attributeType="Integer 32" defaultValueString="32" usesScalarValueType="YES"/>
<attribute name="psk" optional="YES" attributeType="Binary"/>
<attribute name="role" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="uplinkEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<relationship name="myInfoChannel" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MyInfoEntity" inverseName="channels" inverseEntity="MyInfoEntity"/>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="index"/>
</uniquenessConstraint>
</uniquenessConstraints>
</entity>
<entity name="DetectionSensorConfigEntity" representedClassName="DetectionSensorConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="enabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="minimumBroadcastSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="monitorPin" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="name" optional="YES" attributeType="String"/>
<attribute name="sendBell" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="stateBroadcastSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="triggerType" optional="YES" attributeType="Integer 32" usesScalarValueType="YES"/>
<attribute name="usePullup" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<relationship name="detectionSensorConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="detectionSensorConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="DeviceConfigEntity" representedClassName="DeviceConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="buttonGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="buzzerGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="debugLogEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="disableTripleClick" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="doubleTapAsButtonPress" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="isManaged" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="ledHeartbeatEnabled" optional="YES" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
<attribute name="nodeInfoBroadcastSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="rebroadcastMode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="role" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="serialEnabled" optional="YES" attributeType="Boolean" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="tripleClickAsAdHocPing" optional="YES" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
<attribute name="tzdef" optional="YES" attributeType="String"/>
<relationship name="deviceConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="deviceConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="DeviceMetadataEntity" representedClassName="DeviceMetadataEntity" syncable="YES" codeGenerationType="class">
<attribute name="canShutdown" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="deviceStateVersion" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="excludedModules" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="firmwareVersion" optional="YES" attributeType="String"/>
<attribute name="hasBluetooth" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="hasEthernet" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="hasWifi" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="hwModel" optional="YES" attributeType="String"/>
<attribute name="positionFlags" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="role" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="time" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<relationship name="metadataNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="metadata" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="DisplayConfigEntity" representedClassName="DisplayConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="compassNorthTop" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="displayMode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="flipScreen" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="gpsFormat" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="headingBold" optional="YES" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
<attribute name="oledType" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="screenCarouselInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="screenOnSeconds" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="units" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="wakeOnTapOrMotion" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<relationship name="displayConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="displayConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="ExternalNotificationConfigEntity" representedClassName="ExternalNotificationConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="active" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="alertBell" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="alertBellBuzzer" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="alertBellVibra" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="alertMessage" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="alertMessageBuzzer" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="alertMessageVibra" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="nagTimeout" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="output" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="outputBuzzer" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="outputMilliseconds" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="outputVibra" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="useI2SAsBuzzer" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="usePWM" optional="YES" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
<relationship name="externalNotificationConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="externalNotificationConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="LocationEntity" representedClassName="LocationEntity" syncable="YES" codeGenerationType="class">
<attribute name="altitude" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="heading" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="id" optional="YES" attributeType="Integer 32" usesScalarValueType="YES"/>
<attribute name="latitudeI" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="longitudeI" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="speed" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<relationship name="routeLocation" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="RouteEntity" inverseName="locations" inverseEntity="RouteEntity"/>
</entity>
<entity name="LoRaConfigEntity" representedClassName="LoRaConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="bandwidth" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="channelNum" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="codingRate" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="frequencyOffset" optional="YES" attributeType="Float" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="hopLimit" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="ignoreMqtt" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="modemPreset" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="okToMqtt" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="overrideDutyCycle" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="overrideFrequency" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="regionCode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="spreadFactor" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="sx126xRxBoostedGain" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="txEnabled" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
<attribute name="txPower" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="usePreset" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
<relationship name="loRaConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="loRaConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="MessageEntity" representedClassName="MessageEntity" syncable="YES" codeGenerationType="class">
<attribute name="ackError" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="ackSNR" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="ackTimestamp" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="admin" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="adminDescription" optional="YES" attributeType="String"/>
<attribute name="channel" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="isEmoji" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="messageId" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="messagePayload" optional="YES" attributeType="String" defaultValueString=""/>
<attribute name="messagePayloadMarkdown" optional="YES" attributeType="String"/>
<attribute name="messageTimestamp" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="pkiEncrypted" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="portNum" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="publicKey" optional="YES" attributeType="Binary"/>
<attribute name="read" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="realACK" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="receivedACK" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="replyID" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="rssi" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<relationship name="fromUser" optional="YES" maxCount="1" deletionRule="Nullify" ordered="YES" destinationEntity="UserEntity" inverseName="sentMessages" inverseEntity="UserEntity"/>
<relationship name="toUser" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="UserEntity" inverseName="receivedMessages" inverseEntity="UserEntity"/>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="messageId"/>
</uniquenessConstraint>
</uniquenessConstraints>
</entity>
<entity name="MQTTConfigEntity" representedClassName="MQTTConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="address" optional="YES" attributeType="String"/>
<attribute name="enabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="encryptionEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="jsonEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="mapPositionPrecision" optional="YES" attributeType="Integer 32" defaultValueString="13" usesScalarValueType="YES"/>
<attribute name="mapPublishIntervalSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="mapReportingEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="password" optional="YES" attributeType="String" maxValueString="30"/>
<attribute name="proxyToClientEnabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="root" optional="YES" attributeType="String" defaultValueString="msh"/>
<attribute name="tlsEnabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="username" optional="YES" attributeType="String" maxValueString="30"/>
<relationship name="mqttConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="mqttConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="MyInfoEntity" representedClassName="MyInfoEntity" syncable="YES" codeGenerationType="class">
<attribute name="adminIndex" optional="YES" attributeType="Integer 32" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="bleName" optional="YES" attributeType="String"/>
<attribute name="deviceId" optional="YES" attributeType="Binary"/>
<attribute name="minAppVersion" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="myNodeNum" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="peripheralId" optional="YES" attributeType="String"/>
<attribute name="rebootCount" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="registered" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<relationship name="channels" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="ChannelEntity" inverseName="myInfoChannel" inverseEntity="ChannelEntity"/>
<relationship name="myInfoNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="myInfo" inverseEntity="NodeInfoEntity"/>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="myNodeNum"/>
</uniquenessConstraint>
</uniquenessConstraints>
</entity>
<entity name="NetworkConfigEntity" representedClassName="NetworkConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="dns" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="enabledProtocols" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="ethEnabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="gateway" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="ip" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="ntpServer" optional="YES" attributeType="String"/>
<attribute name="subnet" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="wifiEnabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="wifiMode" optional="YES" attributeType="Integer 32" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="wifiPsk" optional="YES" attributeType="String" minValueString="0" maxValueString="60"/>
<attribute name="wifiSsid" optional="YES" attributeType="String" minValueString="0" maxValueString="30"/>
<relationship name="networkConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="networkConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="NodeInfoEntity" representedClassName="NodeInfoEntity" syncable="YES" codeGenerationType="class">
<attribute name="bleName" optional="YES" attributeType="String"/>
<attribute name="channel" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="favorite" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="firstHeard" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="hopsAway" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="id" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="ignored" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="lastHeard" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="num" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="peripheralId" optional="YES" attributeType="String"/>
<attribute name="rssi" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="sessionExpiration" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="sessionPasskey" optional="YES" attributeType="Binary"/>
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="viaMqtt" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<relationship name="ambientLightingConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="AmbientLightingConfigEntity" inverseName="ambientLightingConfigNode" inverseEntity="AmbientLightingConfigEntity"/>
<relationship name="bluetoothConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="BluetoothConfigEntity" inverseName="bluetoothConfigNode" inverseEntity="BluetoothConfigEntity"/>
<relationship name="cannedMessageConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="CannedMessageConfigEntity" inverseName="cannedMessagesConfigNode" inverseEntity="CannedMessageConfigEntity"/>
<relationship name="detectionSensorConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="DetectionSensorConfigEntity" inverseName="detectionSensorConfigNode" inverseEntity="DetectionSensorConfigEntity"/>
<relationship name="deviceConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="DeviceConfigEntity" inverseName="deviceConfigNode" inverseEntity="DeviceConfigEntity"/>
<relationship name="displayConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="DisplayConfigEntity" inverseName="displayConfigNode" inverseEntity="DisplayConfigEntity"/>
<relationship name="externalNotificationConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="ExternalNotificationConfigEntity" inverseName="externalNotificationConfigNode" inverseEntity="ExternalNotificationConfigEntity"/>
<relationship name="loRaConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="LoRaConfigEntity" inverseName="loRaConfigNode" inverseEntity="LoRaConfigEntity"/>
<relationship name="metadata" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="DeviceMetadataEntity" inverseName="metadataNode" inverseEntity="DeviceMetadataEntity"/>
<relationship name="mqttConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MQTTConfigEntity" inverseName="mqttConfigNode" inverseEntity="MQTTConfigEntity"/>
<relationship name="myInfo" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MyInfoEntity" inverseName="myInfoNode" inverseEntity="MyInfoEntity"/>
<relationship name="networkConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NetworkConfigEntity" inverseName="networkConfigNode" inverseEntity="NetworkConfigEntity"/>
<relationship name="pax" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="PaxCounterEntity" inverseName="paxNode" inverseEntity="PaxCounterEntity"/>
<relationship name="paxCounterConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="PaxCounterConfigEntity" inverseName="paxCounterConfigNode" inverseEntity="PaxCounterConfigEntity"/>
<relationship name="positionConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="PositionConfigEntity" inverseName="positionConfigNode" inverseEntity="PositionConfigEntity"/>
<relationship name="positions" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="PositionEntity" inverseName="nodePosition" inverseEntity="PositionEntity"/>
<relationship name="powerConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="PowerConfigEntity" inverseName="powerConfigNode" inverseEntity="PowerConfigEntity"/>
<relationship name="rangeTestConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="RangeTestConfigEntity" inverseName="rangeTestConfigNode" inverseEntity="RangeTestConfigEntity"/>
<relationship name="rtttlConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="RTTTLConfigEntity" inverseName="rtttlConfigNode" inverseEntity="RTTTLConfigEntity"/>
<relationship name="securityConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="SecurityConfigEntity" inverseName="securityConfigNode" inverseEntity="SecurityConfigEntity"/>
<relationship name="serialConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="SerialConfigEntity" inverseName="serialConfigNode" inverseEntity="SerialConfigEntity"/>
<relationship name="storeForwardConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="StoreForwardConfigEntity" inverseName="storeForwardConfigNode" inverseEntity="StoreForwardConfigEntity"/>
<relationship name="telemetries" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="TelemetryEntity" inverseName="nodeTelemetry" inverseEntity="TelemetryEntity"/>
<relationship name="telemetryConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="TelemetryConfigEntity" inverseName="telemetryConfigNode" inverseEntity="TelemetryConfigEntity"/>
<relationship name="traceRoutes" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="TraceRouteEntity" inverseName="node" inverseEntity="TraceRouteEntity"/>
<relationship name="user" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="UserEntity" inverseName="userNode" inverseEntity="UserEntity"/>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="num"/>
</uniquenessConstraint>
</uniquenessConstraints>
</entity>
<entity name="PaxCounterConfigEntity" representedClassName="PaxCounterConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="bleThreshold" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="enabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="updateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="wifiThreshold" optional="YES" attributeType="Integer 32" defaultValueString="-80" usesScalarValueType="YES"/>
<relationship name="paxCounterConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="paxCounterConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="PaxCounterEntity" representedClassName="PaxCounterEntity" syncable="YES" codeGenerationType="class">
<attribute name="ble" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="time" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="uptime" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="wifi" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<relationship name="paxNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="pax" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="PositionConfigEntity" representedClassName="PositionConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="broadcastSmartMinimumDistance" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="broadcastSmartMinimumIntervalSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="deviceGpsEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="fixedPosition" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="gpsAttemptTime" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="gpsEnGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="gpsMode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="gpsUpdateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="positionBroadcastSeconds" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="positionFlags" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="rxGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="smartPositionEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="txGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<relationship name="positionConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="positionConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="PositionEntity" representedClassName="PositionEntity" syncable="YES" codeGenerationType="class">
<attribute name="altitude" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="heading" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="latest" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="latitudeI" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="longitudeI" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="precisionBits" optional="YES" attributeType="Integer 32" defaultValueString="32" usesScalarValueType="YES"/>
<attribute name="rssi" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="satsInView" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="seqNo" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="speed" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="time" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<relationship name="nodePosition" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="positions" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="PowerConfigEntity" representedClassName="PowerConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="adcMultiplierOverride" optional="YES" attributeType="Float" usesScalarValueType="YES"/>
<attribute name="deviceBatteryInaAddress" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="isPowerSaving" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="lsSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="minWakeSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="onBatteryShutdownAfterSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="waitBluetoothSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<relationship name="powerConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="powerConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="RangeTestConfigEntity" representedClassName="RangeTestConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="save" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="sender" optional="YES" attributeType="Integer 32" usesScalarValueType="YES"/>
<relationship name="rangeTestConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="rangeTestConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="RouteEntity" representedClassName="RouteEntity" syncable="YES" codeGenerationType="class">
<attribute name="color" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="date" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="distance" optional="YES" attributeType="Double" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="elevationGain" optional="YES" attributeType="Double" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="enabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="endDate" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="id" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="name" optional="YES" attributeType="String"/>
<attribute name="notes" optional="YES" attributeType="String"/>
<relationship name="locations" optional="YES" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="LocationEntity" inverseName="routeLocation" inverseEntity="LocationEntity"/>
</entity>
<entity name="RTTTLConfigEntity" representedClassName="RTTTLConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="ringtone" optional="YES" attributeType="String" maxValueString="228" defaultValueString=""/>
<relationship name="rtttlConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="rtttlConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="SecurityConfigEntity" representedClassName="SecurityConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="adminChannelEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="adminKey" optional="YES" attributeType="Binary"/>
<attribute name="adminKey2" optional="YES" attributeType="Binary"/>
<attribute name="adminKey3" optional="YES" attributeType="Binary"/>
<attribute name="bluetoothLoggingEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="debugLogApiEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="isManaged" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="privateKey" optional="YES" attributeType="Binary"/>
<attribute name="publicKey" optional="YES" attributeType="Binary"/>
<attribute name="serialEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<relationship name="securityConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="securityConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="SerialConfigEntity" representedClassName="SerialConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="baudRate" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="echo" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="mode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="overrideConsoleSerialPort" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="rxd" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="timeout" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="txd" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<relationship name="serialConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="serialConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="StoreForwardConfigEntity" representedClassName="StoreForwardConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="enabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="heartbeat" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
<attribute name="historyReturnMax" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="historyReturnWindow" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="isRouter" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="lastHeartbeat" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="lastRequest" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="records" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<relationship name="storeForwardConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="storeForwardConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="TelemetryConfigEntity" representedClassName="TelemetryConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="deviceUpdateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="environmentDisplayFahrenheit" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="environmentMeasurementEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="environmentScreenEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="environmentUpdateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="powerMeasurementEnabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="powerScreenEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="powerUpdateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<relationship name="telemetryConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="telemetryConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="TelemetryEntity" representedClassName="TelemetryEntity" syncable="YES">
<attribute name="airUtilTx" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="barometricPressure" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="batteryLevel" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="channelUtilization" optional="YES" attributeType="Float" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="current" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="gasResistance" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="iaq" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="irLux" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="lux" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="metricsType" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="numOnlineNodes" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="numPacketsRx" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="numPacketsRxBad" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="numPacketsTx" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="numRxDupe" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="numTotalNodes" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="numTxRelay" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="numTxRelayCanceled" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="powerCh1Current" optional="YES" attributeType="Float" usesScalarValueType="YES"/>
<attribute name="powerCh1Voltage" optional="YES" attributeType="Float" usesScalarValueType="YES"/>
<attribute name="powerCh2Current" optional="YES" attributeType="Float" usesScalarValueType="YES"/>
<attribute name="powerCh2Voltage" optional="YES" attributeType="Float" usesScalarValueType="YES"/>
<attribute name="powerCh3Current" optional="YES" attributeType="Float" usesScalarValueType="YES"/>
<attribute name="powerCh3Voltage" optional="YES" attributeType="Float" usesScalarValueType="YES"/>
<attribute name="radiation" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="rainfall1H" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="rainfall24H" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="relativeHumidity" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="rssi" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="soilMoisture" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="soilTemperature" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="temperature" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="time" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="uptimeSeconds" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="uvLux" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="voltage" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="weight" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="whiteLux" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="windDirection" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="windGust" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="windLull" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="windSpeed" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<relationship name="nodeTelemetry" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="telemetries" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="TraceRouteEntity" representedClassName="TraceRouteEntity" syncable="YES" codeGenerationType="class">
<attribute name="hasPositions" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="hopsBack" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="hopsTowards" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="id" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="response" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="routeBackText" optional="YES" attributeType="String"/>
<attribute name="routeText" optional="YES" attributeType="String"/>
<attribute name="sent" optional="YES" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="time" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<relationship name="hops" optional="YES" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="TraceRouteHopEntity" inverseName="traceRoute" inverseEntity="TraceRouteHopEntity"/>
<relationship name="node" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="traceRoutes" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="TraceRouteHopEntity" representedClassName="TraceRouteHopEntity" syncable="YES" codeGenerationType="class">
<attribute name="altitude" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="back" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="latitudeI" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="longitudeI" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="name" optional="YES" attributeType="String"/>
<attribute name="num" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="time" attributeType="Date" usesScalarValueType="NO"/>
<relationship name="traceRoute" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="TraceRouteEntity" inverseName="hops" inverseEntity="TraceRouteEntity"/>
</entity>
<entity name="UserEntity" representedClassName="UserEntity" syncable="YES" codeGenerationType="class">
<attribute name="hwDisplayName" optional="YES" attributeType="String"/>
<attribute name="hwModel" attributeType="String"/>
<attribute name="hwModelId" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="isLicensed" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="keyMatch" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
<attribute name="lastMessage" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="longName" attributeType="String"/>
<attribute name="mute" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="newPublicKey" optional="YES" attributeType="Binary"/>
<attribute name="num" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="numString" optional="YES" attributeType="String"/>
<attribute name="pkiEncrypted" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="publicKey" optional="YES" attributeType="Binary"/>
<attribute name="role" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="shortName" attributeType="String"/>
<attribute name="unmessagable" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="userId" attributeType="String"/>
<relationship name="receivedMessages" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="MessageEntity" inverseName="toUser" inverseEntity="MessageEntity"/>
<relationship name="sentMessages" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="MessageEntity" inverseName="fromUser" inverseEntity="MessageEntity"/>
<relationship name="userNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="user" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="WaypointEntity" representedClassName="WaypointEntity" syncable="YES" codeGenerationType="class">
<attribute name="created" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="expire" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="icon" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="id" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="lastUpdated" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="latitudeI" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="locked" attributeType="Integer 64" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="longDescription" optional="YES" attributeType="String" maxValueString="100"/>
<attribute name="longitudeI" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="name" attributeType="String" minValueString="1" maxValueString="30"/>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="id"/>
</uniquenessConstraint>
</uniquenessConstraints>
</entity>
</model>

View file

@ -4,18 +4,14 @@ import SwiftUI
import CoreData
import OSLog
import TipKit
import MeshtasticProtobufs
@main
struct MeshtasticAppleApp: App {
@UIApplicationDelegateAdaptor(MeshtasticAppDelegate.self)
private var appDelegate
@UIApplicationDelegateAdaptor(MeshtasticAppDelegate.self) private var appDelegate
@ObservedObject
var appState: AppState
// @ObservedObject
// private var bleManager: BLEManager
@ObservedObject var appState: AppState
private let persistenceController: PersistenceController
@ -24,6 +20,7 @@ struct MeshtasticAppleApp: App {
@State var incomingUrl: URL?
@State var channelSettings: String?
@State var addChannels = false
public var minimumContactVersion = "2.6.9"
init() {
let persistenceController = PersistenceController.shared
@ -59,8 +56,10 @@ struct MeshtasticAppleApp: App {
.onContinueUserActivity(NSUserActivityTypeBrowsingWeb) { userActivity in
Logger.mesh.debug("URL received \(userActivity, privacy: .public)")
self.incomingUrl = userActivity.webpageURL
if (self.incomingUrl?.absoluteString.lowercased().contains("meshtastic.org/e/#")) != nil {
self.saveChannels = false
if self.incomingUrl?.absoluteString.lowercased().contains("meshtastic.org/v/#") == true {
handleContactUrl(url: self.incomingUrl!)
} else if self.incomingUrl?.absoluteString.lowercased().contains("meshtastic.org/e/#") == true {
if let components = self.incomingUrl?.absoluteString.components(separatedBy: "#") {
self.addChannels = Bool(self.incomingUrl?["add"] ?? "false") ?? false
if (self.incomingUrl?.absoluteString.lowercased().contains("?")) != nil {
@ -84,10 +83,11 @@ struct MeshtasticAppleApp: App {
}
}
.onOpenURL(perform: { (url) in
Logger.mesh.debug("Some sort of URL was received \(url, privacy: .public)")
self.incomingUrl = url
if url.absoluteString.lowercased().contains("meshtastic.org/e/#") {
if url.absoluteString.lowercased().contains("meshtastic.org/v/#") {
handleContactUrl(url: url)
} else if url.absoluteString.lowercased().contains("meshtastic.org/e/#") {
if let components = self.incomingUrl?.absoluteString.components(separatedBy: "#") {
self.addChannels = Bool(self.incomingUrl?["add"] ?? "false") ?? false
if self.incomingUrl?.absoluteString.lowercased().contains("?") != nil {
@ -119,7 +119,7 @@ struct MeshtasticAppleApp: App {
.displayFrequency(.immediate)
]
)
}
}
}
.onChange(of: scenePhase) { (_, newScenePhase) in
switch newScenePhase {
@ -143,4 +143,76 @@ struct MeshtasticAppleApp: App {
}
}
}
func handleContactUrl(url: URL) {
let supportedVersion = UserDefaults.firmwareVersion == "0.0.0" || self.minimumContactVersion.compare(UserDefaults.firmwareVersion, options: .numeric) == .orderedAscending || minimumContactVersion.compare(UserDefaults.firmwareVersion, options: .numeric) == .orderedSame
if !supportedVersion {
// Show an alert letting the user know they need to upgrade their firmware to use the contact import.
let alertController = UIAlertController(
title: "Firmware Upgrade Required",
message: "In order to import contacts via a QR code you need firmware version 2.6.9 or greater.",
preferredStyle: .alert
)
alertController.addAction(UIAlertAction(
title: "Close",
style: .cancel,
handler: nil
))
// Present the alert
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
let rootViewController = windowScene.windows.first?.rootViewController {
rootViewController.present(alertController, animated: true)
}
Logger.services.debug("User Alerted that a firmware upgrade is required to import contacts.")
} else {
let components = url.absoluteString.components(separatedBy: "#")
// Extract contact information from the URL
if let contactData = components.last {
let decodedString = contactData.base64urlToBase64()
if let decodedData = Data(base64Encoded: decodedString) {
do {
let contact = try MeshtasticProtobufs.SharedContact(serializedBytes: decodedData)
// Show an alert to confirm adding the contact
let alertController = UIAlertController(
title: "Add Contact",
message: "Would you like to add \(contact.user.longName) as a contact?",
preferredStyle: .alert
)
alertController.addAction(UIAlertAction(
title: "Yes",
style: .default,
handler: { _ in
let success = BLEManager.shared.addContactFromURL(base64UrlString: contactData)
Logger.services.debug("Contact added from URL: \(success ? "success" : "failed")")
}
))
alertController.addAction(UIAlertAction(
title: "No",
style: .cancel,
handler: nil
))
// Present the alert
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
let rootViewController = windowScene.windows.first?.rootViewController {
rootViewController.present(alertController, animated: true)
}
Logger.services.debug("Contact data extracted from URL: \(contactData, privacy: .public)")
} catch {
Logger.services.error("Failed to parse contact data: \(error.localizedDescription, privacy: .public)")
// Show error alert to user
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
let rootViewController = windowScene.windows.first?.rootViewController {
let errorAlert = UIAlertController(
title: "Error",
message: "Could not process contact information. Invalid format.",
preferredStyle: .alert
)
errorAlert.addAction(UIAlertAction(title: "OK", style: .default))
rootViewController.present(errorAlert, animated: true)
}
}
}
}
}
}
}

View file

@ -97,7 +97,22 @@ class MeshtasticAppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificat
if let targetValue = userInfo["target"] as? String,
let deepLink = userInfo["path"] as? String,
let url = URL(string: deepLink) {
Logger.services.info("userNotificationCenter didReceiveResponse \(targetValue, privacy: .public) \(deepLink, privacy: .public)")
Logger.services.info("userNotificationCenter didReceiveResponse handling deeplink: \(targetValue, privacy: .public) \(deepLink, privacy: .public)")
// Handle TraceRoute notifications specially to ensure they navigate correctly
if deepLink.contains("meshtastic:///nodes") && deepLink.contains("nodenum=") {
// First extract the node number from the URL
if let nodeNumString = deepLink.components(separatedBy: "nodenum=").last,
let nodeNum = Int64(nodeNumString) {
Logger.services.info("Navigation to specific node via notification: \(nodeNum, privacy: .public)")
self.router?.navigationState.selectedTab = .nodes
// Post a notification to trigger app-wide refresh
NotificationCenter.default.post(name: NSNotification.Name("ForceNavigationRefresh"),
object: nil,
userInfo: ["nodeNum": nodeNum])
self.router?.navigationState.nodeListSelectedNodeNum = nodeNum
}
}
// Still call the regular router in all cases
router?.route(url: url)
} else {
Logger.services.error("Failed to handle notification response: \(userInfo, privacy: .public)")

View file

@ -160,7 +160,9 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext)
newNode.channel = Int32(packet.channel)
}
if let nodeInfoMessage = try? NodeInfo(serializedBytes: packet.decoded.payload) {
newNode.hopsAway = Int32(nodeInfoMessage.hopsAway)
if nodeInfoMessage.hasHopsAway {
newNode.hopsAway = Int32(nodeInfoMessage.hopsAway)
}
newNode.favorite = nodeInfoMessage.isFavorite
}
@ -181,6 +183,18 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext)
newUser.role = Int32(newUserMessage.role.rawValue)
newUser.hwModel = String(describing: newUserMessage.hwModel).uppercased()
newUser.hwModelId = Int32(newUserMessage.hwModel.rawValue)
/// For nodes that have the optional isUnmessagable boolean use that, otherwise excluded roles that are unmessagable by default
if newUserMessage.hasIsUnmessagable {
newUser.unmessagable = newUserMessage.isUnmessagable
} else {
let roles = [2, 4, 5, 6, 7, 10, 11]
let containsRole = roles.contains(Int(newUser.role))
if containsRole {
newUser.unmessagable = true
} else {
newUser.unmessagable = false
}
}
if !newUserMessage.publicKey.isEmpty {
newUser.pkiEncrypted = true
newUser.publicKey = newUserMessage.publicKey
@ -277,6 +291,18 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext)
fetchedNode[0].user!.role = Int32(nodeInfoMessage.user.role.rawValue)
fetchedNode[0].user!.hwModel = String(describing: nodeInfoMessage.user.hwModel).uppercased()
fetchedNode[0].user!.hwModelId = Int32(nodeInfoMessage.user.hwModel.rawValue)
/// For nodes that have the optional isUnmessagable boolean use that, otherwise excluded roles that are unmessagable by default
if nodeInfoMessage.user.hasIsUnmessagable {
fetchedNode[0].user!.unmessagable = nodeInfoMessage.user.isUnmessagable
} else {
let roles = [-1, 2, 4, 5, 6, 7, 10, 11]
let containsRole = roles.contains(Int(fetchedNode[0].user?.role ?? -1))
if containsRole {
fetchedNode[0].user?.unmessagable = true
} else {
fetchedNode[0].user?.unmessagable = false
}
}
if !nodeInfoMessage.user.publicKey.isEmpty {
fetchedNode[0].user!.pkiEncrypted = true
fetchedNode[0].user!.publicKey = nodeInfoMessage.user.publicKey

View file

@ -124,6 +124,14 @@ struct Connect: View {
Text("Long Name: \(node?.user?.longName?.addingVariationSelectors ?? "Unknown".localized)")
Text("BLE RSSI: \(connectedPeripheral.rssi)")
Button(role: .destructive) {
if let connectedPeripheral = bleManager.connectedPeripheral,
connectedPeripheral.peripheral.state == .connected {
bleManager.disconnectPeripheral(reconnect: false)
}
} label: {
Label("Disconnect", systemImage: "antenna.radiowaves.left.and.right.slash")
}
Button {
if !bleManager.sendShutdown(fromUser: node!.user!, toUser: node!.user!, adminIndex: node!.myInfo!.adminIndex) {
Logger.mesh.error("Shutdown Failed")

View file

@ -32,47 +32,64 @@ import Foundation
import SwiftUI
struct SignalStrengthIndicator: View {
let signalStrength: BLESignalStrength
// Accessibility: VoiceOver description
private var accessibilityDescription: String {
switch signalStrength {
case .weak:
return "Signal strength weak".localized
case .normal:
return "Signal strength normal".localized
case .strong:
return "Signal strength strong".localized
}
}
var body: some View {
HStack {
ForEach(0..<3) { bar in
RoundedRectangle(cornerRadius: 3)
.divided(amount: (CGFloat(bar) + 1) / CGFloat(3))
.fill(getColor().opacity(bar <= signalStrength.rawValue ? 1 : 0.3))
.frame(width: 8, height: 40)
}
}
}
let signalStrength: BLESignalStrength
private func getColor() -> Color {
switch signalStrength {
case .weak:
return Color.red
case .normal:
return Color.yellow
case .strong:
return Color.green
}
}
var body: some View {
Group {
HStack {
ForEach(0..<3) { bar in
RoundedRectangle(cornerRadius: 3)
.divided(amount: (CGFloat(bar) + 1) / CGFloat(3))
.fill(getColor().opacity(bar <= signalStrength.rawValue ? 1 : 0.3))
.frame(width: 8, height: 40)
}
}
}
.accessibilityElement(children: .ignore)
.accessibilityLabel("Signal strength".localized)
.accessibilityValue(accessibilityDescription)
}
private func getColor() -> Color {
switch signalStrength {
case .weak:
return Color.red
case .normal:
return Color.yellow
case .strong:
return Color.green
}
}
}
struct Divided<S: Shape>: Shape {
var amount: CGFloat // Should be in range 0...1
var shape: S
func path(in rect: CGRect) -> Path {
shape.path(in: rect.divided(atDistance: amount * rect.height, from: .maxYEdge).slice)
}
var amount: CGFloat // Should be in range 0...1
var shape: S
func path(in rect: CGRect) -> Path {
shape.path(in: rect.divided(atDistance: amount * rect.height, from: .maxYEdge).slice)
}
}
extension Shape {
func divided(amount: CGFloat) -> Divided<Self> {
return Divided(amount: amount, shape: self)
}
func divided(amount: CGFloat) -> Divided<Self> {
return Divided(amount: amount, shape: self)
}
}
enum BLESignalStrength: Int {
case weak = 0
case normal = 1
case strong = 2
case weak = 0
case normal = 1
case strong = 2
}

View file

@ -13,69 +13,101 @@ struct BatteryCompact: View {
var color: Color
var body: some View {
// Group the battery icon and label in a single accessible container
HStack(alignment: .center, spacing: 0) {
if let batteryLevel {
if batteryLevel == 100 {
Image(systemName: "battery.100.bolt")
.font(iconFont)
.foregroundColor(color)
.symbolRenderingMode(.multicolor)
} else if batteryLevel < 100 && batteryLevel > 74 {
Image(systemName: "battery.75")
.font(iconFont)
.foregroundColor(color)
.symbolRenderingMode(.multicolor)
} else if batteryLevel < 75 && batteryLevel > 49 {
Image(systemName: "battery.50")
.font(iconFont)
.foregroundColor(color)
.symbolRenderingMode(.multicolor)
} else if batteryLevel < 50 && batteryLevel > 14 {
Image(systemName: "battery.25")
.font(iconFont)
.foregroundColor(color)
.symbolRenderingMode(.multicolor)
} else if batteryLevel < 15 && batteryLevel > 0 {
Image(systemName: "battery.0")
.font(iconFont)
.foregroundColor(color)
.symbolRenderingMode(.multicolor)
} else if batteryLevel == 0 {
Image(systemName: "battery.0")
.font(iconFont)
.foregroundColor(.red)
.symbolRenderingMode(.multicolor)
} else if batteryLevel > 100 {
// Check for plugged in state
let isPluggedIn = batteryLevel > 100
let isCharging = batteryLevel == 100
// Battery icon selection based on level
if isPluggedIn {
Image(systemName: "powerplug")
.font(iconFont)
.foregroundColor(color)
.symbolRenderingMode(.multicolor)
.accessibilityHidden(true) // Hide from VoiceOver since container will handle it
} else if isCharging {
Image(systemName: "battery.100.bolt")
.font(iconFont)
.foregroundColor(color)
.symbolRenderingMode(.multicolor)
.accessibilityHidden(true)
} else if batteryLevel > 74 {
Image(systemName: "battery.75")
.font(iconFont)
.foregroundColor(color)
.symbolRenderingMode(.multicolor)
.accessibilityHidden(true)
} else if batteryLevel > 49 {
Image(systemName: "battery.50")
.font(iconFont)
.foregroundColor(color)
.symbolRenderingMode(.multicolor)
.accessibilityHidden(true)
} else if batteryLevel > 14 {
Image(systemName: "battery.25")
.font(iconFont)
.foregroundColor(color)
.symbolRenderingMode(.multicolor)
.accessibilityHidden(true)
} else if batteryLevel > 0 {
Image(systemName: "battery.0")
.font(iconFont)
.foregroundColor(color)
.symbolRenderingMode(.multicolor)
.accessibilityHidden(true)
} else {
Image(systemName: "battery.0")
.font(iconFont)
.foregroundColor(.red)
.symbolRenderingMode(.multicolor)
.accessibilityHidden(true)
}
} else {
Image(systemName: "battery.0")
.font(iconFont)
.foregroundColor(color)
.symbolRenderingMode(.multicolor)
}
if let batteryLevel {
if batteryLevel > 100 {
// Battery text label
if isPluggedIn {
Text("PWD")
.foregroundStyle(.secondary)
.font(font)
} else if batteryLevel == 100 {
.accessibilityHidden(true)
} else if isCharging {
Text("CHG")
.foregroundStyle(.secondary)
.font(font)
.accessibilityHidden(true)
} else {
Text(verbatim: "\(batteryLevel.formatted(.number.precision(.fractionLength(0))))%")
.foregroundStyle(.secondary)
.font(font)
.accessibilityHidden(true)
}
} else {
// Unknown battery state
Image(systemName: "battery.0")
.font(iconFont)
.foregroundColor(color)
.symbolRenderingMode(.multicolor)
.accessibilityHidden(true)
Text(verbatim: "?")
.foregroundStyle(.secondary)
.font(font)
.accessibilityHidden(true)
}
}
// Setup container-level accessibility for VoiceOver
.accessibilityElement(children: .ignore)
.accessibilityLabel(NSLocalizedString("Battery Level", comment: "VoiceOver label for battery gauge"))
// Set appropriate value based on the battery state using a computed property
.accessibilityValue(batteryLevel.map { level in
if level > 100 {
// Plugged in - same as PWD visual indicator
return "Plugged in".localized
} else if level == 100 {
// Charging - same as CHG visual indicator
return "Charging".localized
} else {
// Normal battery level
return String(format: NSLocalizedString("Battery Level %", comment: "VoiceOver value for battery level"), Int(level))
}
} ?? "Unknown")
}
}

View file

@ -18,18 +18,20 @@ struct BatteryGauge: View {
let deviceMetrics = node.telemetries?.filtered(using: NSPredicate(format: "metricsType == 0"))
let mostRecent = deviceMetrics?.lastObject as? TelemetryEntity
let batteryLevel = Double(mostRecent?.batteryLevel ?? 0)
// For VoiceOver purposes, detect when device is plugged in (battery > 100%)
let isPluggedIn = (mostRecent?.batteryLevel ?? 0) > 100
// Use a capped battery level for UI display
let batteryLevel = Double(min(100, mostRecent?.batteryLevel ?? 0))
VStack {
if batteryLevel > 100.0 {
// Plugged in
Image(systemName: "powerplug")
.font(.largeTitle)
.foregroundColor(.accentColor)
.symbolRenderingMode(.hierarchical)
if isPluggedIn {
// Use a completely standalone view for the plugged in state
// to avoid any VoiceOver confusion
PluggedInIndicator()
} else {
let gradient = Gradient(colors: [.red, .orange, .green])
Gauge(value: batteryLevel, in: minValue...maxValue) {
// Accessibility for battery gauge
if batteryLevel >= 0.0 && batteryLevel < 10 {
Label("Battery Level %", systemImage: "battery.0")
} else if batteryLevel >= 10.0 && batteryLevel < 25.00 {
@ -50,6 +52,8 @@ struct BatteryGauge: View {
Text(Int(batteryLevel), format: .percent)
}
}
.accessibilityLabel(NSLocalizedString("Battery Level", comment: "VoiceOver label for battery gauge"))
.accessibilityValue(String(format: NSLocalizedString("Battery Level %", comment: "VoiceOver value for battery level"), Int(batteryLevel)))
.tint(gradient)
.gaugeStyle(.accessoryCircular)
}
@ -63,6 +67,23 @@ struct BatteryGauge: View {
}
}
/// A dedicated view for showing a device is plugged in
/// With proper VoiceOver support that matches the visual indication
struct PluggedInIndicator: View {
var body: some View {
// This view is isolated from any battery measurement
// to ensure VoiceOver doesn't pick up any percentages
Image(systemName: "powerplug")
.font(.largeTitle)
.foregroundColor(.accentColor)
.symbolRenderingMode(.hierarchical)
// Override the accessibility to ensure correct VoiceOver announcement
.accessibilityElement(children: .ignore)
.accessibilityLabel("Battery Level".localized)
.accessibilityValue("Plugged in".localized)
}
}
struct BatteryGauge_Previews: PreviewProvider {
static var previews: some View {
VStack {

View file

@ -21,22 +21,46 @@ struct ConnectedDevice: View {
if (phoneOnly && UIDevice.current.userInterfaceIdiom == .phone) || !phoneOnly {
if bluetoothOn {
if deviceConnected {
if mqttUplinkEnabled || mqttDownlinkEnabled {
MQTTIcon(connected: mqttProxyConnected, uplink: mqttUplinkEnabled, downlink: mqttDownlinkEnabled, topic: mqttTopic)
}
Image(systemName: "antenna.radiowaves.left.and.right.circle.fill")
.imageScale(.large)
.foregroundColor(.green)
.symbolRenderingMode(.hierarchical)
Text(name.addingVariationSelectors).font(name.isEmoji() ? .title : .callout).foregroundColor(.gray)
// Create an HStack for connected state with proper accessibility
HStack {
if mqttUplinkEnabled || mqttDownlinkEnabled {
MQTTIcon(connected: mqttProxyConnected, uplink: mqttUplinkEnabled, downlink: mqttDownlinkEnabled, topic: mqttTopic)
.accessibilityHidden(true)
}
Image(systemName: "antenna.radiowaves.left.and.right.circle.fill")
.imageScale(.large)
.foregroundColor(.green)
.symbolRenderingMode(.hierarchical)
.accessibilityHidden(true)
Text(name.addingVariationSelectors)
.font(name.isEmoji() ? .title : .callout)
.foregroundColor(.gray)
.accessibilityHidden(true)
}
.accessibilityElement(children: .ignore)
.accessibilityLabel("Connected to Bluetooth device".localized + ", " + name.formatNodeNameForVoiceOver())
} else {
Image(systemName: "antenna.radiowaves.left.and.right.slash")
.imageScale(.medium)
.foregroundColor(.red)
.symbolRenderingMode(.hierarchical)
// Create a container for disconnected state
HStack {
Image(systemName: "antenna.radiowaves.left.and.right.slash")
.imageScale(.medium)
.foregroundColor(.red)
.symbolRenderingMode(.hierarchical)
.accessibilityHidden(true)
}
.accessibilityElement(children: .ignore)
.accessibilityLabel("No Bluetooth device connected".localized)
}
} else {
Text("Bluetooth is off").font(.subheadline).foregroundColor(.red)
// Create a container for Bluetooth off state
HStack {
Text("bluetooth.off".localized)
.font(.subheadline)
.foregroundColor(.red)
.accessibilityHidden(true)
}
.accessibilityElement(children: .ignore)
.accessibilityLabel("bluetooth.off".localized)
}
}
}

View file

@ -51,6 +51,20 @@ struct LoRaSignalStrengthMeter_Previews: PreviewProvider {
static var previews: some View {
ScrollView {
VStack {
VStack {
// Good
LoRaSignalStrengthMeter(snr: -10, rssi: -100, preset: ModemPresets.longFast, compact: true)
.padding(.bottom)
// Fair
LoRaSignalStrengthMeter(snr: -9.5, rssi: -119, preset: ModemPresets.longFast, compact: true)
.padding(.bottom)
// Bad
LoRaSignalStrengthMeter(snr: -12.75, rssi: -139, preset: ModemPresets.longFast, compact: true)
.padding(.bottom)
// None
LoRaSignalStrengthMeter(snr: -26.0, rssi: -128, preset: ModemPresets.longFast, compact: true)
.padding(.bottom)
}.padding()
HStack {
// Good
LoRaSignalStrengthMeter(snr: -1, rssi: -114, preset: ModemPresets.longFast, compact: false)
@ -85,16 +99,5 @@ struct LoRaSignalStrengthMeter_Previews: PreviewProvider {
}
.padding(.top)
}
VStack {
// Good
LoRaSignalStrengthMeter(snr: -10, rssi: -100, preset: ModemPresets.longFast, compact: true)
// Fair
LoRaSignalStrengthMeter(snr: -9.5, rssi: -119, preset: ModemPresets.longFast, compact: true)
// Bad
LoRaSignalStrengthMeter(snr: -12.75, rssi: -139, preset: ModemPresets.longFast, compact: true)
// None
LoRaSignalStrengthMeter(snr: -26.0, rssi: -128, preset: ModemPresets.longFast, compact: true)
}
}
}

View file

@ -0,0 +1,113 @@
//
// RateLimitCountdownView.swift
// Meshtastic
//
// Created by Jake Bordens on 5/5/25.
//
import SwiftUI
// This class provides a rate limited button.
// Provide a key to differentiate which action is rate-limited
// This allows you to keep different rate limits for different action
// Rate limits are stored in a RateLimitStorage singleton, but do not persist
public struct RateLimitedButton<Content: View>: View {
typealias Builder = ((percentComplete: Double, secondsRemaining: TimeInterval)?) -> Content
let key: String
@StateObject var storage = RateLimitStorage.shared
let rateLimit: TimeInterval
let content: Builder
let action: () -> Void
init(key: String, rateLimit: TimeInterval, action: @escaping () -> Void, @ViewBuilder label: @escaping Builder) {
self.key = key
self.rateLimit = rateLimit
self.content = label
self.action = action
}
public var body: some View {
let percentRemaining = storage.rateLimitRemainingPercentage(forKey: key)
let secondsRemaining = storage.rateLimitSecondsRemaining(forKey: key)
if percentRemaining > 0.0 {
content((percentRemaining, secondsRemaining))
} else {
Button {
storage.actionOccured(forKey: key, rateLimit: rateLimit)
action()
} label: {
content(nil)
}
}
}
}
// To store the time an action occured (name by a key) and the time limit
// Does not persist across app launches
class RateLimitStorage: ObservableObject {
private struct RateLimiter {
var actionOccuredTimestamp: Date
var rateLimitSeconds: TimeInterval
var rateLimitExpires: Date {
return actionOccuredTimestamp.addingTimeInterval(rateLimitSeconds)
}
}
static var shared: RateLimitStorage = RateLimitStorage() // Singleton instance
private var rateLimits = [String: RateLimiter]()
private var timer: Timer?
func actionOccured(forKey key: String, rateLimit: TimeInterval) {
let now = Date()
if let existingRateLimit = rateLimits[key] {
if existingRateLimit.rateLimitExpires > now.addingTimeInterval(rateLimit) {
// We have an existing rate limit that is larger than the one being requested
// Ignore
return
}
}
self.objectWillChange.send()
rateLimits[key] = RateLimiter(actionOccuredTimestamp: now, rateLimitSeconds: rateLimit)
startTimerIfNecessary()
}
func rateLimitRemainingPercentage(forKey: String) -> Double {
guard let rateLimit = rateLimits[forKey] else {
return 0.0
}
let percent = (rateLimit.rateLimitExpires.timeIntervalSinceNow) / rateLimit.rateLimitSeconds
return min(1.0, max(percent, 0.0))
}
func rateLimitSecondsRemaining(forKey: String) -> TimeInterval {
guard let rateLimit = rateLimits[forKey] else {
return 0.0
}
return rateLimit.rateLimitExpires.timeIntervalSinceNow
}
func startTimerIfNecessary() {
// Timer exists, don't create one
guard timer == nil else { return }
// Create the timer
self.timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
guard let self = self else { return }
self.objectWillChange.send()
// Determine if we can clean up the dictionary and stop the timer.
let maxExpiration = self.rateLimits.values.map { $0.rateLimitExpires }.max() ?? .distantPast
if maxExpiration.timeIntervalSinceNow < 0 {
// All rateLimits are in the past. Stop and clean up
self.timer?.invalidate()
self.timer = nil
self.rateLimits.removeAll()
}
}
}
}

View file

@ -33,8 +33,15 @@ struct ChannelMessageList: View {
ZStack(alignment: .bottomTrailing) {
ScrollView {
LazyVStack {
ForEach(channel.allPrivateMessages) { (message: MessageEntity) in
ForEach(Array(channel.allPrivateMessages.enumerated()), id: \.element.id) { index, message in
// Get the previous message, if it exists
let previousMessage = index > 0 ? channel.allPrivateMessages[index - 1] : nil
let currentUser: Bool = (Int64(preferredPeripheralNum) == message.fromUser?.num ? true : false)
if message.displayTimestamp(aboveMessage: previousMessage) {
Text(message.timestamp.formatted(date: .abbreviated, time: .shortened))
.font(.caption)
.foregroundColor(.gray)
}
if message.replyID > 0 {
let messageReply = channel.allPrivateMessages.first(where: { $0.messageId == message.replyID })
HStack {
@ -44,7 +51,6 @@ struct ChannelMessageList: View {
messageToHighlight = messageNum
}
scrollView.scrollTo(messageNum, anchor: .center)
// Reset highlight after delay
Task {
try? await Task.sleep(nanoseconds: 1_000_000_000) // 1 second

View file

@ -6,6 +6,7 @@ struct RequestPositionButton: View {
var body: some View {
Button(action: action) {
Image(systemName: "mappin.and.ellipse")
.accessibilityLabel("Position Exchange Requested".localized)
.symbolRenderingMode(.hierarchical)
.imageScale(.large)
.foregroundColor(.accentColor)

View file

@ -6,6 +6,8 @@ struct TextMessageSize: View {
var body: some View {
ProgressView("\("Bytes".localized): \(totalBytes) / \(maxbytes)", value: Double(totalBytes), total: Double(maxbytes))
.accessibilityLabel(NSLocalizedString("Message Size", comment: "VoiceOver label for message size"))
.accessibilityValue(String(format: NSLocalizedString("Bytes Used", comment: "VoiceOver value for bytes used"), totalBytes, maxbytes))
.frame(width: 130)
.padding(5)
.font(.subheadline)

View file

@ -21,6 +21,7 @@ struct UserList: View {
@State private var isPkiEncrypted = false
@State private var isFavorite = false
@State private var isIgnored = false
@State private var isUnmessagable = false
@State private var isEnvironment = false
@State private var distanceFilter = false
@State private var maxDistance: Double = 800000
@ -46,8 +47,8 @@ struct UserList: View {
NSSortDescriptor(key: "userNode.lastHeard", ascending: false),
NSSortDescriptor(key: "longName", ascending: true)],
predicate: NSPredicate(
format: "userNode.ignored == false && longName != '' AND NOT (userNode.viaMqtt == YES AND userNode.hopsAway > 0)"
), animation: .default
format: "userNode.ignored == NO AND unmessagable = NO"
), animation: .spring
)
var users: FetchedResults<UserEntity>
@ -60,141 +61,139 @@ struct UserList: View {
let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMdd", options: 0, locale: Locale.current)
let dateFormatString = (localeDateFormat ?? "MM/dd/YY")
VStack {
List(selection: $userSelection) {
ForEach(users) { (user: UserEntity) in
let mostRecent = user.messageList.last
let lastMessageTime = Date(timeIntervalSince1970: TimeInterval(Int64((mostRecent?.messageTimestamp ?? 0 ))))
let lastMessageDay = Calendar.current.dateComponents([.day], from: lastMessageTime).day ?? 0
let currentDay = Calendar.current.dateComponents([.day], from: Date()).day ?? 0
if user.num != bleManager.connectedPeripheral?.num ?? 0 {
NavigationLink(value: user) {
ZStack {
Image(systemName: "circle.fill")
.opacity(user.unreadMessages > 0 ? 1 : 0)
.font(.system(size: 10))
.foregroundColor(.accentColor)
.brightness(0.2)
}
List(users, selection: $userSelection) { (user: UserEntity) in
let mostRecent = user.messageList.last
let lastMessageTime = Date(timeIntervalSince1970: TimeInterval(Int64((mostRecent?.messageTimestamp ?? 0 ))))
let lastMessageDay = Calendar.current.dateComponents([.day], from: lastMessageTime).day ?? 0
let currentDay = Calendar.current.dateComponents([.day], from: Date()).day ?? 0
if user.num != bleManager.connectedPeripheral?.num ?? 0 {
NavigationLink(value: user) {
ZStack {
Image(systemName: "circle.fill")
.opacity(user.unreadMessages > 0 ? 1 : 0)
.font(.system(size: 10))
.foregroundColor(.accentColor)
.brightness(0.2)
}
CircleText(text: user.shortName ?? "?", color: Color(UIColor(hex: UInt32(user.num))))
CircleText(text: user.shortName ?? "?", color: Color(UIColor(hex: UInt32(user.num))))
VStack(alignment: .leading) {
HStack {
if user.pkiEncrypted {
if !user.keyMatch {
/// Public Key on the User and the Public Key on the Last Message don't match
Image(systemName: "key.slash")
.foregroundColor(.red)
} else {
Image(systemName: "lock.fill")
.foregroundColor(.green)
}
VStack(alignment: .leading) {
HStack {
if user.pkiEncrypted {
if !user.keyMatch {
/// Public Key on the User and the Public Key on the Last Message don't match
Image(systemName: "key.slash")
.foregroundColor(.red)
} else {
Image(systemName: "lock.open.fill")
.foregroundColor(.yellow)
}
Text(user.longName ?? "Unknown".localized)
.font(.headline)
.allowsTightening(true)
Spacer()
if user.userNode?.favorite ?? false {
Image(systemName: "star.fill")
.foregroundColor(.yellow)
}
if user.messageList.count > 0 {
if lastMessageDay == currentDay {
Text(lastMessageTime, style: .time )
.font(.footnote)
.foregroundColor(.secondary)
} else if lastMessageDay == (currentDay - 1) {
Text("Yesterday")
.font(.footnote)
.foregroundColor(.secondary)
} else if lastMessageDay < (currentDay - 1) && lastMessageDay > (currentDay - 5) {
Text(lastMessageTime.formattedDate(format: dateFormatString))
.font(.footnote)
.foregroundColor(.secondary)
} else if lastMessageDay < (currentDay - 1800) {
Text(lastMessageTime.formattedDate(format: dateFormatString))
.font(.footnote)
.foregroundColor(.secondary)
}
Image(systemName: "lock.fill")
.foregroundColor(.green)
}
} else {
Image(systemName: "lock.open.fill")
.foregroundColor(.yellow)
}
Text(user.longName ?? "Unknown".localized)
.font(.headline)
.allowsTightening(true)
Spacer()
if user.userNode?.favorite ?? false {
Image(systemName: "star.fill")
.foregroundColor(.yellow)
}
if user.messageList.count > 0 {
HStack(alignment: .top) {
Text("\(mostRecent != nil ? mostRecent!.messagePayload! : " ")")
if lastMessageDay == currentDay {
Text(lastMessageTime, style: .time )
.font(.footnote)
.foregroundColor(.secondary)
} else if lastMessageDay == (currentDay - 1) {
Text("Yesterday")
.font(.footnote)
.foregroundColor(.secondary)
} else if lastMessageDay < (currentDay - 1) && lastMessageDay > (currentDay - 5) {
Text(lastMessageTime.formattedDate(format: dateFormatString))
.font(.footnote)
.foregroundColor(.secondary)
} else if lastMessageDay < (currentDay - 1800) {
Text(lastMessageTime.formattedDate(format: dateFormatString))
.font(.footnote)
.foregroundColor(.secondary)
}
}
}
}
.frame(height: 62)
.contextMenu {
Button {
if node != nil && !(user.userNode?.favorite ?? false) {
let success = bleManager.setFavoriteNode(node: user.userNode!, connectedNodeNum: Int64(node!.num))
if success {
user.userNode?.favorite = !(user.userNode?.favorite ?? true)
Logger.data.info("Favorited a node")
}
} else {
let success = bleManager.removeFavoriteNode(node: user.userNode!, connectedNodeNum: Int64(node!.num))
if success {
user.userNode?.favorite = !(user.userNode?.favorite ?? true)
Logger.data.info("Un Favorited a node")
}
}
context.refresh(user, mergeChanges: true)
do {
try context.save()
} catch {
context.rollback()
Logger.data.error("Save Node Favorite Error")
}
} label: {
Label((user.userNode?.favorite ?? false) ? "Un-Favorite" : "Favorite", systemImage: (user.userNode?.favorite ?? false) ? "star.slash.fill" : "star.fill")
}
Button {
user.mute = !user.mute
do {
try context.save()
} catch {
context.rollback()
Logger.data.error("Save User Mute Error")
}
} label: {
Label(user.mute ? "Show Alerts" : "Hide Alerts", systemImage: user.mute ? "bell" : "bell.slash")
}
if user.messageList.count > 0 {
Button(role: .destructive) {
isPresentingDeleteUserMessagesConfirm = true
userSelection = user
} label: {
Label("Delete Messages", systemImage: "trash")
if user.messageList.count > 0 {
HStack(alignment: .top) {
Text("\(mostRecent != nil ? mostRecent!.messagePayload! : " ")")
.font(.footnote)
.foregroundColor(.secondary)
}
}
}
.confirmationDialog(
"This conversation will be deleted.",
isPresented: $isPresentingDeleteUserMessagesConfirm,
titleVisibility: .visible
) {
Button(role: .destructive) {
deleteUserMessages(user: userSelection!, context: context)
context.refresh(node!.user!, mergeChanges: true)
} label: {
Text("Delete")
}
.frame(height: 62)
.contextMenu {
Button {
if node != nil && !(user.userNode?.favorite ?? false) {
let success = bleManager.setFavoriteNode(node: user.userNode!, connectedNodeNum: Int64(node!.num))
if success {
user.userNode?.favorite = !(user.userNode?.favorite ?? true)
Logger.data.info("Favorited a node")
}
} else {
let success = bleManager.removeFavoriteNode(node: user.userNode!, connectedNodeNum: Int64(node!.num))
if success {
user.userNode?.favorite = !(user.userNode?.favorite ?? true)
Logger.data.info("Un Favorited a node")
}
}
context.refresh(user, mergeChanges: true)
do {
try context.save()
} catch {
context.rollback()
Logger.data.error("Save Node Favorite Error")
}
} label: {
Label((user.userNode?.favorite ?? false) ? "Un-Favorite" : "Favorite", systemImage: (user.userNode?.favorite ?? false) ? "star.slash.fill" : "star.fill")
}
Button {
user.mute = !user.mute
do {
try context.save()
} catch {
context.rollback()
Logger.data.error("Save User Mute Error")
}
} label: {
Label(user.mute ? "Show Alerts" : "Hide Alerts", systemImage: user.mute ? "bell" : "bell.slash")
}
if user.messageList.count > 0 {
Button(role: .destructive) {
isPresentingDeleteUserMessagesConfirm = true
userSelection = user
} label: {
Label("Delete Messages", systemImage: "trash")
}
}
}
.confirmationDialog(
"This conversation will be deleted.",
isPresented: $isPresentingDeleteUserMessagesConfirm,
titleVisibility: .visible
) {
Button(role: .destructive) {
deleteUserMessages(user: userSelection!, context: context)
context.refresh(node!.user!, mergeChanges: true)
} label: {
Text("Delete")
}
}
}
}
.listStyle(.plain)
.navigationTitle(String.localizedStringWithFormat("Contacts (%@)".localized, String(users.count == 0 ? 0 : users.count)))
.navigationTitle(String.localizedStringWithFormat("Contacts (%@)".localized, String(users.count == 0 ? 0 : users.count - 1)))
.sheet(isPresented: $editingFilters) {
NodeListFilter(filterTitle: "Contact Filters", viaLora: $viaLora, viaMqtt: $viaMqtt, isOnline: $isOnline, isPkiEncrypted: $isPkiEncrypted, isFavorite: $isFavorite, isIgnored: $isIgnored, isEnvironment: $isEnvironment, distanceFilter: $distanceFilter, maximumDistance: $maxDistance, hopsAway: $hopsAway, roleFilter: $roleFilter, deviceRoles: $deviceRoles)
}
@ -247,7 +246,7 @@ struct UserList: View {
await searchUserList()
}
}
.onAppear {
.onFirstAppear {
Task {
await searchUserList()
}
@ -304,11 +303,11 @@ struct UserList: View {
let loraPredicate = NSPredicate(format: "userNode.viaMqtt == NO")
predicates.append(loraPredicate)
} else {
let mqttPredicate = NSPredicate(format: "userNode.viaMqtt == YES AND userNode.hopsAway == 0")
let mqttPredicate = NSPredicate(format: "userNode.viaMqtt == YES")
predicates.append(mqttPredicate)
}
} else {
let mqttPredicate = NSPredicate(format: "NOT (userNode.viaMqtt == YES AND userNode.hopsAway > 0)")
let mqttPredicate = NSPredicate(format: "NOT (userNode.viaMqtt == YES)")
predicates.append(mqttPredicate)
}
/// Roles
@ -344,6 +343,13 @@ struct UserList: View {
let isFavoritePredicate = NSPredicate(format: "userNode.favorite == YES")
predicates.append(isFavoritePredicate)
}
/// Ignored
let isIgnoredPredicate = NSPredicate(format: "userNode.ignored == NO")
predicates.append(isIgnoredPredicate)
/// Unmessagable
let isUnmessagablePredicate = NSPredicate(format: "unmessagable == NO")
predicates.append(isUnmessagablePredicate)
/// Distance
if distanceFilter {
let pointOfInterest = LocationsHandler.currentLocation
@ -362,16 +368,11 @@ struct UserList: View {
predicates.append(distancePredicate)
}
}
if predicates.count > 0 || !searchText.isEmpty {
if !searchText.isEmpty {
let filterPredicates = NSCompoundPredicate(type: .and, subpredicates: predicates)
users.nsPredicate = NSCompoundPredicate(type: .and, subpredicates: [textSearchPredicate, filterPredicates])
} else {
users.nsPredicate = NSCompoundPredicate(type: .and, subpredicates: predicates)
}
if !searchText.isEmpty {
let filterPredicates = NSCompoundPredicate(type: .and, subpredicates: predicates)
users.nsPredicate = NSCompoundPredicate(type: .and, subpredicates: [textSearchPredicate, filterPredicates])
} else {
users.nsPredicate = nil
users.nsPredicate = NSCompoundPredicate(type: .and, subpredicates: predicates)
}
}
}

View file

@ -14,7 +14,6 @@ struct UserMessageList: View {
@EnvironmentObject var appState: AppState
@EnvironmentObject var bleManager: BLEManager
@Environment(\.managedObjectContext) var context
// Keyboard State
@FocusState var messageFieldFocused: Bool
// View State Items
@ -24,16 +23,22 @@ struct UserMessageList: View {
@State private var showScrollToBottomButton = false
@State private var hasReachedBottom = false
@State private var gotFirstUnreadMessage: Bool = false
@State private var messageToHighlight: Int64 = 0
var body: some View {
VStack {
ScrollViewReader { scrollView in
ZStack(alignment: .bottomTrailing) {
ScrollView {
LazyVStack {
ForEach( user.messageList ) { (message: MessageEntity) in
ForEach( Array(user.messageList.enumerated()), id: \.element.id) { index, message in
// Get the previous message, if it exists
let previousMessage = index > 0 ? user.messageList[index - 1] : nil
if message.displayTimestamp(aboveMessage: previousMessage) {
Text(message.timestamp.formatted(date: .abbreviated, time: .shortened))
.font(.caption)
.foregroundColor(.gray)
}
if user.num != bleManager.connectedPeripheral?.num ?? -1 {
let currentUser: Bool = (Int64(UserDefaults.preferredPeripheralNum) == message.fromUser?.num ?? -1 ? true : false)
@ -46,7 +51,6 @@ struct UserMessageList: View {
messageToHighlight = messageNum
}
scrollView.scrollTo(messageNum, anchor: .center)
// Reset highlight after delay
Task {
try? await Task.sleep(nanoseconds: 1_000_000_000) // 1 second

View file

@ -40,6 +40,7 @@ struct IgnoreNodeButton: View {
Image(systemName: node.ignored ? "minus.circle.fill" : "minus.circle")
.symbolRenderingMode(.multicolor)
}
// Accessibility: Label for VoiceOver
}
}
}

View file

@ -9,25 +9,28 @@ struct TraceRouteButton: View {
private var isPresentingTraceRouteSentAlert: Bool = false
var body: some View {
Button {
RateLimitedButton(key: "traceroute", rateLimit: 30.0) {
isPresentingTraceRouteSentAlert = bleManager.sendTraceRouteRequest(
destNum: node.user?.num ?? 0,
wantResponse: true
)
} label: {
Label {
Text("Trace Route")
} icon: {
Image(systemName: "signpost.right.and.left")
.symbolRenderingMode(.hierarchical)
}
}.alert(
"Trace Route Sent",
isPresented: $isPresentingTraceRouteSentAlert
) {
Button("OK") { }.keyboardShortcut(.defaultAction)
} message: {
Text("This could take a while. The response will appear in the trace route log for the node it was sent to.")
} label: { completion in
if let completion, completion.percentComplete > 0.0 {
Label {
Text("Trace Route (in \(completion.secondsRemaining.formatted(.number.precision(.fractionLength(0))))s)")
.foregroundStyle(.secondary)
} icon: {
Image("progress.ring.dashed", variableValue: completion.percentComplete)
.foregroundStyle(.secondary)
}.disabled(true)
} else {
Label {
Text("Trace Route")
} icon: {
Image(systemName: "signpost.right.and.left")
.symbolRenderingMode(.hierarchical)
}
}
}
}
}

View file

@ -15,6 +15,7 @@ struct MeshMapContent: MapContent {
@AppStorage("meshMapShowNodeHistory") private var showNodeHistory = false
@AppStorage("meshMapShowRouteLines") private var showRouteLines = false
@AppStorage("enableMapConvexHull") private var showConvexHull = false
@AppStorage("enableMapShowFavorites") private var showFavorites = false
@Binding var showTraffic: Bool
@Binding var showPointsOfInterest: Bool
@Binding var selectedMapLayer: MapLayer
@ -39,11 +40,12 @@ struct MeshMapContent: MapContent {
@MapContentBuilder
var positionAnnotations: some MapContent {
ForEach(positions, id: \.id) { position in
/// Node color from node.num
let nodeColor = UIColor(hex: UInt32(position.nodePosition?.num ?? 0))
let positionName = position.nodePosition?.user?.longName ?? "?"
/// Latest Position Anotations
Annotation(positionName, coordinate: position.coordinate) {
if !showFavorites || (position.nodePosition?.favorite == true) {
/// Node color from node.num
let nodeColor = UIColor(hex: UInt32(position.nodePosition?.num ?? 0))
let positionName = position.nodePosition?.user?.longName ?? "?"
/// Latest Position Anotations
Annotation(positionName, coordinate: position.coordinate) {
LazyVStack {
ZStack {
let nodeColor = UIColor(hex: UInt32(position.nodePosition?.num ?? 0))
@ -59,6 +61,13 @@ struct MeshMapContent: MapContent {
.onAppear {
self.scale = 1
}
.onChange(of: showFavorites) {
scale = 0.5 // Reset to initial state
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
scale = 1
}
}
.frame(width: 60, height: 60)
}
if position.nodePosition?.hasDetectionSensorMetrics ?? false {
@ -130,7 +139,7 @@ struct MeshMapContent: MapContent {
}
}
/// Reduced Precision Map Circles
if 10...19 ~= position.precisionBits {
if 12...15 ~= position.precisionBits {
let pp = PositionPrecision(rawValue: Int(position.precisionBits))
let radius: CLLocationDistance = pp?.precisionMeters ?? 0
if radius > 0.0 {
@ -141,6 +150,8 @@ struct MeshMapContent: MapContent {
}
}
}
}
}
@MapContentBuilder

View file

@ -49,7 +49,7 @@ struct NodeMapContent: MapContent {
let pf = PositionFlags(rawValue: Int(position.nodePosition?.metadata?.positionFlags ?? 771))
let headingDegrees = Angle.degrees(Double(position.heading))
/// Reduced Precision Map Circle
if position.latest && 10...19 ~= position.precisionBits {
if position.latest && 12...15 ~= position.precisionBits {
let pp = PositionPrecision(rawValue: Int(position.precisionBits))
let radius: CLLocationDistance = pp?.precisionMeters ?? 0
if radius > 0.0 {

View file

@ -12,9 +12,10 @@ struct MapSettingsForm: View {
@Environment(\.dismiss) private var dismiss
@State private var currentDetent = PresentationDetent.medium
@AppStorage("meshMapShowNodeHistory") private var nodeHistory = false
@AppStorage("meshMapShowRouteLines") private var routeLines = false
@AppStorage("meshMapShowRouteLines") private var enableMapRouteLines = false
@AppStorage("enableMapConvexHull") private var convexHull = false
@AppStorage("enableMapWaypoints") private var waypoints = true
@AppStorage("enableMapWaypoints") private var enableMapWaypoints = true
@AppStorage("enableMapShowFavorites") private var enableMapShowFavorites = false
@Binding var traffic: Bool
@Binding var pointsOfInterest: Bool
@Binding var mapLayer: MapLayer
@ -29,7 +30,7 @@ struct MapSettingsForm: View {
Picker(selection: $mapLayer, label: Text("")) {
ForEach(MapLayer.allCases, id: \.self) { layer in
if layer != MapLayer.offline {
Text(layer.localized)
Text(layer.localized.capitalized)
}
}
}
@ -53,15 +54,25 @@ struct MapSettingsForm: View {
.onChange(of: meshMapDistance) { _, newMeshMapDistance in
UserDefaults.meshMapDistance = newMeshMapDistance
}
Toggle(isOn: $waypoints) {
Label("Show Waypoints ", systemImage: "signpost.right.and.left")
Toggle(isOn: $enableMapWaypoints) {
Label {
Text("Show Waypoints")
} icon: {
Image(systemName: "signpost.right.and.left")
.symbolRenderingMode(.multicolor)
}
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
.onTapGesture {
UserDefaults.enableMapWaypoints = !waypoints
.tint(.accentColor)
}
Toggle(isOn: $enableMapShowFavorites) {
Label {
Text("Favorites")
} icon: {
Image(systemName: "star.fill")
.symbolRenderingMode(.multicolor)
}
}
.tint(.accentColor)
Toggle(isOn: $nodeHistory) {
Label("Node History", systemImage: "building.columns.fill")
}
@ -70,15 +81,10 @@ struct MapSettingsForm: View {
self.nodeHistory.toggle()
UserDefaults.enableMapNodeHistoryPins = self.nodeHistory
}
Toggle(isOn: $routeLines) {
Toggle(isOn: $enableMapRouteLines) {
Label("Route Lines", systemImage: "road.lanes")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
.onTapGesture {
self.routeLines.toggle()
UserDefaults.enableMapRouteLines = self.routeLines
}
.tint(.accentColor)
Toggle(isOn: $convexHull) {
Label("Convex Hull", systemImage: "button.angledbottom.horizontal.right")
}
@ -96,9 +102,14 @@ struct MapSettingsForm: View {
UserDefaults.enableMapTraffic = self.traffic
}
Toggle(isOn: $pointsOfInterest) {
Label("Points of Interest", systemImage: "mappin.and.ellipse")
Label {
Text("Points of Interest")
} icon: {
Image(systemName: "mappin.and.ellipse")
.symbolRenderingMode(.multicolor)
}
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
.tint(.accentColor)
.onTapGesture {
self.pointsOfInterest.toggle()
UserDefaults.enableMapPointsOfInterest = self.pointsOfInterest

View file

@ -156,7 +156,7 @@ struct PositionPopover: View {
if lastLocation.distance(from: CLLocation(latitude: LocationsHandler.DefaultLocation.latitude, longitude: LocationsHandler.DefaultLocation.longitude)) > 0.0 {
let metersAway = position.coordinate.distance(from: CLLocationCoordinate2D(latitude: lastLocation.coordinate.latitude, longitude: lastLocation.coordinate.longitude))
Label {
Text("distance".localized + ": \(distanceFormatter.string(fromDistance: Double(metersAway)))")
Text("Distance".localized + ": \(distanceFormatter.string(fromDistance: Double(metersAway)))")
.foregroundColor(.primary)
.font(idiom == .phone ? .callout : .body)
} icon: {

View file

@ -46,7 +46,8 @@ struct NodeDetail: View {
Section("Hardware") {
NodeInfoItem(node: node)
}
Section("Node") {
.accessibilityElement(children: .combine)
Section("Node") { // Node
HStack(alignment: .center) {
Spacer()
CircleText(
@ -67,6 +68,7 @@ struct NodeDetail: View {
.foregroundColor(getRssiColor(rssi: node.rssi))
.font(.caption)
}
.accessibilityElement(children: .combine)
}
if node.telemetries?.count ?? 0 > 0 {
Spacer()
@ -74,6 +76,7 @@ struct NodeDetail: View {
}
Spacer()
}
.accessibilityElement(children: .combine)
.listRowSeparator(.hidden)
if let user = node.user {
if !user.keyMatch {
@ -86,6 +89,7 @@ struct NodeDetail: View {
.foregroundStyle(.secondary)
.font(.callout)
}
.accessibilityElement(children: .combine)
} icon: {
Image(systemName: "key.slash.fill")
.symbolRenderingMode(.multicolor)
@ -104,6 +108,7 @@ struct NodeDetail: View {
Text(String(node.num))
.textSelection(.enabled)
}
.accessibilityElement(children: .combine)
HStack {
Label {
@ -116,6 +121,7 @@ struct NodeDetail: View {
Text(node.num.toHex())
.textSelection(.enabled)
}
.accessibilityElement(children: .combine)
if let metadata = node.metadata {
HStack {
@ -129,6 +135,7 @@ struct NodeDetail: View {
Text(metadata.firmwareVersion ?? "Unknown".localized)
}
.accessibilityElement(children: .combine)
}
if let role = node.user?.role, let deviceRole = DeviceRoles(rawValue: Int(role)) {
@ -142,6 +149,20 @@ struct NodeDetail: View {
Spacer()
Text(deviceRole.name)
}
.accessibilityElement(children: .combine)
}
if node.user?.unmessagable ?? false {
HStack {
Label {
Text("Messaging")
} icon: {
Image(systemName: "iphone.slash")
.symbolRenderingMode(.multicolor)
}
Spacer()
Text("Unmonitored")
}
.accessibilityElement(children: .combine)
}
if let dm = node.telemetries?.filtered(using: NSPredicate(format: "metricsType == 0")).lastObject as? TelemetryEntity, let uptimeSeconds = dm.uptimeSeconds {
@ -161,6 +182,7 @@ struct NodeDetail: View {
Text(uptime)
.textSelection(.enabled)
}
.accessibilityElement(children: .combine)
}
if let firstHeard = node.firstHeard, firstHeard.timeIntervalSince1970 > 0 && firstHeard < Calendar.current.date(byAdding: .year, value: 1, to: Date())! {
@ -179,7 +201,9 @@ struct NodeDetail: View {
Text(firstHeard.formatted())
.textSelection(.enabled)
}
}.onTapGesture {
}
.accessibilityElement(children: .combine)
.onTapGesture {
dateFormatRelative.toggle()
}
}
@ -203,7 +227,9 @@ struct NodeDetail: View {
Text(lastHeard.formatted())
.textSelection(.enabled)
}
}.onTapGesture {
}
.accessibilityElement(children: .combine)
.onTapGesture {
dateFormatRelative.toggle()
}
}
@ -216,79 +242,84 @@ struct NodeDetail: View {
if node.hasPositions && UserDefaults.environmentEnableWeatherKit
|| node.hasDataForLatestEnvironmentMetrics(attributes: ["iaq", "temperature", "relativeHumidity", "barometricPressure", "windSpeed", "radiation", "weight", "Distance", "soilTemperature", "soilMoisture"]) {
Section("Environment") {
if !node.hasEnvironmentMetrics {
LocalWeatherConditions(location: node.latestPosition?.nodeLocation)
} else {
VStack {
if node.latestEnvironmentMetrics?.iaq ?? -1 > 0 {
IndoorAirQuality(iaq: Int(node.latestEnvironmentMetrics?.iaq ?? 0), displayMode: .gradient)
.padding(.vertical)
}
LazyVGrid(columns: gridItemLayout) {
if let temperature = node.latestEnvironmentMetrics?.temperature?.shortFormattedTemperature() {
WeatherConditionsCompactWidget(temperature: String(temperature), symbolName: "cloud.sun", description: "TEMP")
// Group weather/environment data for better VoiceOver experience
VStack {
if !node.hasEnvironmentMetrics {
LocalWeatherConditions(location: node.latestPosition?.nodeLocation)
} else {
VStack {
if node.latestEnvironmentMetrics?.iaq ?? -1 > 0 {
IndoorAirQuality(iaq: Int(node.latestEnvironmentMetrics?.iaq ?? 0), displayMode: .gradient)
.padding(.vertical)
}
if let humidity = node.latestEnvironmentMetrics?.relativeHumidity {
if let temperature = node.latestEnvironmentMetrics?.temperature {
let dewPoint = calculateDewPoint(temp: temperature, relativeHumidity: humidity)
.formatted(.number.precision(.fractionLength(0))) + "°"
HumidityCompactWidget(humidity: Int(humidity), dewPoint: dewPoint)
} else {
HumidityCompactWidget(humidity: Int(humidity), dewPoint: nil)
LazyVGrid(columns: gridItemLayout) {
if let temperature = node.latestEnvironmentMetrics?.temperature?.shortFormattedTemperature() {
WeatherConditionsCompactWidget(temperature: String(temperature), symbolName: "cloud.sun", description: "TEMP")
}
if let humidity = node.latestEnvironmentMetrics?.relativeHumidity {
if let temperature = node.latestEnvironmentMetrics?.temperature {
let dewPoint = calculateDewPoint(temp: temperature, relativeHumidity: humidity)
.formatted(.number.precision(.fractionLength(0))) + "°"
HumidityCompactWidget(humidity: Int(humidity), dewPoint: dewPoint)
} else {
HumidityCompactWidget(humidity: Int(humidity), dewPoint: nil)
}
}
if let pressure = node.latestEnvironmentMetrics?.barometricPressure {
PressureCompactWidget(pressure: pressure.formatted(.number.precision(.fractionLength(2))), unit: "hPA", low: pressure <= 1009.144)
}
if let windSpeed = node.latestEnvironmentMetrics?.windSpeed {
let windSpeedMeasurement = Measurement(value: Double(windSpeed), unit: UnitSpeed.metersPerSecond)
let windGust = node.latestEnvironmentMetrics?.windGust.map { Measurement(value: Double($0), unit: UnitSpeed.metersPerSecond) }
let direction = cardinalValue(from: Double(node.latestEnvironmentMetrics?.windDirection ?? 0))
WindCompactWidget(speed: windSpeedMeasurement.formatted(.measurement(width: .abbreviated, numberFormatStyle: .number.precision(.fractionLength(0)))),
gust: node.latestEnvironmentMetrics?.windGust ?? 0.0 > 0.0 ? windGust?.formatted(.measurement(width: .abbreviated, numberFormatStyle: .number.precision(.fractionLength(0)))) : "", direction: direction)
}
if let rainfall1h = node.latestEnvironmentMetrics?.rainfall1H {
let locale = NSLocale.current as NSLocale
let usesMetricSystem = locale.usesMetricSystem // Returns true for metric (mm), false for imperial (inches)
let unit = usesMetricSystem ? UnitLength.millimeters : UnitLength.inches
let unitLabel = usesMetricSystem ? "mm" : "in"
let measurement = Measurement(value: Double(rainfall1h), unit: UnitLength.millimeters)
let decimals = usesMetricSystem ? 0 : 1
let formattedRain = measurement.converted(to: unit).value.formatted(.number.precision(.fractionLength(decimals)))
RainfallCompactWidget(timespan: .rainfall1H, rainfall: formattedRain, unit: unitLabel)
}
if let rainfall24h = node.latestEnvironmentMetrics?.rainfall24H {
let locale = NSLocale.current as NSLocale
let usesMetricSystem = locale.usesMetricSystem // Returns true for metric (mm), false for imperial (inches)
let unit = usesMetricSystem ? UnitLength.millimeters : UnitLength.inches
let unitLabel = usesMetricSystem ? "mm" : "in"
let measurement = Measurement(value: Double(rainfall24h), unit: UnitLength.millimeters)
let decimals = usesMetricSystem ? 0 : 1
let formattedRain = measurement.converted(to: unit).value.formatted(.number.precision(.fractionLength(decimals)))
RainfallCompactWidget(timespan: .rainfall24H, rainfall: formattedRain, unit: unitLabel)
}
if let radiation = node.latestEnvironmentMetrics?.radiation {
RadiationCompactWidget(radiation: radiation.formatted(.number.precision(.fractionLength(1))), unit: "µR/hr")
}
if let weight = node.latestEnvironmentMetrics?.weight {
WeightCompactWidget(weight: weight.formatted(.number.precision(.fractionLength(1))), unit: "kg")
}
if let distance = node.latestEnvironmentMetrics?.distance {
DistanceCompactWidget(distance: distance.formatted(.number.precision(.fractionLength(0))), unit: "mm")
}
if let soilTemperature = node.latestEnvironmentMetrics?.soilTemperature {
let locale = NSLocale.current as NSLocale
let localeUnit = locale.object(forKey: NSLocale.Key(rawValue: "kCFLocaleTemperatureUnitKey"))
let unit = localeUnit as? String ?? "Celsius" == "Fahrenheit" ? "°F" : "°C"
SoilTemperatureCompactWidget(temperature: soilTemperature.localeTemperature().formatted(.number.precision(.fractionLength(0))), unit: unit)
}
if let soilMoisture = node.latestEnvironmentMetrics?.soilMoisture {
SoilMoistureCompactWidget(moisture: soilMoisture.formatted(.number.precision(.fractionLength(0))), unit: "%")
}
}
if let pressure = node.latestEnvironmentMetrics?.barometricPressure {
PressureCompactWidget(pressure: pressure.formatted(.number.precision(.fractionLength(2))), unit: "hPA", low: pressure <= 1009.144)
}
if let windSpeed = node.latestEnvironmentMetrics?.windSpeed {
let windSpeedMeasurement = Measurement(value: Double(windSpeed), unit: UnitSpeed.metersPerSecond)
let windGust = node.latestEnvironmentMetrics?.windGust.map { Measurement(value: Double($0), unit: UnitSpeed.metersPerSecond) }
let direction = cardinalValue(from: Double(node.latestEnvironmentMetrics?.windDirection ?? 0))
WindCompactWidget(speed: windSpeedMeasurement.formatted(.measurement(width: .abbreviated, numberFormatStyle: .number.precision(.fractionLength(0)))),
gust: node.latestEnvironmentMetrics?.windGust ?? 0.0 > 0.0 ? windGust?.formatted(.measurement(width: .abbreviated, numberFormatStyle: .number.precision(.fractionLength(0)))) : "", direction: direction)
}
if let rainfall1h = node.latestEnvironmentMetrics?.rainfall1H {
let locale = NSLocale.current as NSLocale
let usesMetricSystem = locale.usesMetricSystem // Returns true for metric (mm), false for imperial (inches)
let unit = usesMetricSystem ? UnitLength.millimeters : UnitLength.inches
let unitLabel = usesMetricSystem ? "mm" : "in"
let measurement = Measurement(value: Double(rainfall1h), unit: UnitLength.millimeters)
let decimals = usesMetricSystem ? 0 : 1
let formattedRain = measurement.converted(to: unit).value.formatted(.number.precision(.fractionLength(decimals)))
RainfallCompactWidget(timespan: .rainfall1H, rainfall: formattedRain, unit: unitLabel)
}
if let rainfall24h = node.latestEnvironmentMetrics?.rainfall24H {
let locale = NSLocale.current as NSLocale
let usesMetricSystem = locale.usesMetricSystem // Returns true for metric (mm), false for imperial (inches)
let unit = usesMetricSystem ? UnitLength.millimeters : UnitLength.inches
let unitLabel = usesMetricSystem ? "mm" : "in"
let measurement = Measurement(value: Double(rainfall24h), unit: UnitLength.millimeters)
let decimals = usesMetricSystem ? 0 : 1
let formattedRain = measurement.converted(to: unit).value.formatted(.number.precision(.fractionLength(decimals)))
RainfallCompactWidget(timespan: .rainfall24H, rainfall: formattedRain, unit: unitLabel)
}
if let radiation = node.latestEnvironmentMetrics?.radiation {
RadiationCompactWidget(radiation: radiation.formatted(.number.precision(.fractionLength(1))), unit: "µR/hr")
}
if let weight = node.latestEnvironmentMetrics?.weight {
WeightCompactWidget(weight: weight.formatted(.number.precision(.fractionLength(1))), unit: "kg")
}
if let distance = node.latestEnvironmentMetrics?.distance {
DistanceCompactWidget(distance: distance.formatted(.number.precision(.fractionLength(0))), unit: "mm")
}
if let soilTemperature = node.latestEnvironmentMetrics?.soilTemperature {
let locale = NSLocale.current as NSLocale
let localeUnit = locale.object(forKey: NSLocale.Key(rawValue: "kCFLocaleTemperatureUnitKey"))
let unit = localeUnit as? String ?? "Celsius" == "Fahrenheit" ? "°F" : "°C"
SoilTemperatureCompactWidget(temperature: soilTemperature.localeTemperature().formatted(.number.precision(.fractionLength(0))), unit: unit)
}
if let soilMoisture = node.latestEnvironmentMetrics?.soilMoisture {
SoilMoistureCompactWidget(moisture: soilMoisture.formatted(.number.precision(.fractionLength(0))), unit: "%")
}
.padding(node.latestEnvironmentMetrics?.iaq ?? -1 > 0 ? .bottom : .vertical)
}
.padding(node.latestEnvironmentMetrics?.iaq ?? -1 > 0 ? .bottom : .vertical)
}
}
// Apply accessibility properties to the environment section
.accessibilityElement(children: .combine)
}
}
if node.hasPowerMetrics && node.latestPowerMetrics != nil {
@ -298,6 +329,7 @@ struct NodeDetail: View {
PowerMetrics(metric: metric)
}
}
.accessibilityElement(children: .combine)
}
}
Section("Logs") {

View file

@ -31,6 +31,7 @@ struct NodeInfoItem: View {
.foregroundStyle(.gray)
.font(.callout)
}
.accessibilityElement(children: .combine)
Spacer()
}
VStack(alignment: .center) {
@ -49,9 +50,11 @@ struct NodeInfoItem: View {
.cornerRadius(5)
}
}
.accessibilityElement(children: .combine)
}
Spacer()
}
.accessibilityElement(children: .combine)
.onAppear {
Api().loadDeviceHardwareData { (hw) in
for device in hw {
@ -79,6 +82,7 @@ struct NodeInfoItem: View {
Text(String("incomplete".localized))
}
}
.accessibilityElement(children: .combine)
}
}
}

View file

@ -7,9 +7,96 @@
import SwiftUI
import CoreLocation
import Foundation
struct NodeListItem: View {
// Accessibility: Synthesized description for VoiceOver
private var accessibilityDescription: String {
var desc = ""
if let shortName = node.user?.shortName {
// Format the shortName using the String extension method
desc = shortName.formatNodeNameForVoiceOver()
} else if let longName = node.user?.longName {
desc = longName
} else {
desc = "Unknown".localized + " " + "Node".localized
}
if connected {
desc += ", currently connected"
}
if node.favorite {
desc += ", favorite"
}
if node.lastHeard != nil {
let formatter = RelativeDateTimeFormatter()
formatter.unitsStyle = .full
let relative = formatter.localizedString(for: node.lastHeard!, relativeTo: Date())
desc += ", last heard " + relative
}
if node.isOnline {
desc += ", online"
} else {
desc += ", offline"
}
let role = DeviceRoles(rawValue: Int(node.user?.role ?? 0))
if let roleName = role?.name {
desc += ", role: \(roleName)"
}
if node.hopsAway > 0 {
desc += ", \(node.hopsAway) hops away"
}
if let battery = node.latestDeviceMetrics?.batteryLevel {
// Check for plugged in and charging states, same logic as in BatteryCompact and BatteryGauge
if battery > 100 {
desc += ", " + "Plugged in".localized
} else if battery == 100 {
desc += ", " + "Charging".localized
} else {
desc += ", battery \(battery)%"
}
}
// Add distance and heading/bearing if available, but only for non-connected nodes
if !connected, let (lastPosition, myCoord) = locationData {
let nodeCoord = CLLocation(latitude: lastPosition.nodeCoordinate!.latitude, longitude: lastPosition.nodeCoordinate!.longitude)
let metersAway = nodeCoord.distance(from: myCoord)
// Distance information
let distanceFormatter = LengthFormatter()
distanceFormatter.unitStyle = .medium
let formattedDistance = distanceFormatter.string(fromMeters: metersAway)
// For VoiceOver, prepend 'Distance' (localized)
desc += ", " + String(format: "%@: %@", "Distance".localized, formattedDistance)
// Add bearing/heading information for VoiceOver
let trueBearing = getBearingBetweenTwoPoints(point1: myCoord, point2: nodeCoord)
let heading = Measurement(value: trueBearing, unit: UnitAngle.degrees)
let formattedHeading = heading.formatted(.measurement(width: .narrow, numberFormatStyle: .number.precision(.fractionLength(0))))
// Using a direct format without requiring a new localization key
desc += ", " + "Heading".localized + " " + formattedHeading
}
// Add signal strength if available
if node.snr != 0 && !node.viaMqtt {
let signalStrength: BLESignalStrength
if node.snr < -10 {
signalStrength = .weak
} else if node.snr < 5 {
signalStrength = .normal
} else {
signalStrength = .strong
}
let signalString: String
switch signalStrength {
case .weak:
signalString = "Signal strength weak".localized
case .normal:
signalString = "Signal strength normal".localized
case .strong:
signalString = "Signal strength strong".localized
}
desc += ", " + signalString
}
return desc
}
@ObservedObject var node: NodeInfoEntity
var connected: Bool
var connectedNode: Int64
@ -85,6 +172,11 @@ struct NodeListItem: View {
let role = DeviceRoles(rawValue: Int(node.user?.role ?? 0))
IconAndText(systemName: role?.systemName ?? "figure",
text: "Role: \(role?.name ?? "Unknown".localized)")
if node.user?.unmessagable ?? false {
IconAndText(systemName: "iphone.slash",
renderingMode: .multicolor,
text: "Unmonitored")
}
if node.isStoreForwardRouter {
IconAndText(systemName: "envelope.arrow.triangle.branch",
renderingMode: .multicolor,
@ -167,7 +259,10 @@ struct NodeListItem: View {
}
.padding(.top, 4)
.padding(.bottom, 4)
}
// Accessibility: Make the whole row a single element for VoiceOver
.accessibilityElement(children: .ignore)
.accessibilityLabel(accessibilityDescription)
}
}
struct DefaultIcon: View {

View file

@ -0,0 +1,92 @@
// ShareContactQRDialog.swift
// Meshtastic
//
// Created by GitHub Copilot on 5/13/25.
import SwiftUI
import CoreImage.CIFilterBuiltins
#if canImport(UIKit)
import UIKit
#endif
import CoreData
import MeshtasticProtobufs
import OSLog
struct ShareContactQRDialog: View {
let node: NodeInfo
@Environment(\.dismiss) private var dismiss
var qrString: String {
var contact = SharedContact()
contact.nodeNum = node.num
contact.user = node.user
do {
let contactString = try contact.serializedData().base64EncodedString()
return ("https://meshtastic.org/v/#" + contactString.base64ToBase64url())
} catch {
Logger.services.error("Error serializing contact: \(error)")
return ""
}
}
var qrImage: UIImage {
let context = CIContext()
let filter = CIFilter.qrCodeGenerator()
filter.setValue(Data(qrString.utf8), forKey: "inputMessage")
let transform = CGAffineTransform(scaleX: 10, y: 10)
if let outputImage = filter.outputImage?.transformed(by: transform),
let cgimg = context.createCGImage(outputImage, from: outputImage.extent) {
return UIImage(cgImage: cgimg)
}
return UIImage(systemName: "xmark.circle") ?? UIImage()
}
var body: some View {
VStack(spacing: 20) {
Text("Share Contact QR")
.font(.title2)
.padding(.top)
Text(node.user.longName)
.font(.headline)
Image(uiImage: qrImage)
.interpolation(.none)
.resizable()
.scaledToFit()
.background(Color(.systemBackground))
.cornerRadius(16)
.shadow(radius: 4)
Text("Scan this QR code to add \(node.user.longName) to another device.")
.font(.subheadline)
.multilineTextAlignment(.center)
.foregroundColor(.secondary)
ShareLink("Share QR Code & Link",
item: Image(uiImage: qrImage),
subject: Text("Add Meshtastic Node \(node.user.shortName) as a contact"),
message: Text(qrString),
preview: SharePreview("Add Meshtastic Node \(node.user.shortName) as a contact",
image: Image(uiImage: qrImage))
)
Button("Done") { dismiss() }
.buttonStyle(.borderedProminent)
.padding(.bottom)
}
.padding()
.frame(maxWidth: 350)
}
}
#if DEBUG
struct ShareContactQRDialog_Previews: PreviewProvider {
static var previews: some View {
var node = NodeInfo()
node.num = 123456
var userProto = User()
userProto.id = "!1234"
userProto.longName = "Bud"
userProto.shortName = "Bud"
userProto.hwModel = HardwareModel.tbeam
userProto.role = Config.DeviceConfig.Role.client
userProto.publicKey = Data()
node.user = userProto
return ShareContactQRDialog(node: node)
}
}
#endif

View file

@ -28,6 +28,8 @@ struct NodeList: View {
@State private var isFavorite = false
@State private var isIgnored = false
@State private var isEnvironment = false
// Force refresh ID to make SwiftUI rebuild the view hierarchy
@State private var forceRefreshID = UUID()
@State private var distanceFilter = false
@State private var maxDistance: Double = 800000
@State private var hopsAway: Double = -1.0
@ -38,6 +40,8 @@ struct NodeList: View {
@State private var isPresentingPositionFailedAlert = false
@State private var isPresentingDeleteNodeAlert = false
@State private var deleteNodeId: Int64 = 0
@State private var isPresentingShareContactQR = false
@State private var shareContactNode: NodeInfoEntity?
var boolFilters: [Bool] {[
isFavorite,
@ -76,13 +80,21 @@ struct NodeList: View {
/// Allow users to mute notifications for a node even if they are not connected
if let user = node.user {
NodeAlertsButton(context: context, node: node, user: user)
if !user.unmessagable {
Button(action: {
shareContactNode = node
isPresentingShareContactQR = true
}) {
Label("Share Contact QR", systemImage: "qrcode")
}
}
}
if let connectedNode {
/// Favoriting a node requires being connected
FavoriteNodeButton(bleManager: bleManager, context: context, node: node)
/// Don't show message, trace route, position exchange or delete context menu items for the connected node
if connectedNode.num != node.num {
if !node.viaMqtt || node.viaMqtt && node.hopsAway == 0 {
if !(node.user?.unmessagable ?? true) {
Button(action: {
if let url = URL(string: "meshtastic:///messages?userNum=\(node.num)") {
UIApplication.shared.open(url)
@ -91,21 +103,10 @@ struct NodeList: View {
Label("Message", systemImage: "message")
}
}
Button {
let traceRouteSent = bleManager.sendTraceRouteRequest(
destNum: node.num,
wantResponse: true
)
if traceRouteSent {
isPresentingTraceRouteSentAlert = true
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
isPresentingTraceRouteSentAlert = false
}
}
} label: {
Label("Trace Route", systemImage: "signpost.right.and.left")
}
TraceRouteButton(
bleManager: bleManager,
node: node
)
Button {
let positionSent = bleManager.sendPosition(
channel: node.channel,
@ -142,6 +143,7 @@ struct NodeList: View {
}
var body: some View {
// Use forceRefreshID to completely rebuild the view when notifications update the selected node
NavigationSplitView(columnVisibility: $columnVisibility) {
List(nodes, id: \.self, selection: $selectedNode) { node in
NodeListItem(
@ -231,6 +233,13 @@ struct NodeList: View {
}
}
}
}
.sheet(isPresented: $isPresentingShareContactQR) {
if let node = shareContactNode {
ShareContactQRDialog(node: node.toProto())
} else {
EmptyView()
}
}
.navigationSplitViewColumnWidth(min: 100, ideal: 250, max: 500)
.navigationBarItems(
@ -243,6 +252,8 @@ struct NodeList: View {
phoneOnly: true
)
}
// Make sure the ZStack passes through accessibility to the ConnectedDevice component
.accessibilityElement(children: .contain)
)
} content: {
if let node = selectedNode {
@ -261,6 +272,7 @@ struct NodeList: View {
} label: {
Image(systemName: "rectangle")
}
.accessibilityLabel("Hide sidebar")
}
ConnectedDevice(
bluetoothOn: bleManager.isSwitchedOn,
@ -269,6 +281,8 @@ struct NodeList: View {
phoneOnly: true
)
}
// Make sure the ZStack passes through accessibility to the ConnectedDevice component
.accessibilityElement(children: .contain)
)
}
} else {
@ -326,16 +340,40 @@ struct NodeList: View {
}
.onChange(of: router.navigationState) {
if let selected = router.navigationState.nodeListSelectedNodeNum {
self.selectedNode = getNodeInfo(id: selected, context: context)
// Force a complete view rebuild by generating a new UUID
Logger.services.info("Forcing view rebuild with new ID: \(self.forceRefreshID)")
// First clear selection
self.forceRefreshID = UUID()
self.selectedNode = nil
// Then after a short delay, set the new selection. Makes it obvious to use page is refreshing too.
DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) {
// Generate another UUID to ensure view gets rebuilt
self.forceRefreshID = UUID()
self.selectedNode = getNodeInfo(id: selected, context: context)
Logger.services.info("Complete view refresh with node: \(selected, privacy: .public)")
}
} else {
self.selectedNode = nil
}
}
.onAppear {
// Set up notification observer for forced refreshes from notifications
NotificationCenter.default.addObserver(forName: NSNotification.Name("ForceNavigationRefresh"), object: nil, queue: .main) { notification in
if let nodeNum = notification.userInfo?["nodeNum"] as? Int64 {
// Force complete refresh of view
self.forceRefreshID = UUID()
self.selectedNode = getNodeInfo(id: nodeNum, context: self.context)
Logger.services.info("NodeList directly updated from notification for node: \(nodeNum, privacy: .public)")
}
}
Task {
await searchNodeList()
}
}
.onDisappear {
// Remove observer when view disappears
NotificationCenter.default.removeObserver(self, name: NSNotification.Name("ForceNavigationRefresh"), object: nil)
}
}
private func searchNodeList() async {

View file

@ -148,7 +148,7 @@ struct ChannelForm: View {
.listRowSeparator(.visible)
.onChange(of: preciseLocation) { _, pl in
if pl == false {
positionPrecision = 14
positionPrecision = 15
}
}
}
@ -157,11 +157,11 @@ struct ChannelForm: View {
VStack(alignment: .leading) {
Label("Approximate Location", systemImage: "location.slash.circle.fill")
Slider(value: $positionPrecision, in: 11...14, step: 1) {
Slider(value: $positionPrecision, in: 12...15, step: 1) {
} minimumValueLabel: {
Image(systemName: "minus")
} maximumValueLabel: {
Image(systemName: "plus")
} maximumValueLabel: {
Image(systemName: "minus")
}
Text(PositionPrecision(rawValue: Int(positionPrecision))?.description ?? "")
.foregroundColor(.gray)
@ -228,7 +228,7 @@ struct ChannelForm: View {
.onChange(of: positionsEnabled) { _, pe in
if pe {
if positionPrecision == 0 {
positionPrecision = 14
positionPrecision = 15
}
} else {
positionPrecision = 0

View file

@ -115,8 +115,7 @@ struct BluetoothConfig: View {
}
} else {
/// Legacy Administration
Logger.mesh.info("☠️ Using insecure legacy admin, empty bluetooth config")
_ = bleManager.requestBluetoothConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0)
Logger.mesh.info("☠️ Using insecure legacy admin that is no longer supported, please upgrade your firmware.")
}
}
}

View file

@ -273,8 +273,7 @@ struct DeviceConfig: View {
} else {
if node.deviceConfig == nil {
/// Legacy Administration
Logger.mesh.info("☠️ Using insecure legacy admin, empty device config")
_ = bleManager.requestDeviceConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0)
Logger.mesh.info("☠️ Using insecure legacy admin that is no longer supported, please upgrade your firmware.")
}
}
}

View file

@ -178,8 +178,7 @@ struct DisplayConfig: View {
}
} else {
/// Legacy Administration
Logger.mesh.info("☠️ Using insecure legacy admin, empty display config")
_ = bleManager.requestDisplayConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0)
Logger.mesh.info("☠️ Using insecure legacy admin that is no longer supported, please upgrade your firmware.")
}
}
}

View file

@ -249,12 +249,13 @@ struct LoRaConfig: View {
let expiration = node.sessionExpiration ?? Date()
if expiration < Date() || node.loRaConfig == nil {
Logger.mesh.info("⚙️ Empty or expired lora config requesting via PKI admin")
_ = bleManager.requestLoRaConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0)
if connectedNode.user != nil && node.user != nil {
_ = bleManager.requestLoRaConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0)
}
}
} else {
/// Legacy Administration
Logger.mesh.info("☠️ Using insecure legacy admin, empty lora config")
_ = bleManager.requestLoRaConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0)
Logger.mesh.info("☠️ Using insecure legacy admin that is no longer supported, please upgrade your firmware.")
}
}
}

View file

@ -100,8 +100,7 @@ struct AmbientLightingConfig: View {
}
} else {
/// Legacy Administration
Logger.mesh.info("☠️ Using insecure legacy admin, empty ambient lighting module config")
_ = bleManager.requestAmbientLightingConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0)
Logger.mesh.info("☠️ Using insecure legacy admin that is no longer supported, please upgrade your firmware.")
}
}
}

View file

@ -248,8 +248,7 @@ struct CannedMessagesConfig: View {
}
} else {
/// Legacy Administration
Logger.mesh.info("☠️ Using insecure legacy admin, empty canned messages module config")
_ = bleManager.requestCannedMessagesModuleConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0)
Logger.mesh.info("☠️ Using insecure legacy admin that is no longer supported, please upgrade your firmware.")
}
}
}

View file

@ -206,8 +206,7 @@ struct DetectionSensorConfig: View {
}
} else {
/// Legacy Administration
Logger.mesh.info("☠️ Using insecure legacy admin, empty detection sensor module config")
_ = bleManager.requestDetectionSensorModuleConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0)
Logger.mesh.info("☠️ Using insecure legacy admin that is no longer supported, please upgrade your firmware.")
}
}
}

View file

@ -214,8 +214,7 @@ struct ExternalNotificationConfig: View {
}
} else {
/// Legacy Administration
Logger.mesh.info("☠️ Using insecure legacy admin, empty external notificaiton module config")
_ = bleManager.requestExternalNotificationModuleConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0)
Logger.mesh.info("☠️ Using insecure legacy admin that is no longer supported, please upgrade your firmware.")
}
}
}

View file

@ -112,7 +112,6 @@ struct MQTTConfig: View {
Label("I have read and understand the above. I voluntarily consent to the unencrypted transmission of my node data via MQTT.", systemImage: "hand.raised")
.foregroundColor(.gray)
.font(.callout)
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
}
@ -130,11 +129,11 @@ struct MQTTConfig: View {
Text("To comply with privacy laws like CCPA and GDPR, we avoid sharing exact location data. Instead, we use anonymized or approximate (imprecise) location information to protect your privacy.")
.foregroundColor(.gray)
.font(.callout)
Slider(value: $mapPositionPrecision, in: 11...14, step: 1) {
Slider(value: $mapPositionPrecision, in: 12...15, step: 1) {
} minimumValueLabel: {
Image(systemName: "minus")
} maximumValueLabel: {
Image(systemName: "plus")
} maximumValueLabel: {
Image(systemName: "minus")
}
Text(PositionPrecision(rawValue: Int(mapPositionPrecision))?.description ?? "")
.foregroundColor(.gray)
@ -365,8 +364,7 @@ struct MQTTConfig: View {
}
} else {
/// Legacy Administration
Logger.mesh.info("☠️ Using insecure legacy admin, empty mqtt module config")
_ = bleManager.requestMqttModuleConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0)
Logger.mesh.info("☠️ Using insecure legacy admin that is no longer supported, please upgrade your firmware.")
}
}
}
@ -430,8 +428,13 @@ struct MQTTConfig: View {
self.tlsEnabled = node?.mqttConfig?.tlsEnabled ?? false
self.mqttConnected = bleManager.mqttProxyConnected
self.mapReportingEnabled = node?.mqttConfig?.mapReportingEnabled ?? false
self.mapPublishIntervalSecs = Int(node?.mqttConfig?.mapPublishIntervalSecs ?? 3600)
if node?.mqttConfig?.mapPublishIntervalSecs ?? 0 < 3600 {
self.mapPublishIntervalSecs = 3600
} else {
self.mapPublishIntervalSecs = Int(node?.mqttConfig?.mapPublishIntervalSecs ?? 3600)
}
self.mapPositionPrecision = Double(node?.mqttConfig?.mapPositionPrecision ?? 14)
self.mapReportingOptIn = UserDefaults.mapReportingOptIn
if mapPositionPrecision < 11 || mapPositionPrecision > 14 {
self.mapPositionPrecision = 14
self.hasChanges = true

View file

@ -73,8 +73,7 @@ struct PaxCounterConfig: View {
}
} else {
/// Legacy Administration
Logger.mesh.info("☠️ Using insecure legacy admin, empty pax counter module config")
_ = bleManager.requestPaxCounterModuleConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0)
Logger.mesh.info("☠️ Using insecure legacy admin that is no longer supported, please upgrade your firmware.")
}
}
}

View file

@ -96,8 +96,7 @@ struct RangeTestConfig: View {
}
} else {
/// Legacy Administration
Logger.mesh.info("☠️ Using insecure legacy admin, empty range test module config")
_ = bleManager.requestRangeTestModuleConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0)
Logger.mesh.info("☠️ Using insecure legacy admin that is no longer supported, please upgrade your firmware.")
}
}
}

View file

@ -87,8 +87,7 @@ struct RtttlConfig: View {
}
} else {
/// Legacy Administration
Logger.mesh.info("☠️ Using insecure legacy admin, empty ringtone module config")
_ = bleManager.requestRtttlConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0)
Logger.mesh.info("☠️ Using insecure legacy admin that is no longer supported, please upgrade your firmware.")
}
}
}

View file

@ -151,8 +151,7 @@ struct SerialConfig: View {
}
} else {
/// Legacy Administration
Logger.mesh.info("☠️ Using insecure legacy admin, empty serial module config")
_ = bleManager.requestSerialModuleConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0)
Logger.mesh.info("☠️ Using insecure legacy admin that is no longer supported, please upgrade your firmware.")
}
}
}

View file

@ -152,8 +152,7 @@ struct StoreForwardConfig: View {
}
} else {
/// Legacy Administration
Logger.mesh.info("☠️ Using insecure legacy admin, empty store & forward module config")
_ = bleManager.requestStoreAndForwardModuleConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0)
Logger.mesh.info("☠️ Using insecure legacy admin that is no longer supported, please upgrade your firmware.")
}
}
}

View file

@ -149,8 +149,7 @@ struct TelemetryConfig: View {
}
} else {
/// Legacy Administration
Logger.mesh.info("☠️ Using insecure legacy admin, empty telemetry module config")
_ = bleManager.requestTelemetryModuleConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0)
Logger.mesh.info("☠️ Using insecure legacy admin that is no longer supported, please upgrade your firmware.")
}
}
}

View file

@ -158,8 +158,7 @@ struct NetworkConfig: View {
}
} else {
/// Legacy Administration
Logger.mesh.info("☠️ Using insecure legacy admin, empty network config")
_ = bleManager.requestNetworkConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0)
Logger.mesh.info("☠️ Using insecure legacy admin that is no longer supported, please upgrade your firmware.")
}
}
}

View file

@ -416,8 +416,7 @@ struct PositionConfig: View {
}
} else {
/// Legacy Administration
Logger.mesh.info("☠️ Using insecure legacy admin, empty position config")
_ = bleManager.requestPositionConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0)
Logger.mesh.info("☠️ Using insecure legacy admin that is no longer supported, please upgrade your firmware.")
}
}
}

View file

@ -143,8 +143,7 @@ struct PowerConfig: View {
}
} else {
/// Legacy Administration
Logger.mesh.info("☠️ Using insecure legacy admin, empty power config")
_ = bleManager.requestPowerConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0)
Logger.mesh.info("☠️ Using insecure legacy admin that is no longer supported, please upgrade your firmware.")
}
}
}

View file

@ -204,8 +204,7 @@ struct SecurityConfig: View {
} else {
if node.deviceConfig == nil {
/// Legacy Administration
Logger.mesh.info("☠️ Using insecure legacy admin, empty security config")
_ = bleManager.requestSecurityConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0)
Logger.mesh.info("☠️ Using insecure legacy admin that is no longer supported, please upgrade your firmware.")
}
}
}

View file

@ -25,13 +25,14 @@ struct UserConfig: View {
@State var hasChanges = false
@State var shortName = ""
@State var longName: String = ""
@State var isUnmessagable: Bool = false
@State var isLicensed = false
@State var overrideDutyCycle = false
@State var overrideFrequency: Float = 0.0
@State var txPower = 0
@FocusState var focusedField: Field?
public var minimumVersion = "2.6.9"
let floatFormatter: NumberFormatter = {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
@ -96,6 +97,14 @@ struct UserConfig: View {
Text("The last 4 of the device MAC address will be appended to the short name to set the device's BLE Name. Short name can be up to 4 bytes long.")
.foregroundColor(.gray)
.font(.callout)
let supportedVersion = UserDefaults.firmwareVersion == "0.0.0" || self.minimumVersion.compare(UserDefaults.firmwareVersion, options: .numeric) == .orderedAscending || minimumVersion.compare(UserDefaults.firmwareVersion, options: .numeric) == .orderedSame
Toggle(isOn: $isUnmessagable) {
Label("Unmessagable", systemImage: "iphone.slash")
Text("Used to identify unmonitored or infrastructure nodes so that messaging is not avaliable to nodes that will never respond.")
.font(.caption2)
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
.disabled(!supportedVersion)
}
// Only manage ham mode for the locally connected node
if node?.num ?? 0 > 0 && node?.num ?? 0 == bleManager.connectedPeripheral?.num ?? 0 {
@ -166,6 +175,7 @@ struct UserConfig: View {
var u = User()
u.shortName = shortName
u.longName = longName
u.isUnmessagable = isUnmessagable
let adminMessageId = bleManager.saveUser(config: u, fromUser: connectedUser, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0)
if adminMessageId > 0 {
hasChanges = false
@ -174,6 +184,7 @@ struct UserConfig: View {
} else {
var ham = HamParameters()
ham.shortName = shortName
// ham.isUnmessagable = isUnmessagable
ham.callSign = longName
ham.txPower = Int32(txPower)
ham.frequency = overrideFrequency
@ -199,6 +210,7 @@ struct UserConfig: View {
.onAppear {
self.shortName = node?.user?.shortName ?? ""
self.longName = node?.user?.longName ?? ""
self.isUnmessagable = node?.user?.unmessagable ?? false
self.isLicensed = node?.user?.isLicensed ?? false
self.txPower = Int(node?.loRaConfig?.txPower ?? 0)
self.overrideFrequency = node?.loRaConfig?.overrideFrequency ?? 0.00

View file

@ -1,5 +1,6 @@
// DO NOT EDIT.
// swift-format-ignore-file
// swiftlint:disable all
//
// Generated by the Swift generator plugin for the protocol buffer compiler.
// Source: meshtastic/admin.proto
@ -24,7 +25,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP
/// This message is handled by the Admin module and is responsible for all settings/channel read/write operations.
/// This message is used to do settings operations to both remote AND local nodes.
/// (Prior to 1.2 these operations were done via special ToRadio operations)
public struct AdminMessage {
public struct AdminMessage: @unchecked Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -487,6 +488,16 @@ public struct AdminMessage {
set {payloadVariant = .commitEditSettings(newValue)}
}
///
/// Add a contact (User) to the nodedb
public var addContact: SharedContact {
get {
if case .addContact(let v)? = payloadVariant {return v}
return SharedContact()
}
set {payloadVariant = .addContact(newValue)}
}
///
/// Tell the node to factory reset config everything; all device state and configuration will be returned to factory defaults and BLE bonds will be cleared.
public var factoryResetDevice: Int32 {
@ -563,7 +574,7 @@ public struct AdminMessage {
///
/// TODO: REPLACE
public enum OneOf_PayloadVariant: Equatable {
public enum OneOf_PayloadVariant: Equatable, Sendable {
///
/// Send the specified channel in the response to this message
/// NOTE: This field is sent with the channel index + 1 (to ensure we never try to send 'zero' - which protobufs treats as not present)
@ -705,6 +716,9 @@ public struct AdminMessage {
/// Commits an open transaction for any edits made to config, module config, owner, and channel settings
case commitEditSettings(Bool)
///
/// Add a contact (User) to the nodedb
case addContact(SharedContact)
///
/// Tell the node to factory reset config everything; all device state and configuration will be returned to factory defaults and BLE bonds will be cleared.
case factoryResetDevice(Int32)
///
@ -728,225 +742,11 @@ public struct AdminMessage {
/// Tell the node to reset the nodedb.
case nodedbReset(Int32)
#if !swift(>=4.1)
public static func ==(lhs: AdminMessage.OneOf_PayloadVariant, rhs: AdminMessage.OneOf_PayloadVariant) -> Bool {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every case branch when no optimizations are
// enabled. https://github.com/apple/swift-protobuf/issues/1034
switch (lhs, rhs) {
case (.getChannelRequest, .getChannelRequest): return {
guard case .getChannelRequest(let l) = lhs, case .getChannelRequest(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.getChannelResponse, .getChannelResponse): return {
guard case .getChannelResponse(let l) = lhs, case .getChannelResponse(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.getOwnerRequest, .getOwnerRequest): return {
guard case .getOwnerRequest(let l) = lhs, case .getOwnerRequest(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.getOwnerResponse, .getOwnerResponse): return {
guard case .getOwnerResponse(let l) = lhs, case .getOwnerResponse(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.getConfigRequest, .getConfigRequest): return {
guard case .getConfigRequest(let l) = lhs, case .getConfigRequest(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.getConfigResponse, .getConfigResponse): return {
guard case .getConfigResponse(let l) = lhs, case .getConfigResponse(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.getModuleConfigRequest, .getModuleConfigRequest): return {
guard case .getModuleConfigRequest(let l) = lhs, case .getModuleConfigRequest(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.getModuleConfigResponse, .getModuleConfigResponse): return {
guard case .getModuleConfigResponse(let l) = lhs, case .getModuleConfigResponse(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.getCannedMessageModuleMessagesRequest, .getCannedMessageModuleMessagesRequest): return {
guard case .getCannedMessageModuleMessagesRequest(let l) = lhs, case .getCannedMessageModuleMessagesRequest(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.getCannedMessageModuleMessagesResponse, .getCannedMessageModuleMessagesResponse): return {
guard case .getCannedMessageModuleMessagesResponse(let l) = lhs, case .getCannedMessageModuleMessagesResponse(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.getDeviceMetadataRequest, .getDeviceMetadataRequest): return {
guard case .getDeviceMetadataRequest(let l) = lhs, case .getDeviceMetadataRequest(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.getDeviceMetadataResponse, .getDeviceMetadataResponse): return {
guard case .getDeviceMetadataResponse(let l) = lhs, case .getDeviceMetadataResponse(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.getRingtoneRequest, .getRingtoneRequest): return {
guard case .getRingtoneRequest(let l) = lhs, case .getRingtoneRequest(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.getRingtoneResponse, .getRingtoneResponse): return {
guard case .getRingtoneResponse(let l) = lhs, case .getRingtoneResponse(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.getDeviceConnectionStatusRequest, .getDeviceConnectionStatusRequest): return {
guard case .getDeviceConnectionStatusRequest(let l) = lhs, case .getDeviceConnectionStatusRequest(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.getDeviceConnectionStatusResponse, .getDeviceConnectionStatusResponse): return {
guard case .getDeviceConnectionStatusResponse(let l) = lhs, case .getDeviceConnectionStatusResponse(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.setHamMode, .setHamMode): return {
guard case .setHamMode(let l) = lhs, case .setHamMode(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.getNodeRemoteHardwarePinsRequest, .getNodeRemoteHardwarePinsRequest): return {
guard case .getNodeRemoteHardwarePinsRequest(let l) = lhs, case .getNodeRemoteHardwarePinsRequest(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.getNodeRemoteHardwarePinsResponse, .getNodeRemoteHardwarePinsResponse): return {
guard case .getNodeRemoteHardwarePinsResponse(let l) = lhs, case .getNodeRemoteHardwarePinsResponse(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.enterDfuModeRequest, .enterDfuModeRequest): return {
guard case .enterDfuModeRequest(let l) = lhs, case .enterDfuModeRequest(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.deleteFileRequest, .deleteFileRequest): return {
guard case .deleteFileRequest(let l) = lhs, case .deleteFileRequest(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.setScale, .setScale): return {
guard case .setScale(let l) = lhs, case .setScale(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.backupPreferences, .backupPreferences): return {
guard case .backupPreferences(let l) = lhs, case .backupPreferences(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.restorePreferences, .restorePreferences): return {
guard case .restorePreferences(let l) = lhs, case .restorePreferences(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.removeBackupPreferences, .removeBackupPreferences): return {
guard case .removeBackupPreferences(let l) = lhs, case .removeBackupPreferences(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.setOwner, .setOwner): return {
guard case .setOwner(let l) = lhs, case .setOwner(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.setChannel, .setChannel): return {
guard case .setChannel(let l) = lhs, case .setChannel(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.setConfig, .setConfig): return {
guard case .setConfig(let l) = lhs, case .setConfig(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.setModuleConfig, .setModuleConfig): return {
guard case .setModuleConfig(let l) = lhs, case .setModuleConfig(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.setCannedMessageModuleMessages, .setCannedMessageModuleMessages): return {
guard case .setCannedMessageModuleMessages(let l) = lhs, case .setCannedMessageModuleMessages(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.setRingtoneMessage, .setRingtoneMessage): return {
guard case .setRingtoneMessage(let l) = lhs, case .setRingtoneMessage(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.removeByNodenum, .removeByNodenum): return {
guard case .removeByNodenum(let l) = lhs, case .removeByNodenum(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.setFavoriteNode, .setFavoriteNode): return {
guard case .setFavoriteNode(let l) = lhs, case .setFavoriteNode(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.removeFavoriteNode, .removeFavoriteNode): return {
guard case .removeFavoriteNode(let l) = lhs, case .removeFavoriteNode(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.setFixedPosition, .setFixedPosition): return {
guard case .setFixedPosition(let l) = lhs, case .setFixedPosition(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.removeFixedPosition, .removeFixedPosition): return {
guard case .removeFixedPosition(let l) = lhs, case .removeFixedPosition(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.setTimeOnly, .setTimeOnly): return {
guard case .setTimeOnly(let l) = lhs, case .setTimeOnly(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.getUiConfigRequest, .getUiConfigRequest): return {
guard case .getUiConfigRequest(let l) = lhs, case .getUiConfigRequest(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.getUiConfigResponse, .getUiConfigResponse): return {
guard case .getUiConfigResponse(let l) = lhs, case .getUiConfigResponse(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.storeUiConfig, .storeUiConfig): return {
guard case .storeUiConfig(let l) = lhs, case .storeUiConfig(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.setIgnoredNode, .setIgnoredNode): return {
guard case .setIgnoredNode(let l) = lhs, case .setIgnoredNode(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.removeIgnoredNode, .removeIgnoredNode): return {
guard case .removeIgnoredNode(let l) = lhs, case .removeIgnoredNode(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.beginEditSettings, .beginEditSettings): return {
guard case .beginEditSettings(let l) = lhs, case .beginEditSettings(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.commitEditSettings, .commitEditSettings): return {
guard case .commitEditSettings(let l) = lhs, case .commitEditSettings(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.factoryResetDevice, .factoryResetDevice): return {
guard case .factoryResetDevice(let l) = lhs, case .factoryResetDevice(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.rebootOtaSeconds, .rebootOtaSeconds): return {
guard case .rebootOtaSeconds(let l) = lhs, case .rebootOtaSeconds(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.exitSimulator, .exitSimulator): return {
guard case .exitSimulator(let l) = lhs, case .exitSimulator(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.rebootSeconds, .rebootSeconds): return {
guard case .rebootSeconds(let l) = lhs, case .rebootSeconds(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.shutdownSeconds, .shutdownSeconds): return {
guard case .shutdownSeconds(let l) = lhs, case .shutdownSeconds(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.factoryResetConfig, .factoryResetConfig): return {
guard case .factoryResetConfig(let l) = lhs, case .factoryResetConfig(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.nodedbReset, .nodedbReset): return {
guard case .nodedbReset(let l) = lhs, case .nodedbReset(let r) = rhs else { preconditionFailure() }
return l == r
}()
default: return false
}
}
#endif
}
///
/// TODO: REPLACE
public enum ConfigType: SwiftProtobuf.Enum {
public enum ConfigType: SwiftProtobuf.Enum, Swift.CaseIterable {
public typealias RawValue = Int
///
@ -1026,11 +826,25 @@ public struct AdminMessage {
}
}
// The compiler won't synthesize support with the UNRECOGNIZED case.
public static let allCases: [AdminMessage.ConfigType] = [
.deviceConfig,
.positionConfig,
.powerConfig,
.networkConfig,
.displayConfig,
.loraConfig,
.bluetoothConfig,
.securityConfig,
.sessionkeyConfig,
.deviceuiConfig,
]
}
///
/// TODO: REPLACE
public enum ModuleConfigType: SwiftProtobuf.Enum {
public enum ModuleConfigType: SwiftProtobuf.Enum, Swift.CaseIterable {
public typealias RawValue = Int
///
@ -1128,9 +942,26 @@ public struct AdminMessage {
}
}
// The compiler won't synthesize support with the UNRECOGNIZED case.
public static let allCases: [AdminMessage.ModuleConfigType] = [
.mqttConfig,
.serialConfig,
.extnotifConfig,
.storeforwardConfig,
.rangetestConfig,
.telemetryConfig,
.cannedmsgConfig,
.audioConfig,
.remotehardwareConfig,
.neighborinfoConfig,
.ambientlightingConfig,
.detectionsensorConfig,
.paxcounterConfig,
]
}
public enum BackupLocation: SwiftProtobuf.Enum {
public enum BackupLocation: SwiftProtobuf.Enum, Swift.CaseIterable {
public typealias RawValue = Int
///
@ -1162,61 +993,20 @@ public struct AdminMessage {
}
}
// The compiler won't synthesize support with the UNRECOGNIZED case.
public static let allCases: [AdminMessage.BackupLocation] = [
.flash,
.sd,
]
}
public init() {}
}
#if swift(>=4.2)
extension AdminMessage.ConfigType: CaseIterable {
// The compiler won't synthesize support with the UNRECOGNIZED case.
public static let allCases: [AdminMessage.ConfigType] = [
.deviceConfig,
.positionConfig,
.powerConfig,
.networkConfig,
.displayConfig,
.loraConfig,
.bluetoothConfig,
.securityConfig,
.sessionkeyConfig,
.deviceuiConfig,
]
}
extension AdminMessage.ModuleConfigType: CaseIterable {
// The compiler won't synthesize support with the UNRECOGNIZED case.
public static let allCases: [AdminMessage.ModuleConfigType] = [
.mqttConfig,
.serialConfig,
.extnotifConfig,
.storeforwardConfig,
.rangetestConfig,
.telemetryConfig,
.cannedmsgConfig,
.audioConfig,
.remotehardwareConfig,
.neighborinfoConfig,
.ambientlightingConfig,
.detectionsensorConfig,
.paxcounterConfig,
]
}
extension AdminMessage.BackupLocation: CaseIterable {
// The compiler won't synthesize support with the UNRECOGNIZED case.
public static let allCases: [AdminMessage.BackupLocation] = [
.flash,
.sd,
]
}
#endif // swift(>=4.2)
///
/// Parameters for setting up Meshtastic for ameteur radio usage
public struct HamParameters {
public struct HamParameters: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -1246,7 +1036,7 @@ public struct HamParameters {
///
/// Response envelope for node_remote_hardware_pins
public struct NodeRemoteHardwarePinsResponse {
public struct NodeRemoteHardwarePinsResponse: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -1260,15 +1050,32 @@ public struct NodeRemoteHardwarePinsResponse {
public init() {}
}
#if swift(>=5.5) && canImport(_Concurrency)
extension AdminMessage: @unchecked Sendable {}
extension AdminMessage.OneOf_PayloadVariant: @unchecked Sendable {}
extension AdminMessage.ConfigType: @unchecked Sendable {}
extension AdminMessage.ModuleConfigType: @unchecked Sendable {}
extension AdminMessage.BackupLocation: @unchecked Sendable {}
extension HamParameters: @unchecked Sendable {}
extension NodeRemoteHardwarePinsResponse: @unchecked Sendable {}
#endif // swift(>=5.5) && canImport(_Concurrency)
public struct SharedContact: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
///
/// The node number of the contact
public var nodeNum: UInt32 = 0
///
/// The User of the contact
public var user: User {
get {return _user ?? User()}
set {_user = newValue}
}
/// Returns true if `user` has been explicitly set.
public var hasUser: Bool {return self._user != nil}
/// Clears the value of `user`. Subsequent reads from it will return its default value.
public mutating func clearUser() {self._user = nil}
public var unknownFields = SwiftProtobuf.UnknownStorage()
public init() {}
fileprivate var _user: User? = nil
}
// MARK: - Code below here is support for the SwiftProtobuf runtime.
@ -1322,6 +1129,7 @@ extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat
48: .standard(proto: "remove_ignored_node"),
64: .standard(proto: "begin_edit_settings"),
65: .standard(proto: "commit_edit_settings"),
66: .standard(proto: "add_contact"),
94: .standard(proto: "factory_reset_device"),
95: .standard(proto: "reboot_ota_seconds"),
96: .standard(proto: "exit_simulator"),
@ -1764,6 +1572,19 @@ extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat
self.payloadVariant = .commitEditSettings(v)
}
}()
case 66: try {
var v: SharedContact?
var hadOneofValue = false
if let current = self.payloadVariant {
hadOneofValue = true
if case .addContact(let m) = current {v = m}
}
try decoder.decodeSingularMessageField(value: &v)
if let v = v {
if hadOneofValue {try decoder.handleConflictingOneOf()}
self.payloadVariant = .addContact(v)
}
}()
case 94: try {
var v: Int32?
try decoder.decodeSingularInt32Field(value: &v)
@ -2008,6 +1829,10 @@ extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat
guard case .commitEditSettings(let v)? = self.payloadVariant else { preconditionFailure() }
try visitor.visitSingularBoolField(value: v, fieldNumber: 65)
}()
case .addContact?: try {
guard case .addContact(let v)? = self.payloadVariant else { preconditionFailure() }
try visitor.visitSingularMessageField(value: v, fieldNumber: 66)
}()
case .factoryResetDevice?: try {
guard case .factoryResetDevice(let v)? = self.payloadVariant else { preconditionFailure() }
try visitor.visitSingularInt32Field(value: v, fieldNumber: 94)
@ -2123,7 +1948,7 @@ extension HamParameters: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementa
if self.txPower != 0 {
try visitor.visitSingularInt32Field(value: self.txPower, fieldNumber: 2)
}
if self.frequency != 0 {
if self.frequency.bitPattern != 0 {
try visitor.visitSingularFloatField(value: self.frequency, fieldNumber: 3)
}
if !self.shortName.isEmpty {
@ -2173,3 +1998,45 @@ extension NodeRemoteHardwarePinsResponse: SwiftProtobuf.Message, SwiftProtobuf._
return true
}
}
extension SharedContact: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
public static let protoMessageName: String = _protobuf_package + ".SharedContact"
public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .standard(proto: "node_num"),
2: .same(proto: "user"),
]
public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
while let fieldNumber = try decoder.nextFieldNumber() {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every case branch when no optimizations are
// enabled. https://github.com/apple/swift-protobuf/issues/1034
switch fieldNumber {
case 1: try { try decoder.decodeSingularUInt32Field(value: &self.nodeNum) }()
case 2: try { try decoder.decodeSingularMessageField(value: &self._user) }()
default: break
}
}
}
public func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every if/case branch local when no optimizations
// are enabled. https://github.com/apple/swift-protobuf/issues/1034 and
// https://github.com/apple/swift-protobuf/issues/1182
if self.nodeNum != 0 {
try visitor.visitSingularUInt32Field(value: self.nodeNum, fieldNumber: 1)
}
try { if let v = self._user {
try visitor.visitSingularMessageField(value: v, fieldNumber: 2)
} }()
try unknownFields.traverse(visitor: &visitor)
}
public static func ==(lhs: SharedContact, rhs: SharedContact) -> Bool {
if lhs.nodeNum != rhs.nodeNum {return false}
if lhs._user != rhs._user {return false}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}
}

View file

@ -1,5 +1,6 @@
// DO NOT EDIT.
// swift-format-ignore-file
// swiftlint:disable all
//
// Generated by the Swift generator plugin for the protocol buffer compiler.
// Source: meshtastic/apponly.proto
@ -7,7 +8,6 @@
// For information on using the generated types, please see the documentation:
// https://github.com/apple/swift-protobuf/
import Foundation
import SwiftProtobuf
// If the compiler emits an error on this type, it is because this file
@ -26,7 +26,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP
/// any SECONDARY channels.
/// No DISABLED channels are included.
/// This abstraction is used only on the the 'app side' of the world (ie python, javascript and android etc) to show a group of Channels as a (long) URL
public struct ChannelSet {
public struct ChannelSet: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -53,10 +53,6 @@ public struct ChannelSet {
fileprivate var _loraConfig: Config.LoRaConfig? = nil
}
#if swift(>=5.5) && canImport(_Concurrency)
extension ChannelSet: @unchecked Sendable {}
#endif // swift(>=5.5) && canImport(_Concurrency)
// MARK: - Code below here is support for the SwiftProtobuf runtime.
fileprivate let _protobuf_package = "meshtastic"

View file

@ -1,5 +1,6 @@
// DO NOT EDIT.
// swift-format-ignore-file
// swiftlint:disable all
//
// Generated by the Swift generator plugin for the protocol buffer compiler.
// Source: meshtastic/atak.proto
@ -20,7 +21,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP
typealias Version = _2
}
public enum Team: SwiftProtobuf.Enum {
public enum Team: SwiftProtobuf.Enum, Swift.CaseIterable {
public typealias RawValue = Int
///
@ -130,11 +131,6 @@ public enum Team: SwiftProtobuf.Enum {
}
}
}
#if swift(>=4.2)
extension Team: CaseIterable {
// The compiler won't synthesize support with the UNRECOGNIZED case.
public static let allCases: [Team] = [
.unspecifedColor,
@ -153,13 +149,12 @@ extension Team: CaseIterable {
.darkGreen,
.brown,
]
}
#endif // swift(>=4.2)
}
///
/// Role of the group member
public enum MemberRole: SwiftProtobuf.Enum {
public enum MemberRole: SwiftProtobuf.Enum, Swift.CaseIterable {
public typealias RawValue = Int
///
@ -233,11 +228,6 @@ public enum MemberRole: SwiftProtobuf.Enum {
}
}
}
#if swift(>=4.2)
extension MemberRole: CaseIterable {
// The compiler won't synthesize support with the UNRECOGNIZED case.
public static let allCases: [MemberRole] = [
.unspecifed,
@ -250,13 +240,12 @@ extension MemberRole: CaseIterable {
.rto,
.k9,
]
}
#endif // swift(>=4.2)
}
///
/// Packets for the official ATAK Plugin
public struct TAKPacket {
public struct TAKPacket: @unchecked Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -337,7 +326,7 @@ public struct TAKPacket {
///
/// The payload of the packet
public enum OneOf_PayloadVariant: Equatable {
public enum OneOf_PayloadVariant: Equatable, @unchecked Sendable {
///
/// TAK position report
case pli(PLI)
@ -349,28 +338,6 @@ public struct TAKPacket {
/// May be compressed / truncated by the sender (EUD)
case detail(Data)
#if !swift(>=4.1)
public static func ==(lhs: TAKPacket.OneOf_PayloadVariant, rhs: TAKPacket.OneOf_PayloadVariant) -> Bool {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every case branch when no optimizations are
// enabled. https://github.com/apple/swift-protobuf/issues/1034
switch (lhs, rhs) {
case (.pli, .pli): return {
guard case .pli(let l) = lhs, case .pli(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.chat, .chat): return {
guard case .chat(let l) = lhs, case .chat(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.detail, .detail): return {
guard case .detail(let l) = lhs, case .detail(let r) = rhs else { preconditionFailure() }
return l == r
}()
default: return false
}
}
#endif
}
public init() {}
@ -382,7 +349,7 @@ public struct TAKPacket {
///
/// ATAK GeoChat message
public struct GeoChat {
public struct GeoChat: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -424,7 +391,7 @@ public struct GeoChat {
///
/// ATAK Group
/// <__group role='Team Member' name='Cyan'/>
public struct Group {
public struct Group: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -446,7 +413,7 @@ public struct Group {
///
/// ATAK EUD Status
/// <status battery='100' />
public struct Status {
public struct Status: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -463,7 +430,7 @@ public struct Status {
///
/// ATAK Contact
/// <contact endpoint='0.0.0.0:4242:tcp' phone='+12345678' callsign='FALKE'/>
public struct Contact {
public struct Contact: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -483,7 +450,7 @@ public struct Contact {
///
/// Position Location Information from ATAK
public struct PLI {
public struct PLI: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -515,18 +482,6 @@ public struct PLI {
public init() {}
}
#if swift(>=5.5) && canImport(_Concurrency)
extension Team: @unchecked Sendable {}
extension MemberRole: @unchecked Sendable {}
extension TAKPacket: @unchecked Sendable {}
extension TAKPacket.OneOf_PayloadVariant: @unchecked Sendable {}
extension GeoChat: @unchecked Sendable {}
extension Group: @unchecked Sendable {}
extension Status: @unchecked Sendable {}
extension Contact: @unchecked Sendable {}
extension PLI: @unchecked Sendable {}
#endif // swift(>=5.5) && canImport(_Concurrency)
// MARK: - Code below here is support for the SwiftProtobuf runtime.
fileprivate let _protobuf_package = "meshtastic"

View file

@ -1,5 +1,6 @@
// DO NOT EDIT.
// swift-format-ignore-file
// swiftlint:disable all
//
// Generated by the Swift generator plugin for the protocol buffer compiler.
// Source: meshtastic/cannedmessages.proto
@ -7,7 +8,6 @@
// For information on using the generated types, please see the documentation:
// https://github.com/apple/swift-protobuf/
import Foundation
import SwiftProtobuf
// If the compiler emits an error on this type, it is because this file
@ -22,7 +22,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP
///
/// Canned message module configuration.
public struct CannedMessageModuleConfig {
public struct CannedMessageModuleConfig: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -36,10 +36,6 @@ public struct CannedMessageModuleConfig {
public init() {}
}
#if swift(>=5.5) && canImport(_Concurrency)
extension CannedMessageModuleConfig: @unchecked Sendable {}
#endif // swift(>=5.5) && canImport(_Concurrency)
// MARK: - Code below here is support for the SwiftProtobuf runtime.
fileprivate let _protobuf_package = "meshtastic"

View file

@ -1,5 +1,6 @@
// DO NOT EDIT.
// swift-format-ignore-file
// swiftlint:disable all
//
// Generated by the Swift generator plugin for the protocol buffer compiler.
// Source: meshtastic/channel.proto
@ -36,13 +37,15 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP
/// FIXME: Add description of multi-channel support and how primary vs secondary channels are used.
/// FIXME: explain how apps use channels for security.
/// explain how remote settings and remote gpio are managed as an example
public struct ChannelSettings {
public struct ChannelSettings: @unchecked Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
///
/// Deprecated in favor of LoraConfig.channel_num
///
/// NOTE: This field was marked as deprecated in the .proto file.
public var channelNum: UInt32 = 0
///
@ -111,7 +114,7 @@ public struct ChannelSettings {
///
/// This message is specifically for modules to store per-channel configuration data.
public struct ModuleSettings {
public struct ModuleSettings: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -132,7 +135,7 @@ public struct ModuleSettings {
///
/// A pair of a channel number, mode and the (sharable) settings for that channel
public struct Channel {
public struct Channel: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -170,7 +173,7 @@ public struct Channel {
/// cross band routing as needed.
/// If a device has only a single radio (the common case) only one channel can be PRIMARY at a time
/// (but any number of SECONDARY channels can't be sent received on that common frequency)
public enum Role: SwiftProtobuf.Enum {
public enum Role: SwiftProtobuf.Enum, Swift.CaseIterable {
public typealias RawValue = Int
///
@ -209,6 +212,13 @@ public struct Channel {
}
}
// The compiler won't synthesize support with the UNRECOGNIZED case.
public static let allCases: [Channel.Role] = [
.disabled,
.primary,
.secondary,
]
}
public init() {}
@ -216,26 +226,6 @@ public struct Channel {
fileprivate var _settings: ChannelSettings? = nil
}
#if swift(>=4.2)
extension Channel.Role: CaseIterable {
// The compiler won't synthesize support with the UNRECOGNIZED case.
public static let allCases: [Channel.Role] = [
.disabled,
.primary,
.secondary,
]
}
#endif // swift(>=4.2)
#if swift(>=5.5) && canImport(_Concurrency)
extension ChannelSettings: @unchecked Sendable {}
extension ModuleSettings: @unchecked Sendable {}
extension Channel: @unchecked Sendable {}
extension Channel.Role: @unchecked Sendable {}
#endif // swift(>=5.5) && canImport(_Concurrency)
// MARK: - Code below here is support for the SwiftProtobuf runtime.
fileprivate let _protobuf_package = "meshtastic"

View file

@ -1,5 +1,6 @@
// DO NOT EDIT.
// swift-format-ignore-file
// swiftlint:disable all
//
// Generated by the Swift generator plugin for the protocol buffer compiler.
// Source: meshtastic/clientonly.proto
@ -7,7 +8,6 @@
// For information on using the generated types, please see the documentation:
// https://github.com/apple/swift-protobuf/
import Foundation
import SwiftProtobuf
// If the compiler emits an error on this type, it is because this file
@ -23,7 +23,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP
///
/// This abstraction is used to contain any configuration for provisioning a node on any client.
/// It is useful for importing and exporting configurations.
public struct DeviceProfile {
public struct DeviceProfile: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -130,10 +130,6 @@ public struct DeviceProfile {
fileprivate var _cannedMessages: String? = nil
}
#if swift(>=5.5) && canImport(_Concurrency)
extension DeviceProfile: @unchecked Sendable {}
#endif // swift(>=5.5) && canImport(_Concurrency)
// MARK: - Code below here is support for the SwiftProtobuf runtime.
fileprivate let _protobuf_package = "meshtastic"

View file

@ -1,5 +1,6 @@
// DO NOT EDIT.
// swift-format-ignore-file
// swiftlint:disable all
//
// Generated by the Swift generator plugin for the protocol buffer compiler.
// Source: meshtastic/config.proto
@ -20,7 +21,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP
typealias Version = _2
}
public struct Config {
public struct Config: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -113,7 +114,7 @@ public struct Config {
///
/// Payload Variant
public enum OneOf_PayloadVariant: Equatable {
public enum OneOf_PayloadVariant: Equatable, Sendable {
case device(Config.DeviceConfig)
case position(Config.PositionConfig)
case power(Config.PowerConfig)
@ -125,61 +126,11 @@ public struct Config {
case sessionkey(Config.SessionkeyConfig)
case deviceUi(DeviceUIConfig)
#if !swift(>=4.1)
public static func ==(lhs: Config.OneOf_PayloadVariant, rhs: Config.OneOf_PayloadVariant) -> Bool {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every case branch when no optimizations are
// enabled. https://github.com/apple/swift-protobuf/issues/1034
switch (lhs, rhs) {
case (.device, .device): return {
guard case .device(let l) = lhs, case .device(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.position, .position): return {
guard case .position(let l) = lhs, case .position(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.power, .power): return {
guard case .power(let l) = lhs, case .power(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.network, .network): return {
guard case .network(let l) = lhs, case .network(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.display, .display): return {
guard case .display(let l) = lhs, case .display(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.lora, .lora): return {
guard case .lora(let l) = lhs, case .lora(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.bluetooth, .bluetooth): return {
guard case .bluetooth(let l) = lhs, case .bluetooth(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.security, .security): return {
guard case .security(let l) = lhs, case .security(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.sessionkey, .sessionkey): return {
guard case .sessionkey(let l) = lhs, case .sessionkey(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.deviceUi, .deviceUi): return {
guard case .deviceUi(let l) = lhs, case .deviceUi(let r) = rhs else { preconditionFailure() }
return l == r
}()
default: return false
}
}
#endif
}
///
/// Configuration
public struct DeviceConfig {
public struct DeviceConfig: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -191,6 +142,8 @@ public struct Config {
///
/// Disabling this will disable the SerialConsole by not initilizing the StreamAPI
/// Moved to SecurityConfig
///
/// NOTE: This field was marked as deprecated in the .proto file.
public var serialEnabled: Bool = false
///
@ -220,6 +173,8 @@ public struct Config {
/// If true, device is considered to be "managed" by a mesh administrator
/// Clients should then limit available configuration and administrative options inside the user interface
/// Moved to SecurityConfig
///
/// NOTE: This field was marked as deprecated in the .proto file.
public var isManaged: Bool = false
///
@ -238,7 +193,7 @@ public struct Config {
///
/// Defines the device's role on the Mesh network
public enum Role: SwiftProtobuf.Enum {
public enum Role: SwiftProtobuf.Enum, Swift.CaseIterable {
public typealias RawValue = Int
///
@ -256,6 +211,8 @@ public struct Config {
/// The wifi radio and the oled screen will be put to sleep.
/// This mode may still potentially have higher power usage due to it's preference in message rebroadcasting on the mesh.
case router // = 2
/// NOTE: This enum value was marked as deprecated in the .proto file
case routerClient // = 3
///
@ -356,11 +313,27 @@ public struct Config {
}
}
// The compiler won't synthesize support with the UNRECOGNIZED case.
public static let allCases: [Config.DeviceConfig.Role] = [
.client,
.clientMute,
.router,
.routerClient,
.repeater,
.tracker,
.sensor,
.tak,
.clientHidden,
.lostAndFound,
.takTracker,
.routerLate,
]
}
///
/// Defines the device's behavior for how messages are rebroadcast
public enum RebroadcastMode: SwiftProtobuf.Enum {
public enum RebroadcastMode: SwiftProtobuf.Enum, Swift.CaseIterable {
public typealias RawValue = Int
///
@ -421,6 +394,16 @@ public struct Config {
}
}
// The compiler won't synthesize support with the UNRECOGNIZED case.
public static let allCases: [Config.DeviceConfig.RebroadcastMode] = [
.all,
.allSkipDecoding,
.localOnly,
.knownOnly,
.none,
.corePortnumsOnly,
]
}
public init() {}
@ -428,7 +411,7 @@ public struct Config {
///
/// Position Config
public struct PositionConfig {
public struct PositionConfig: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -450,6 +433,8 @@ public struct Config {
///
/// Is GPS enabled for this node?
///
/// NOTE: This field was marked as deprecated in the .proto file.
public var gpsEnabled: Bool = false
///
@ -460,6 +445,8 @@ public struct Config {
///
/// Deprecated in favor of using smart / regular broadcast intervals as implicit attempt time
///
/// NOTE: This field was marked as deprecated in the .proto file.
public var gpsAttemptTime: UInt32 = 0
///
@ -500,7 +487,7 @@ public struct Config {
/// are always included (also time if GPS-synced)
/// NOTE: the more fields are included, the larger the message will be -
/// leading to longer airtime and a higher risk of packet loss
public enum PositionFlags: SwiftProtobuf.Enum {
public enum PositionFlags: SwiftProtobuf.Enum, Swift.CaseIterable {
public typealias RawValue = Int
///
@ -590,9 +577,24 @@ public struct Config {
}
}
// The compiler won't synthesize support with the UNRECOGNIZED case.
public static let allCases: [Config.PositionConfig.PositionFlags] = [
.unset,
.altitude,
.altitudeMsl,
.geoidalSeparation,
.dop,
.hvdop,
.satinview,
.seqNo,
.timestamp,
.heading,
.speed,
]
}
public enum GpsMode: SwiftProtobuf.Enum {
public enum GpsMode: SwiftProtobuf.Enum, Swift.CaseIterable {
public typealias RawValue = Int
///
@ -630,6 +632,13 @@ public struct Config {
}
}
// The compiler won't synthesize support with the UNRECOGNIZED case.
public static let allCases: [Config.PositionConfig.GpsMode] = [
.disabled,
.enabled,
.notPresent,
]
}
public init() {}
@ -638,7 +647,7 @@ public struct Config {
///
/// Power Config\
/// See [Power Config](/docs/settings/config/power) for additional power config details.
public struct PowerConfig {
public struct PowerConfig: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -698,7 +707,7 @@ public struct Config {
///
/// Network Config
public struct NetworkConfig {
public struct NetworkConfig: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -749,7 +758,7 @@ public struct Config {
public var unknownFields = SwiftProtobuf.UnknownStorage()
public enum AddressMode: SwiftProtobuf.Enum {
public enum AddressMode: SwiftProtobuf.Enum, Swift.CaseIterable {
public typealias RawValue = Int
///
@ -781,11 +790,17 @@ public struct Config {
}
}
// The compiler won't synthesize support with the UNRECOGNIZED case.
public static let allCases: [Config.NetworkConfig.AddressMode] = [
.dhcp,
.static,
]
}
///
/// Available flags auxiliary network protocols
public enum ProtocolFlags: SwiftProtobuf.Enum {
public enum ProtocolFlags: SwiftProtobuf.Enum, Swift.CaseIterable {
public typealias RawValue = Int
///
@ -817,9 +832,15 @@ public struct Config {
}
}
// The compiler won't synthesize support with the UNRECOGNIZED case.
public static let allCases: [Config.NetworkConfig.ProtocolFlags] = [
.noBroadcast,
.udpBroadcast,
]
}
public struct IpV4Config {
public struct IpV4Config: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -852,7 +873,7 @@ public struct Config {
///
/// Display Config
public struct DisplayConfig {
public struct DisplayConfig: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -913,7 +934,7 @@ public struct Config {
///
/// How the GPS coordinates are displayed on the OLED screen.
public enum GpsCoordinateFormat: SwiftProtobuf.Enum {
public enum GpsCoordinateFormat: SwiftProtobuf.Enum, Swift.CaseIterable {
public typealias RawValue = Int
///
@ -976,11 +997,21 @@ public struct Config {
}
}
// The compiler won't synthesize support with the UNRECOGNIZED case.
public static let allCases: [Config.DisplayConfig.GpsCoordinateFormat] = [
.dec,
.dms,
.utm,
.mgrs,
.olc,
.osgr,
]
}
///
/// Unit display preference
public enum DisplayUnits: SwiftProtobuf.Enum {
public enum DisplayUnits: SwiftProtobuf.Enum, Swift.CaseIterable {
public typealias RawValue = Int
///
@ -1012,11 +1043,17 @@ public struct Config {
}
}
// The compiler won't synthesize support with the UNRECOGNIZED case.
public static let allCases: [Config.DisplayConfig.DisplayUnits] = [
.metric,
.imperial,
]
}
///
/// Override OLED outo detect with this if it fails.
public enum OledType: SwiftProtobuf.Enum {
public enum OledType: SwiftProtobuf.Enum, Swift.CaseIterable {
public typealias RawValue = Int
///
@ -1066,9 +1103,18 @@ public struct Config {
}
}
// The compiler won't synthesize support with the UNRECOGNIZED case.
public static let allCases: [Config.DisplayConfig.OledType] = [
.oledAuto,
.oledSsd1306,
.oledSh1106,
.oledSh1107,
.oledSh110712864,
]
}
public enum DisplayMode: SwiftProtobuf.Enum {
public enum DisplayMode: SwiftProtobuf.Enum, Swift.CaseIterable {
public typealias RawValue = Int
///
@ -1112,9 +1158,17 @@ public struct Config {
}
}
// The compiler won't synthesize support with the UNRECOGNIZED case.
public static let allCases: [Config.DisplayConfig.DisplayMode] = [
.default,
.twocolor,
.inverted,
.color,
]
}
public enum CompassOrientation: SwiftProtobuf.Enum {
public enum CompassOrientation: SwiftProtobuf.Enum, Swift.CaseIterable {
public typealias RawValue = Int
///
@ -1182,6 +1236,18 @@ public struct Config {
}
}
// The compiler won't synthesize support with the UNRECOGNIZED case.
public static let allCases: [Config.DisplayConfig.CompassOrientation] = [
.degrees0,
.degrees90,
.degrees180,
.degrees270,
.degrees0Inverted,
.degrees90Inverted,
.degrees180Inverted,
.degrees270Inverted,
]
}
public init() {}
@ -1189,7 +1255,7 @@ public struct Config {
///
/// Lora Config
public struct LoRaConfig {
public struct LoRaConfig: @unchecked Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -1353,7 +1419,7 @@ public struct Config {
public var unknownFields = SwiftProtobuf.UnknownStorage()
public enum RegionCode: SwiftProtobuf.Enum {
public enum RegionCode: SwiftProtobuf.Enum, Swift.CaseIterable {
public typealias RawValue = Int
///
@ -1505,12 +1571,38 @@ public struct Config {
}
}
// The compiler won't synthesize support with the UNRECOGNIZED case.
public static let allCases: [Config.LoRaConfig.RegionCode] = [
.unset,
.us,
.eu433,
.eu868,
.cn,
.jp,
.anz,
.kr,
.tw,
.ru,
.in,
.nz865,
.th,
.lora24,
.ua433,
.ua868,
.my433,
.my919,
.sg923,
.ph433,
.ph868,
.ph915,
]
}
///
/// Standard predefined channel settings
/// Note: these mappings must match ModemPreset Choice in the device code.
public enum ModemPreset: SwiftProtobuf.Enum {
public enum ModemPreset: SwiftProtobuf.Enum, Swift.CaseIterable {
public typealias RawValue = Int
///
@ -1524,6 +1616,8 @@ public struct Config {
///
/// Very Long Range - Slow
/// Deprecated in 2.5: Works only with txco and is unusably slow
///
/// NOTE: This enum value was marked as deprecated in the .proto file
case veryLongSlow // = 2
///
@ -1587,6 +1681,19 @@ public struct Config {
}
}
// The compiler won't synthesize support with the UNRECOGNIZED case.
public static let allCases: [Config.LoRaConfig.ModemPreset] = [
.longFast,
.longSlow,
.veryLongSlow,
.mediumSlow,
.mediumFast,
.shortSlow,
.shortFast,
.longModerate,
.shortTurbo,
]
}
public init() {}
@ -1594,7 +1701,7 @@ public struct Config {
fileprivate var _storage = _StorageClass.defaultInstance
}
public struct BluetoothConfig {
public struct BluetoothConfig: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -1613,7 +1720,7 @@ public struct Config {
public var unknownFields = SwiftProtobuf.UnknownStorage()
public enum PairingMode: SwiftProtobuf.Enum {
public enum PairingMode: SwiftProtobuf.Enum, Swift.CaseIterable {
public typealias RawValue = Int
///
@ -1651,12 +1758,19 @@ public struct Config {
}
}
// The compiler won't synthesize support with the UNRECOGNIZED case.
public static let allCases: [Config.BluetoothConfig.PairingMode] = [
.randomPin,
.fixedPin,
.noPin,
]
}
public init() {}
}
public struct SecurityConfig {
public struct SecurityConfig: @unchecked Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -1700,7 +1814,7 @@ public struct Config {
///
/// Blank config request, strictly for getting the session key
public struct SessionkeyConfig {
public struct SessionkeyConfig: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -1713,218 +1827,6 @@ public struct Config {
public init() {}
}
#if swift(>=4.2)
extension Config.DeviceConfig.Role: CaseIterable {
// The compiler won't synthesize support with the UNRECOGNIZED case.
public static let allCases: [Config.DeviceConfig.Role] = [
.client,
.clientMute,
.router,
.routerClient,
.repeater,
.tracker,
.sensor,
.tak,
.clientHidden,
.lostAndFound,
.takTracker,
.routerLate,
]
}
extension Config.DeviceConfig.RebroadcastMode: CaseIterable {
// The compiler won't synthesize support with the UNRECOGNIZED case.
public static let allCases: [Config.DeviceConfig.RebroadcastMode] = [
.all,
.allSkipDecoding,
.localOnly,
.knownOnly,
.none,
.corePortnumsOnly,
]
}
extension Config.PositionConfig.PositionFlags: CaseIterable {
// The compiler won't synthesize support with the UNRECOGNIZED case.
public static let allCases: [Config.PositionConfig.PositionFlags] = [
.unset,
.altitude,
.altitudeMsl,
.geoidalSeparation,
.dop,
.hvdop,
.satinview,
.seqNo,
.timestamp,
.heading,
.speed,
]
}
extension Config.PositionConfig.GpsMode: CaseIterable {
// The compiler won't synthesize support with the UNRECOGNIZED case.
public static let allCases: [Config.PositionConfig.GpsMode] = [
.disabled,
.enabled,
.notPresent,
]
}
extension Config.NetworkConfig.AddressMode: CaseIterable {
// The compiler won't synthesize support with the UNRECOGNIZED case.
public static let allCases: [Config.NetworkConfig.AddressMode] = [
.dhcp,
.static,
]
}
extension Config.NetworkConfig.ProtocolFlags: CaseIterable {
// The compiler won't synthesize support with the UNRECOGNIZED case.
public static let allCases: [Config.NetworkConfig.ProtocolFlags] = [
.noBroadcast,
.udpBroadcast,
]
}
extension Config.DisplayConfig.GpsCoordinateFormat: CaseIterable {
// The compiler won't synthesize support with the UNRECOGNIZED case.
public static let allCases: [Config.DisplayConfig.GpsCoordinateFormat] = [
.dec,
.dms,
.utm,
.mgrs,
.olc,
.osgr,
]
}
extension Config.DisplayConfig.DisplayUnits: CaseIterable {
// The compiler won't synthesize support with the UNRECOGNIZED case.
public static let allCases: [Config.DisplayConfig.DisplayUnits] = [
.metric,
.imperial,
]
}
extension Config.DisplayConfig.OledType: CaseIterable {
// The compiler won't synthesize support with the UNRECOGNIZED case.
public static let allCases: [Config.DisplayConfig.OledType] = [
.oledAuto,
.oledSsd1306,
.oledSh1106,
.oledSh1107,
.oledSh110712864,
]
}
extension Config.DisplayConfig.DisplayMode: CaseIterable {
// The compiler won't synthesize support with the UNRECOGNIZED case.
public static let allCases: [Config.DisplayConfig.DisplayMode] = [
.default,
.twocolor,
.inverted,
.color,
]
}
extension Config.DisplayConfig.CompassOrientation: CaseIterable {
// The compiler won't synthesize support with the UNRECOGNIZED case.
public static let allCases: [Config.DisplayConfig.CompassOrientation] = [
.degrees0,
.degrees90,
.degrees180,
.degrees270,
.degrees0Inverted,
.degrees90Inverted,
.degrees180Inverted,
.degrees270Inverted,
]
}
extension Config.LoRaConfig.RegionCode: CaseIterable {
// The compiler won't synthesize support with the UNRECOGNIZED case.
public static let allCases: [Config.LoRaConfig.RegionCode] = [
.unset,
.us,
.eu433,
.eu868,
.cn,
.jp,
.anz,
.kr,
.tw,
.ru,
.in,
.nz865,
.th,
.lora24,
.ua433,
.ua868,
.my433,
.my919,
.sg923,
.ph433,
.ph868,
.ph915,
]
}
extension Config.LoRaConfig.ModemPreset: CaseIterable {
// The compiler won't synthesize support with the UNRECOGNIZED case.
public static let allCases: [Config.LoRaConfig.ModemPreset] = [
.longFast,
.longSlow,
.veryLongSlow,
.mediumSlow,
.mediumFast,
.shortSlow,
.shortFast,
.longModerate,
.shortTurbo,
]
}
extension Config.BluetoothConfig.PairingMode: CaseIterable {
// The compiler won't synthesize support with the UNRECOGNIZED case.
public static let allCases: [Config.BluetoothConfig.PairingMode] = [
.randomPin,
.fixedPin,
.noPin,
]
}
#endif // swift(>=4.2)
#if swift(>=5.5) && canImport(_Concurrency)
extension Config: @unchecked Sendable {}
extension Config.OneOf_PayloadVariant: @unchecked Sendable {}
extension Config.DeviceConfig: @unchecked Sendable {}
extension Config.DeviceConfig.Role: @unchecked Sendable {}
extension Config.DeviceConfig.RebroadcastMode: @unchecked Sendable {}
extension Config.PositionConfig: @unchecked Sendable {}
extension Config.PositionConfig.PositionFlags: @unchecked Sendable {}
extension Config.PositionConfig.GpsMode: @unchecked Sendable {}
extension Config.PowerConfig: @unchecked Sendable {}
extension Config.NetworkConfig: @unchecked Sendable {}
extension Config.NetworkConfig.AddressMode: @unchecked Sendable {}
extension Config.NetworkConfig.ProtocolFlags: @unchecked Sendable {}
extension Config.NetworkConfig.IpV4Config: @unchecked Sendable {}
extension Config.DisplayConfig: @unchecked Sendable {}
extension Config.DisplayConfig.GpsCoordinateFormat: @unchecked Sendable {}
extension Config.DisplayConfig.DisplayUnits: @unchecked Sendable {}
extension Config.DisplayConfig.OledType: @unchecked Sendable {}
extension Config.DisplayConfig.DisplayMode: @unchecked Sendable {}
extension Config.DisplayConfig.CompassOrientation: @unchecked Sendable {}
extension Config.LoRaConfig: @unchecked Sendable {}
extension Config.LoRaConfig.RegionCode: @unchecked Sendable {}
extension Config.LoRaConfig.ModemPreset: @unchecked Sendable {}
extension Config.BluetoothConfig: @unchecked Sendable {}
extension Config.BluetoothConfig.PairingMode: @unchecked Sendable {}
extension Config.SecurityConfig: @unchecked Sendable {}
extension Config.SessionkeyConfig: @unchecked Sendable {}
#endif // swift(>=5.5) && canImport(_Concurrency)
// MARK: - Code below here is support for the SwiftProtobuf runtime.
fileprivate let _protobuf_package = "meshtastic"
@ -2432,7 +2334,7 @@ extension Config.PowerConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImple
if self.onBatteryShutdownAfterSecs != 0 {
try visitor.visitSingularUInt32Field(value: self.onBatteryShutdownAfterSecs, fieldNumber: 2)
}
if self.adcMultiplierOverride != 0 {
if self.adcMultiplierOverride.bitPattern != 0 {
try visitor.visitSingularFloatField(value: self.adcMultiplierOverride, fieldNumber: 3)
}
if self.waitBluetoothSecs != 0 {
@ -2900,7 +2802,7 @@ extension Config.LoRaConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem
if _storage._codingRate != 0 {
try visitor.visitSingularUInt32Field(value: _storage._codingRate, fieldNumber: 5)
}
if _storage._frequencyOffset != 0 {
if _storage._frequencyOffset.bitPattern != 0 {
try visitor.visitSingularFloatField(value: _storage._frequencyOffset, fieldNumber: 6)
}
if _storage._region != .unset {
@ -2924,7 +2826,7 @@ extension Config.LoRaConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem
if _storage._sx126XRxBoostedGain != false {
try visitor.visitSingularBoolField(value: _storage._sx126XRxBoostedGain, fieldNumber: 13)
}
if _storage._overrideFrequency != 0 {
if _storage._overrideFrequency.bitPattern != 0 {
try visitor.visitSingularFloatField(value: _storage._overrideFrequency, fieldNumber: 14)
}
if _storage._paFanDisabled != false {
@ -3141,8 +3043,8 @@ extension Config.SessionkeyConfig: SwiftProtobuf.Message, SwiftProtobuf._Message
public static let _protobuf_nameMap = SwiftProtobuf._NameMap()
public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
while let _ = try decoder.nextFieldNumber() {
}
// Load everything into unknown fields
while try decoder.nextFieldNumber() != nil {}
}
public func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {

View file

@ -1,5 +1,6 @@
// DO NOT EDIT.
// swift-format-ignore-file
// swiftlint:disable all
//
// Generated by the Swift generator plugin for the protocol buffer compiler.
// Source: meshtastic/connection_status.proto
@ -7,7 +8,6 @@
// For information on using the generated types, please see the documentation:
// https://github.com/apple/swift-protobuf/
import Foundation
import SwiftProtobuf
// If the compiler emits an error on this type, it is because this file
@ -20,7 +20,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP
typealias Version = _2
}
public struct DeviceConnectionStatus {
public struct DeviceConnectionStatus: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -81,7 +81,7 @@ public struct DeviceConnectionStatus {
///
/// WiFi connection status
public struct WifiConnectionStatus {
public struct WifiConnectionStatus: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -114,7 +114,7 @@ public struct WifiConnectionStatus {
///
/// Ethernet connection status
public struct EthernetConnectionStatus {
public struct EthernetConnectionStatus: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -139,7 +139,7 @@ public struct EthernetConnectionStatus {
///
/// Ethernet or WiFi connection status
public struct NetworkConnectionStatus {
public struct NetworkConnectionStatus: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -167,7 +167,7 @@ public struct NetworkConnectionStatus {
///
/// Bluetooth connection status
public struct BluetoothConnectionStatus {
public struct BluetoothConnectionStatus: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -191,7 +191,7 @@ public struct BluetoothConnectionStatus {
///
/// Serial connection status
public struct SerialConnectionStatus {
public struct SerialConnectionStatus: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -209,15 +209,6 @@ public struct SerialConnectionStatus {
public init() {}
}
#if swift(>=5.5) && canImport(_Concurrency)
extension DeviceConnectionStatus: @unchecked Sendable {}
extension WifiConnectionStatus: @unchecked Sendable {}
extension EthernetConnectionStatus: @unchecked Sendable {}
extension NetworkConnectionStatus: @unchecked Sendable {}
extension BluetoothConnectionStatus: @unchecked Sendable {}
extension SerialConnectionStatus: @unchecked Sendable {}
#endif // swift(>=5.5) && canImport(_Concurrency)
// MARK: - Code below here is support for the SwiftProtobuf runtime.
fileprivate let _protobuf_package = "meshtastic"

View file

@ -1,5 +1,6 @@
// DO NOT EDIT.
// swift-format-ignore-file
// swiftlint:disable all
//
// Generated by the Swift generator plugin for the protocol buffer compiler.
// Source: meshtastic/device_ui.proto
@ -20,7 +21,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP
typealias Version = _2
}
public enum Theme: SwiftProtobuf.Enum {
public enum Theme: SwiftProtobuf.Enum, Swift.CaseIterable {
public typealias RawValue = Int
///
@ -58,24 +59,18 @@ public enum Theme: SwiftProtobuf.Enum {
}
}
}
#if swift(>=4.2)
extension Theme: CaseIterable {
// The compiler won't synthesize support with the UNRECOGNIZED case.
public static let allCases: [Theme] = [
.dark,
.light,
.red,
]
}
#endif // swift(>=4.2)
}
///
/// Localization
public enum Language: SwiftProtobuf.Enum {
public enum Language: SwiftProtobuf.Enum, Swift.CaseIterable {
public typealias RawValue = Int
///
@ -209,11 +204,6 @@ public enum Language: SwiftProtobuf.Enum {
}
}
}
#if swift(>=4.2)
extension Language: CaseIterable {
// The compiler won't synthesize support with the UNRECOGNIZED case.
public static let allCases: [Language] = [
.english,
@ -236,11 +226,10 @@ extension Language: CaseIterable {
.simplifiedChinese,
.traditionalChinese,
]
}
#endif // swift(>=4.2)
public struct DeviceUIConfig {
public struct DeviceUIConfig: @unchecked Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -361,7 +350,7 @@ public struct DeviceUIConfig {
fileprivate var _storage = _StorageClass.defaultInstance
}
public struct NodeFilter {
public struct NodeFilter: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -399,7 +388,7 @@ public struct NodeFilter {
public init() {}
}
public struct NodeHighlight {
public struct NodeHighlight: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -429,7 +418,7 @@ public struct NodeHighlight {
public init() {}
}
public struct GeoPoint {
public struct GeoPoint: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -451,7 +440,7 @@ public struct GeoPoint {
public init() {}
}
public struct Map {
public struct Map: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -482,16 +471,6 @@ public struct Map {
fileprivate var _home: GeoPoint? = nil
}
#if swift(>=5.5) && canImport(_Concurrency)
extension Theme: @unchecked Sendable {}
extension Language: @unchecked Sendable {}
extension DeviceUIConfig: @unchecked Sendable {}
extension NodeFilter: @unchecked Sendable {}
extension NodeHighlight: @unchecked Sendable {}
extension GeoPoint: @unchecked Sendable {}
extension Map: @unchecked Sendable {}
#endif // swift(>=5.5) && canImport(_Concurrency)
// MARK: - Code below here is support for the SwiftProtobuf runtime.
fileprivate let _protobuf_package = "meshtastic"

View file

@ -1,5 +1,6 @@
// DO NOT EDIT.
// swift-format-ignore-file
// swiftlint:disable all
//
// Generated by the Swift generator plugin for the protocol buffer compiler.
// Source: meshtastic/deviceonly.proto
@ -22,7 +23,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP
///
/// Position with static location information only for NodeDBLite
public struct PositionLite {
public struct PositionLite: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -57,13 +58,15 @@ public struct PositionLite {
public init() {}
}
public struct UserLite {
public struct UserLite: @unchecked Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
///
/// This is the addr of the radio.
///
/// NOTE: This field was marked as deprecated in the .proto file.
public var macaddr: Data = Data()
///
@ -97,12 +100,25 @@ public struct UserLite {
/// This is sent out to other nodes on the mesh to allow them to compute a shared secret key.
public var publicKey: Data = Data()
///
/// Whether or not the node can be messaged
public var isUnmessagable: Bool {
get {return _isUnmessagable ?? false}
set {_isUnmessagable = newValue}
}
/// Returns true if `isUnmessagable` has been explicitly set.
public var hasIsUnmessagable: Bool {return self._isUnmessagable != nil}
/// Clears the value of `isUnmessagable`. Subsequent reads from it will return its default value.
public mutating func clearIsUnmessagable() {self._isUnmessagable = nil}
public var unknownFields = SwiftProtobuf.UnknownStorage()
public init() {}
fileprivate var _isUnmessagable: Bool? = nil
}
public struct NodeInfoLite {
public struct NodeInfoLite: @unchecked Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -224,7 +240,7 @@ public struct NodeInfoLite {
/// FIXME, since we write this each time we enter deep sleep (and have infinite
/// flash) it would be better to use some sort of append only data structure for
/// the receive queue and use the preferences store for the other stuff
public struct DeviceState {
public struct DeviceState: @unchecked Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -284,6 +300,8 @@ public struct DeviceState {
/// Used only during development.
/// Indicates developer is testing and changes should never be saved to flash.
/// Deprecated in 2.3.1
///
/// NOTE: This field was marked as deprecated in the .proto file.
public var noSave: Bool {
get {return _storage._noSave}
set {_uniqueStorage()._noSave = newValue}
@ -292,6 +310,8 @@ public struct DeviceState {
///
/// Previously used to manage GPS factory resets.
/// Deprecated in 2.5.23
///
/// NOTE: This field was marked as deprecated in the .proto file.
public var didGpsReset: Bool {
get {return _storage._didGpsReset}
set {_uniqueStorage()._didGpsReset = newValue}
@ -324,7 +344,7 @@ public struct DeviceState {
fileprivate var _storage = _StorageClass.defaultInstance
}
public struct NodeDatabase {
public struct NodeDatabase: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -346,7 +366,7 @@ public struct NodeDatabase {
///
/// The on-disk saved channels
public struct ChannelFile {
public struct ChannelFile: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -368,7 +388,7 @@ public struct ChannelFile {
///
/// The on-disk backup of the node's preferences
public struct BackupPreferences {
public struct BackupPreferences: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -435,16 +455,6 @@ public struct BackupPreferences {
fileprivate var _owner: User? = nil
}
#if swift(>=5.5) && canImport(_Concurrency)
extension PositionLite: @unchecked Sendable {}
extension UserLite: @unchecked Sendable {}
extension NodeInfoLite: @unchecked Sendable {}
extension DeviceState: @unchecked Sendable {}
extension NodeDatabase: @unchecked Sendable {}
extension ChannelFile: @unchecked Sendable {}
extension BackupPreferences: @unchecked Sendable {}
#endif // swift(>=5.5) && canImport(_Concurrency)
// MARK: - Code below here is support for the SwiftProtobuf runtime.
fileprivate let _protobuf_package = "meshtastic"
@ -515,6 +525,7 @@ extension UserLite: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB
5: .standard(proto: "is_licensed"),
6: .same(proto: "role"),
7: .standard(proto: "public_key"),
9: .standard(proto: "is_unmessagable"),
]
public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
@ -530,12 +541,17 @@ extension UserLite: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB
case 5: try { try decoder.decodeSingularBoolField(value: &self.isLicensed) }()
case 6: try { try decoder.decodeSingularEnumField(value: &self.role) }()
case 7: try { try decoder.decodeSingularBytesField(value: &self.publicKey) }()
case 9: try { try decoder.decodeSingularBoolField(value: &self._isUnmessagable) }()
default: break
}
}
}
public func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every if/case branch local when no optimizations
// are enabled. https://github.com/apple/swift-protobuf/issues/1034 and
// https://github.com/apple/swift-protobuf/issues/1182
if !self.macaddr.isEmpty {
try visitor.visitSingularBytesField(value: self.macaddr, fieldNumber: 1)
}
@ -557,6 +573,9 @@ extension UserLite: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB
if !self.publicKey.isEmpty {
try visitor.visitSingularBytesField(value: self.publicKey, fieldNumber: 7)
}
try { if let v = self._isUnmessagable {
try visitor.visitSingularBoolField(value: v, fieldNumber: 9)
} }()
try unknownFields.traverse(visitor: &visitor)
}
@ -568,6 +587,7 @@ extension UserLite: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB
if lhs.isLicensed != rhs.isLicensed {return false}
if lhs.role != rhs.role {return false}
if lhs.publicKey != rhs.publicKey {return false}
if lhs._isUnmessagable != rhs._isUnmessagable {return false}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}
@ -680,7 +700,7 @@ extension NodeInfoLite: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat
try { if let v = _storage._position {
try visitor.visitSingularMessageField(value: v, fieldNumber: 3)
} }()
if _storage._snr != 0 {
if _storage._snr.bitPattern != 0 {
try visitor.visitSingularFloatField(value: _storage._snr, fieldNumber: 4)
}
if _storage._lastHeard != 0 {

View file

@ -1,5 +1,6 @@
// DO NOT EDIT.
// swift-format-ignore-file
// swiftlint:disable all
//
// Generated by the Swift generator plugin for the protocol buffer compiler.
// Source: meshtastic/interdevice.proto
@ -7,7 +8,6 @@
// For information on using the generated types, please see the documentation:
// https://github.com/apple/swift-protobuf/
import Foundation
import SwiftProtobuf
// If the compiler emits an error on this type, it is because this file
@ -20,7 +20,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP
typealias Version = _2
}
public enum MessageType: SwiftProtobuf.Enum {
public enum MessageType: SwiftProtobuf.Enum, Swift.CaseIterable {
public typealias RawValue = Int
case ack // = 0
@ -82,11 +82,6 @@ public enum MessageType: SwiftProtobuf.Enum {
}
}
}
#if swift(>=4.2)
extension MessageType: CaseIterable {
// The compiler won't synthesize support with the UNRECOGNIZED case.
public static let allCases: [MessageType] = [
.ack,
@ -102,11 +97,10 @@ extension MessageType: CaseIterable {
.aht20Humidity,
.tvocIndex,
]
}
#endif // swift(>=4.2)
public struct SensorData {
public struct SensorData: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -136,34 +130,16 @@ public struct SensorData {
public var unknownFields = SwiftProtobuf.UnknownStorage()
/// The sensor data, either as a float or an uint32
public enum OneOf_Data: Equatable {
public enum OneOf_Data: Equatable, Sendable {
case floatValue(Float)
case uint32Value(UInt32)
#if !swift(>=4.1)
public static func ==(lhs: SensorData.OneOf_Data, rhs: SensorData.OneOf_Data) -> Bool {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every case branch when no optimizations are
// enabled. https://github.com/apple/swift-protobuf/issues/1034
switch (lhs, rhs) {
case (.floatValue, .floatValue): return {
guard case .floatValue(let l) = lhs, case .floatValue(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.uint32Value, .uint32Value): return {
guard case .uint32Value(let l) = lhs, case .uint32Value(let r) = rhs else { preconditionFailure() }
return l == r
}()
default: return false
}
}
#endif
}
public init() {}
}
public struct InterdeviceMessage {
public struct InterdeviceMessage: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -190,41 +166,15 @@ public struct InterdeviceMessage {
public var unknownFields = SwiftProtobuf.UnknownStorage()
/// The message data
public enum OneOf_Data: Equatable {
public enum OneOf_Data: Equatable, Sendable {
case nmea(String)
case sensor(SensorData)
#if !swift(>=4.1)
public static func ==(lhs: InterdeviceMessage.OneOf_Data, rhs: InterdeviceMessage.OneOf_Data) -> Bool {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every case branch when no optimizations are
// enabled. https://github.com/apple/swift-protobuf/issues/1034
switch (lhs, rhs) {
case (.nmea, .nmea): return {
guard case .nmea(let l) = lhs, case .nmea(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.sensor, .sensor): return {
guard case .sensor(let l) = lhs, case .sensor(let r) = rhs else { preconditionFailure() }
return l == r
}()
default: return false
}
}
#endif
}
public init() {}
}
#if swift(>=5.5) && canImport(_Concurrency)
extension MessageType: @unchecked Sendable {}
extension SensorData: @unchecked Sendable {}
extension SensorData.OneOf_Data: @unchecked Sendable {}
extension InterdeviceMessage: @unchecked Sendable {}
extension InterdeviceMessage.OneOf_Data: @unchecked Sendable {}
#endif // swift(>=5.5) && canImport(_Concurrency)
// MARK: - Code below here is support for the SwiftProtobuf runtime.
fileprivate let _protobuf_package = "meshtastic"

View file

@ -1,5 +1,6 @@
// DO NOT EDIT.
// swift-format-ignore-file
// swiftlint:disable all
//
// Generated by the Swift generator plugin for the protocol buffer compiler.
// Source: meshtastic/localonly.proto
@ -7,7 +8,6 @@
// For information on using the generated types, please see the documentation:
// https://github.com/apple/swift-protobuf/
import Foundation
import SwiftProtobuf
// If the compiler emits an error on this type, it is because this file
@ -20,7 +20,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP
typealias Version = _2
}
public struct LocalConfig {
public struct LocalConfig: @unchecked Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -129,7 +129,7 @@ public struct LocalConfig {
fileprivate var _storage = _StorageClass.defaultInstance
}
public struct LocalModuleConfig {
public struct LocalModuleConfig: @unchecked Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -293,11 +293,6 @@ public struct LocalModuleConfig {
fileprivate var _storage = _StorageClass.defaultInstance
}
#if swift(>=5.5) && canImport(_Concurrency)
extension LocalConfig: @unchecked Sendable {}
extension LocalModuleConfig: @unchecked Sendable {}
#endif // swift(>=5.5) && canImport(_Concurrency)
// MARK: - Code below here is support for the SwiftProtobuf runtime.
fileprivate let _protobuf_package = "meshtastic"

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,6 @@
// DO NOT EDIT.
// swift-format-ignore-file
// swiftlint:disable all
//
// Generated by the Swift generator plugin for the protocol buffer compiler.
// Source: meshtastic/module_config.proto
@ -7,7 +8,6 @@
// For information on using the generated types, please see the documentation:
// https://github.com/apple/swift-protobuf/
import Foundation
import SwiftProtobuf
// If the compiler emits an error on this type, it is because this file
@ -20,7 +20,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP
typealias Version = _2
}
public enum RemoteHardwarePinType: SwiftProtobuf.Enum {
public enum RemoteHardwarePinType: SwiftProtobuf.Enum, Swift.CaseIterable {
public typealias RawValue = Int
///
@ -58,24 +58,18 @@ public enum RemoteHardwarePinType: SwiftProtobuf.Enum {
}
}
}
#if swift(>=4.2)
extension RemoteHardwarePinType: CaseIterable {
// The compiler won't synthesize support with the UNRECOGNIZED case.
public static let allCases: [RemoteHardwarePinType] = [
.unknown,
.digitalRead,
.digitalWrite,
]
}
#endif // swift(>=4.2)
}
///
/// Module Config
public struct ModuleConfig {
public struct ModuleConfig: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -218,7 +212,7 @@ public struct ModuleConfig {
///
/// TODO: REPLACE
public enum OneOf_PayloadVariant: Equatable {
public enum OneOf_PayloadVariant: Equatable, Sendable {
///
/// TODO: REPLACE
case mqtt(ModuleConfig.MQTTConfig)
@ -259,73 +253,11 @@ public struct ModuleConfig {
/// TODO: REPLACE
case paxcounter(ModuleConfig.PaxcounterConfig)
#if !swift(>=4.1)
public static func ==(lhs: ModuleConfig.OneOf_PayloadVariant, rhs: ModuleConfig.OneOf_PayloadVariant) -> Bool {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every case branch when no optimizations are
// enabled. https://github.com/apple/swift-protobuf/issues/1034
switch (lhs, rhs) {
case (.mqtt, .mqtt): return {
guard case .mqtt(let l) = lhs, case .mqtt(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.serial, .serial): return {
guard case .serial(let l) = lhs, case .serial(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.externalNotification, .externalNotification): return {
guard case .externalNotification(let l) = lhs, case .externalNotification(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.storeForward, .storeForward): return {
guard case .storeForward(let l) = lhs, case .storeForward(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.rangeTest, .rangeTest): return {
guard case .rangeTest(let l) = lhs, case .rangeTest(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.telemetry, .telemetry): return {
guard case .telemetry(let l) = lhs, case .telemetry(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.cannedMessage, .cannedMessage): return {
guard case .cannedMessage(let l) = lhs, case .cannedMessage(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.audio, .audio): return {
guard case .audio(let l) = lhs, case .audio(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.remoteHardware, .remoteHardware): return {
guard case .remoteHardware(let l) = lhs, case .remoteHardware(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.neighborInfo, .neighborInfo): return {
guard case .neighborInfo(let l) = lhs, case .neighborInfo(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.ambientLighting, .ambientLighting): return {
guard case .ambientLighting(let l) = lhs, case .ambientLighting(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.detectionSensor, .detectionSensor): return {
guard case .detectionSensor(let l) = lhs, case .detectionSensor(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.paxcounter, .paxcounter): return {
guard case .paxcounter(let l) = lhs, case .paxcounter(let r) = rhs else { preconditionFailure() }
return l == r
}()
default: return false
}
}
#endif
}
///
/// MQTT Client Config
public struct MQTTConfig {
public struct MQTTConfig: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -400,7 +332,7 @@ public struct ModuleConfig {
///
/// Settings for reporting unencrypted information about our node to a map via MQTT
public struct MapReportSettings {
public struct MapReportSettings: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -413,6 +345,10 @@ public struct ModuleConfig {
/// Bits of precision for the location sent (default of 32 is full precision).
public var positionPrecision: UInt32 = 0
///
/// Whether we have opted-in to report our location to the map
public var shouldReportLocation: Bool = false
public var unknownFields = SwiftProtobuf.UnknownStorage()
public init() {}
@ -420,7 +356,7 @@ public struct ModuleConfig {
///
/// RemoteHardwareModule Config
public struct RemoteHardwareConfig {
public struct RemoteHardwareConfig: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -444,7 +380,7 @@ public struct ModuleConfig {
///
/// NeighborInfoModule Config
public struct NeighborInfoConfig {
public struct NeighborInfoConfig: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -470,7 +406,7 @@ public struct ModuleConfig {
///
/// Detection Sensor Module Config
public struct DetectionSensorConfig {
public struct DetectionSensorConfig: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -517,7 +453,7 @@ public struct ModuleConfig {
public var unknownFields = SwiftProtobuf.UnknownStorage()
public enum TriggerType: SwiftProtobuf.Enum {
public enum TriggerType: SwiftProtobuf.Enum, Swift.CaseIterable {
public typealias RawValue = Int
/// Event is triggered if pin is low
@ -569,6 +505,16 @@ public struct ModuleConfig {
}
}
// The compiler won't synthesize support with the UNRECOGNIZED case.
public static let allCases: [ModuleConfig.DetectionSensorConfig.TriggerType] = [
.logicLow,
.logicHigh,
.fallingEdge,
.risingEdge,
.eitherEdgeActiveLow,
.eitherEdgeActiveHigh,
]
}
public init() {}
@ -576,7 +522,7 @@ public struct ModuleConfig {
///
/// Audio Config for codec2 voice
public struct AudioConfig {
public struct AudioConfig: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -613,7 +559,7 @@ public struct ModuleConfig {
///
/// Baudrate for codec2 voice
public enum Audio_Baud: SwiftProtobuf.Enum {
public enum Audio_Baud: SwiftProtobuf.Enum, Swift.CaseIterable {
public typealias RawValue = Int
case codec2Default // = 0
case codec23200 // = 1
@ -660,6 +606,19 @@ public struct ModuleConfig {
}
}
// The compiler won't synthesize support with the UNRECOGNIZED case.
public static let allCases: [ModuleConfig.AudioConfig.Audio_Baud] = [
.codec2Default,
.codec23200,
.codec22400,
.codec21600,
.codec21400,
.codec21300,
.codec21200,
.codec2700,
.codec2700B,
]
}
public init() {}
@ -667,7 +626,7 @@ public struct ModuleConfig {
///
/// Config for the Paxcounter Module
public struct PaxcounterConfig {
public struct PaxcounterConfig: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -693,7 +652,7 @@ public struct ModuleConfig {
///
/// Serial Config
public struct SerialConfig {
public struct SerialConfig: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -736,7 +695,7 @@ public struct ModuleConfig {
///
/// TODO: REPLACE
public enum Serial_Baud: SwiftProtobuf.Enum {
public enum Serial_Baud: SwiftProtobuf.Enum, Swift.CaseIterable {
public typealias RawValue = Int
case baudDefault // = 0
case baud110 // = 1
@ -804,11 +763,31 @@ public struct ModuleConfig {
}
}
// The compiler won't synthesize support with the UNRECOGNIZED case.
public static let allCases: [ModuleConfig.SerialConfig.Serial_Baud] = [
.baudDefault,
.baud110,
.baud300,
.baud600,
.baud1200,
.baud2400,
.baud4800,
.baud9600,
.baud19200,
.baud38400,
.baud57600,
.baud115200,
.baud230400,
.baud460800,
.baud576000,
.baud921600,
]
}
///
/// TODO: REPLACE
public enum Serial_Mode: SwiftProtobuf.Enum {
public enum Serial_Mode: SwiftProtobuf.Enum, Swift.CaseIterable {
public typealias RawValue = Int
case `default` // = 0
case simple // = 1
@ -821,6 +800,10 @@ public struct ModuleConfig {
/// Ecowitt WS85 weather station
case ws85 // = 6
/// VE.Direct is a serial protocol used by Victron Energy products
/// https://beta.ivc.no/wiki/index.php/Victron_VE_Direct_DIY_Cable
case veDirect // = 7
case UNRECOGNIZED(Int)
public init() {
@ -836,6 +819,7 @@ public struct ModuleConfig {
case 4: self = .nmea
case 5: self = .caltopo
case 6: self = .ws85
case 7: self = .veDirect
default: self = .UNRECOGNIZED(rawValue)
}
}
@ -849,10 +833,23 @@ public struct ModuleConfig {
case .nmea: return 4
case .caltopo: return 5
case .ws85: return 6
case .veDirect: return 7
case .UNRECOGNIZED(let i): return i
}
}
// The compiler won't synthesize support with the UNRECOGNIZED case.
public static let allCases: [ModuleConfig.SerialConfig.Serial_Mode] = [
.default,
.simple,
.proto,
.textmsg,
.nmea,
.caltopo,
.ws85,
.veDirect,
]
}
public init() {}
@ -860,7 +857,7 @@ public struct ModuleConfig {
///
/// External Notifications Config
public struct ExternalNotificationConfig {
public struct ExternalNotificationConfig: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -943,7 +940,7 @@ public struct ModuleConfig {
///
/// Store and Forward Module Config
public struct StoreForwardConfig {
public struct StoreForwardConfig: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -979,7 +976,7 @@ public struct ModuleConfig {
///
/// Preferences for the RangeTestModule
public struct RangeTestConfig {
public struct RangeTestConfig: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -1004,7 +1001,7 @@ public struct ModuleConfig {
///
/// Configuration for both device and environment metrics
public struct TelemetryConfig {
public struct TelemetryConfig: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -1073,7 +1070,7 @@ public struct ModuleConfig {
///
/// Canned Messages Module Config
public struct CannedMessageConfig {
public struct CannedMessageConfig: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -1128,7 +1125,7 @@ public struct ModuleConfig {
///
/// TODO: REPLACE
public enum InputEventChar: SwiftProtobuf.Enum {
public enum InputEventChar: SwiftProtobuf.Enum, Swift.CaseIterable {
public typealias RawValue = Int
///
@ -1196,6 +1193,18 @@ public struct ModuleConfig {
}
}
// The compiler won't synthesize support with the UNRECOGNIZED case.
public static let allCases: [ModuleConfig.CannedMessageConfig.InputEventChar] = [
.none,
.up,
.down,
.left,
.right,
.select,
.back,
.cancel,
]
}
public init() {}
@ -1204,7 +1213,7 @@ public struct ModuleConfig {
///
///Ambient Lighting Module - Settings for control of onboard LEDs to allow users to adjust the brightness levels and respective color levels.
///Initially created for the RAK14001 RGB LED module.
public struct AmbientLightingConfig {
public struct AmbientLightingConfig: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -1237,89 +1246,9 @@ public struct ModuleConfig {
public init() {}
}
#if swift(>=4.2)
extension ModuleConfig.DetectionSensorConfig.TriggerType: CaseIterable {
// The compiler won't synthesize support with the UNRECOGNIZED case.
public static let allCases: [ModuleConfig.DetectionSensorConfig.TriggerType] = [
.logicLow,
.logicHigh,
.fallingEdge,
.risingEdge,
.eitherEdgeActiveLow,
.eitherEdgeActiveHigh,
]
}
extension ModuleConfig.AudioConfig.Audio_Baud: CaseIterable {
// The compiler won't synthesize support with the UNRECOGNIZED case.
public static let allCases: [ModuleConfig.AudioConfig.Audio_Baud] = [
.codec2Default,
.codec23200,
.codec22400,
.codec21600,
.codec21400,
.codec21300,
.codec21200,
.codec2700,
.codec2700B,
]
}
extension ModuleConfig.SerialConfig.Serial_Baud: CaseIterable {
// The compiler won't synthesize support with the UNRECOGNIZED case.
public static let allCases: [ModuleConfig.SerialConfig.Serial_Baud] = [
.baudDefault,
.baud110,
.baud300,
.baud600,
.baud1200,
.baud2400,
.baud4800,
.baud9600,
.baud19200,
.baud38400,
.baud57600,
.baud115200,
.baud230400,
.baud460800,
.baud576000,
.baud921600,
]
}
extension ModuleConfig.SerialConfig.Serial_Mode: CaseIterable {
// The compiler won't synthesize support with the UNRECOGNIZED case.
public static let allCases: [ModuleConfig.SerialConfig.Serial_Mode] = [
.default,
.simple,
.proto,
.textmsg,
.nmea,
.caltopo,
.ws85,
]
}
extension ModuleConfig.CannedMessageConfig.InputEventChar: CaseIterable {
// The compiler won't synthesize support with the UNRECOGNIZED case.
public static let allCases: [ModuleConfig.CannedMessageConfig.InputEventChar] = [
.none,
.up,
.down,
.left,
.right,
.select,
.back,
.cancel,
]
}
#endif // swift(>=4.2)
///
/// A GPIO pin definition for remote hardware module
public struct RemoteHardwarePin {
public struct RemoteHardwarePin: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -1341,32 +1270,6 @@ public struct RemoteHardwarePin {
public init() {}
}
#if swift(>=5.5) && canImport(_Concurrency)
extension RemoteHardwarePinType: @unchecked Sendable {}
extension ModuleConfig: @unchecked Sendable {}
extension ModuleConfig.OneOf_PayloadVariant: @unchecked Sendable {}
extension ModuleConfig.MQTTConfig: @unchecked Sendable {}
extension ModuleConfig.MapReportSettings: @unchecked Sendable {}
extension ModuleConfig.RemoteHardwareConfig: @unchecked Sendable {}
extension ModuleConfig.NeighborInfoConfig: @unchecked Sendable {}
extension ModuleConfig.DetectionSensorConfig: @unchecked Sendable {}
extension ModuleConfig.DetectionSensorConfig.TriggerType: @unchecked Sendable {}
extension ModuleConfig.AudioConfig: @unchecked Sendable {}
extension ModuleConfig.AudioConfig.Audio_Baud: @unchecked Sendable {}
extension ModuleConfig.PaxcounterConfig: @unchecked Sendable {}
extension ModuleConfig.SerialConfig: @unchecked Sendable {}
extension ModuleConfig.SerialConfig.Serial_Baud: @unchecked Sendable {}
extension ModuleConfig.SerialConfig.Serial_Mode: @unchecked Sendable {}
extension ModuleConfig.ExternalNotificationConfig: @unchecked Sendable {}
extension ModuleConfig.StoreForwardConfig: @unchecked Sendable {}
extension ModuleConfig.RangeTestConfig: @unchecked Sendable {}
extension ModuleConfig.TelemetryConfig: @unchecked Sendable {}
extension ModuleConfig.CannedMessageConfig: @unchecked Sendable {}
extension ModuleConfig.CannedMessageConfig.InputEventChar: @unchecked Sendable {}
extension ModuleConfig.AmbientLightingConfig: @unchecked Sendable {}
extension RemoteHardwarePin: @unchecked Sendable {}
#endif // swift(>=5.5) && canImport(_Concurrency)
// MARK: - Code below here is support for the SwiftProtobuf runtime.
fileprivate let _protobuf_package = "meshtastic"
@ -1748,6 +1651,7 @@ extension ModuleConfig.MapReportSettings: SwiftProtobuf.Message, SwiftProtobuf._
public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .standard(proto: "publish_interval_secs"),
2: .standard(proto: "position_precision"),
3: .standard(proto: "should_report_location"),
]
public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
@ -1758,6 +1662,7 @@ extension ModuleConfig.MapReportSettings: SwiftProtobuf.Message, SwiftProtobuf._
switch fieldNumber {
case 1: try { try decoder.decodeSingularUInt32Field(value: &self.publishIntervalSecs) }()
case 2: try { try decoder.decodeSingularUInt32Field(value: &self.positionPrecision) }()
case 3: try { try decoder.decodeSingularBoolField(value: &self.shouldReportLocation) }()
default: break
}
}
@ -1770,12 +1675,16 @@ extension ModuleConfig.MapReportSettings: SwiftProtobuf.Message, SwiftProtobuf._
if self.positionPrecision != 0 {
try visitor.visitSingularUInt32Field(value: self.positionPrecision, fieldNumber: 2)
}
if self.shouldReportLocation != false {
try visitor.visitSingularBoolField(value: self.shouldReportLocation, fieldNumber: 3)
}
try unknownFields.traverse(visitor: &visitor)
}
public static func ==(lhs: ModuleConfig.MapReportSettings, rhs: ModuleConfig.MapReportSettings) -> Bool {
if lhs.publishIntervalSecs != rhs.publishIntervalSecs {return false}
if lhs.positionPrecision != rhs.positionPrecision {return false}
if lhs.shouldReportLocation != rhs.shouldReportLocation {return false}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}
@ -2190,6 +2099,7 @@ extension ModuleConfig.SerialConfig.Serial_Mode: SwiftProtobuf._ProtoNameProvidi
4: .same(proto: "NMEA"),
5: .same(proto: "CALTOPO"),
6: .same(proto: "WS85"),
7: .same(proto: "VE_DIRECT"),
]
}

View file

@ -1,5 +1,6 @@
// DO NOT EDIT.
// swift-format-ignore-file
// swiftlint:disable all
//
// Generated by the Swift generator plugin for the protocol buffer compiler.
// Source: meshtastic/mqtt.proto
@ -7,7 +8,6 @@
// For information on using the generated types, please see the documentation:
// https://github.com/apple/swift-protobuf/
import Foundation
import SwiftProtobuf
// If the compiler emits an error on this type, it is because this file
@ -22,7 +22,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP
///
/// This message wraps a MeshPacket with extra metadata about the sender and how it arrived.
public struct ServiceEnvelope {
public struct ServiceEnvelope: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -57,7 +57,7 @@ public struct ServiceEnvelope {
///
/// Information about a node intended to be reported unencrypted to a map using MQTT.
public struct MapReport {
public struct MapReport: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -116,16 +116,16 @@ public struct MapReport {
/// Number of online nodes (heard in the last 2 hours) this node has in its list that were received locally (not via MQTT)
public var numOnlineLocalNodes: UInt32 = 0
///
/// User has opted in to share their location (map report) with the mqtt server
/// Controlled by map_report.should_report_location
public var hasOptedReportLocation_p: Bool = false
public var unknownFields = SwiftProtobuf.UnknownStorage()
public init() {}
}
#if swift(>=5.5) && canImport(_Concurrency)
extension ServiceEnvelope: @unchecked Sendable {}
extension MapReport: @unchecked Sendable {}
#endif // swift(>=5.5) && canImport(_Concurrency)
// MARK: - Code below here is support for the SwiftProtobuf runtime.
fileprivate let _protobuf_package = "meshtastic"
@ -194,6 +194,7 @@ extension MapReport: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementation
11: .same(proto: "altitude"),
12: .standard(proto: "position_precision"),
13: .standard(proto: "num_online_local_nodes"),
14: .standard(proto: "has_opted_report_location"),
]
public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
@ -215,6 +216,7 @@ extension MapReport: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementation
case 11: try { try decoder.decodeSingularInt32Field(value: &self.altitude) }()
case 12: try { try decoder.decodeSingularUInt32Field(value: &self.positionPrecision) }()
case 13: try { try decoder.decodeSingularUInt32Field(value: &self.numOnlineLocalNodes) }()
case 14: try { try decoder.decodeSingularBoolField(value: &self.hasOptedReportLocation_p) }()
default: break
}
}
@ -260,6 +262,9 @@ extension MapReport: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementation
if self.numOnlineLocalNodes != 0 {
try visitor.visitSingularUInt32Field(value: self.numOnlineLocalNodes, fieldNumber: 13)
}
if self.hasOptedReportLocation_p != false {
try visitor.visitSingularBoolField(value: self.hasOptedReportLocation_p, fieldNumber: 14)
}
try unknownFields.traverse(visitor: &visitor)
}
@ -277,6 +282,7 @@ extension MapReport: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementation
if lhs.altitude != rhs.altitude {return false}
if lhs.positionPrecision != rhs.positionPrecision {return false}
if lhs.numOnlineLocalNodes != rhs.numOnlineLocalNodes {return false}
if lhs.hasOptedReportLocation_p != rhs.hasOptedReportLocation_p {return false}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}

View file

@ -1,5 +1,6 @@
// DO NOT EDIT.
// swift-format-ignore-file
// swiftlint:disable all
//
// Generated by the Swift generator plugin for the protocol buffer compiler.
// Source: meshtastic/paxcount.proto
@ -7,7 +8,6 @@
// For information on using the generated types, please see the documentation:
// https://github.com/apple/swift-protobuf/
import Foundation
import SwiftProtobuf
// If the compiler emits an error on this type, it is because this file
@ -22,7 +22,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP
///
/// TODO: REPLACE
public struct Paxcount {
public struct Paxcount: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -44,10 +44,6 @@ public struct Paxcount {
public init() {}
}
#if swift(>=5.5) && canImport(_Concurrency)
extension Paxcount: @unchecked Sendable {}
#endif // swift(>=5.5) && canImport(_Concurrency)
// MARK: - Code below here is support for the SwiftProtobuf runtime.
fileprivate let _protobuf_package = "meshtastic"

View file

@ -1,5 +1,6 @@
// DO NOT EDIT.
// swift-format-ignore-file
// swiftlint:disable all
//
// Generated by the Swift generator plugin for the protocol buffer compiler.
// Source: meshtastic/portnums.proto
@ -7,7 +8,6 @@
// For information on using the generated types, please see the documentation:
// https://github.com/apple/swift-protobuf/
import Foundation
import SwiftProtobuf
// If the compiler emits an error on this type, it is because this file
@ -33,7 +33,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP
/// Note: This was formerly a Type enum named 'typ' with the same id #
/// We have change to this 'portnum' based scheme for specifying app handlers for particular payloads.
/// This change is backwards compatible by treating the legacy OPAQUE/CLEAR_TEXT values identically.
public enum PortNum: SwiftProtobuf.Enum {
public enum PortNum: SwiftProtobuf.Enum, Swift.CaseIterable {
public typealias RawValue = Int
///
@ -290,11 +290,6 @@ public enum PortNum: SwiftProtobuf.Enum {
}
}
}
#if swift(>=4.2)
extension PortNum: CaseIterable {
// The compiler won't synthesize support with the UNRECOGNIZED case.
public static let allCases: [PortNum] = [
.unknownApp,
@ -328,14 +323,9 @@ extension PortNum: CaseIterable {
.atakForwarder,
.max,
]
}
#endif // swift(>=4.2)
#if swift(>=5.5) && canImport(_Concurrency)
extension PortNum: @unchecked Sendable {}
#endif // swift(>=5.5) && canImport(_Concurrency)
// MARK: - Code below here is support for the SwiftProtobuf runtime.
extension PortNum: SwiftProtobuf._ProtoNameProviding {

View file

@ -1,5 +1,6 @@
// DO NOT EDIT.
// swift-format-ignore-file
// swiftlint:disable all
//
// Generated by the Swift generator plugin for the protocol buffer compiler.
// Source: meshtastic/powermon.proto
@ -7,7 +8,6 @@
// For information on using the generated types, please see the documentation:
// https://github.com/apple/swift-protobuf/
import Foundation
import SwiftProtobuf
// If the compiler emits an error on this type, it is because this file
@ -22,7 +22,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP
/// Note: There are no 'PowerMon' messages normally in use (PowerMons are sent only as structured logs - slogs).
///But we wrap our State enum in this message to effectively nest a namespace (without our linter yelling at us)
public struct PowerMon {
public struct PowerMon: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -31,7 +31,7 @@ public struct PowerMon {
/// Any significant power changing event in meshtastic should be tagged with a powermon state transition.
///If you are making new meshtastic features feel free to add new entries at the end of this definition.
public enum State: SwiftProtobuf.Enum {
public enum State: SwiftProtobuf.Enum, Swift.CaseIterable {
public typealias RawValue = Int
case none // = 0
case cpuDeepSleep // = 1
@ -104,37 +104,31 @@ public struct PowerMon {
}
}
// The compiler won't synthesize support with the UNRECOGNIZED case.
public static let allCases: [PowerMon.State] = [
.none,
.cpuDeepSleep,
.cpuLightSleep,
.vext1On,
.loraRxon,
.loraTxon,
.loraRxactive,
.btOn,
.ledOn,
.screenOn,
.screenDrawing,
.wifiOn,
.gpsActive,
]
}
public init() {}
}
#if swift(>=4.2)
extension PowerMon.State: CaseIterable {
// The compiler won't synthesize support with the UNRECOGNIZED case.
public static let allCases: [PowerMon.State] = [
.none,
.cpuDeepSleep,
.cpuLightSleep,
.vext1On,
.loraRxon,
.loraTxon,
.loraRxactive,
.btOn,
.ledOn,
.screenOn,
.screenDrawing,
.wifiOn,
.gpsActive,
]
}
#endif // swift(>=4.2)
///
/// PowerStress testing support via the C++ PowerStress module
public struct PowerStressMessage {
public struct PowerStressMessage: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -151,7 +145,7 @@ public struct PowerStressMessage {
/// What operation would we like the UUT to perform.
///note: senders should probably set want_response in their request packets, so that they can know when the state
///machine has started processing their request
public enum Opcode: SwiftProtobuf.Enum {
public enum Opcode: SwiftProtobuf.Enum, Swift.CaseIterable {
public typealias RawValue = Int
///
@ -272,48 +266,35 @@ public struct PowerStressMessage {
}
}
// The compiler won't synthesize support with the UNRECOGNIZED case.
public static let allCases: [PowerStressMessage.Opcode] = [
.unset,
.printInfo,
.forceQuiet,
.endQuiet,
.screenOn,
.screenOff,
.cpuIdle,
.cpuDeepsleep,
.cpuFullon,
.ledOn,
.ledOff,
.loraOff,
.loraTx,
.loraRx,
.btOff,
.btOn,
.wifiOff,
.wifiOn,
.gpsOff,
.gpsOn,
]
}
public init() {}
}
#if swift(>=4.2)
extension PowerStressMessage.Opcode: CaseIterable {
// The compiler won't synthesize support with the UNRECOGNIZED case.
public static let allCases: [PowerStressMessage.Opcode] = [
.unset,
.printInfo,
.forceQuiet,
.endQuiet,
.screenOn,
.screenOff,
.cpuIdle,
.cpuDeepsleep,
.cpuFullon,
.ledOn,
.ledOff,
.loraOff,
.loraTx,
.loraRx,
.btOff,
.btOn,
.wifiOff,
.wifiOn,
.gpsOff,
.gpsOn,
]
}
#endif // swift(>=4.2)
#if swift(>=5.5) && canImport(_Concurrency)
extension PowerMon: @unchecked Sendable {}
extension PowerMon.State: @unchecked Sendable {}
extension PowerStressMessage: @unchecked Sendable {}
extension PowerStressMessage.Opcode: @unchecked Sendable {}
#endif // swift(>=5.5) && canImport(_Concurrency)
// MARK: - Code below here is support for the SwiftProtobuf runtime.
fileprivate let _protobuf_package = "meshtastic"
@ -323,8 +304,8 @@ extension PowerMon: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB
public static let _protobuf_nameMap = SwiftProtobuf._NameMap()
public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
while let _ = try decoder.nextFieldNumber() {
}
// Load everything into unknown fields
while try decoder.nextFieldNumber() != nil {}
}
public func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
@ -379,7 +360,7 @@ extension PowerStressMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImple
if self.cmd != .unset {
try visitor.visitSingularEnumField(value: self.cmd, fieldNumber: 1)
}
if self.numSeconds != 0 {
if self.numSeconds.bitPattern != 0 {
try visitor.visitSingularFloatField(value: self.numSeconds, fieldNumber: 2)
}
try unknownFields.traverse(visitor: &visitor)

View file

@ -1,5 +1,6 @@
// DO NOT EDIT.
// swift-format-ignore-file
// swiftlint:disable all
//
// Generated by the Swift generator plugin for the protocol buffer compiler.
// Source: meshtastic/remote_hardware.proto
@ -7,7 +8,6 @@
// For information on using the generated types, please see the documentation:
// https://github.com/apple/swift-protobuf/
import Foundation
import SwiftProtobuf
// If the compiler emits an error on this type, it is because this file
@ -30,7 +30,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP
/// because no security yet (beyond the channel mechanism).
/// It should be off by default and then protected based on some TBD mechanism
/// (a special channel once multichannel support is included?)
public struct HardwareMessage {
public struct HardwareMessage: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -52,7 +52,7 @@ public struct HardwareMessage {
///
/// TODO: REPLACE
public enum TypeEnum: SwiftProtobuf.Enum {
public enum TypeEnum: SwiftProtobuf.Enum, Swift.CaseIterable {
public typealias RawValue = Int
///
@ -110,32 +110,21 @@ public struct HardwareMessage {
}
}
// The compiler won't synthesize support with the UNRECOGNIZED case.
public static let allCases: [HardwareMessage.TypeEnum] = [
.unset,
.writeGpios,
.watchGpios,
.gpiosChanged,
.readGpios,
.readGpiosReply,
]
}
public init() {}
}
#if swift(>=4.2)
extension HardwareMessage.TypeEnum: CaseIterable {
// The compiler won't synthesize support with the UNRECOGNIZED case.
public static let allCases: [HardwareMessage.TypeEnum] = [
.unset,
.writeGpios,
.watchGpios,
.gpiosChanged,
.readGpios,
.readGpiosReply,
]
}
#endif // swift(>=4.2)
#if swift(>=5.5) && canImport(_Concurrency)
extension HardwareMessage: @unchecked Sendable {}
extension HardwareMessage.TypeEnum: @unchecked Sendable {}
#endif // swift(>=5.5) && canImport(_Concurrency)
// MARK: - Code below here is support for the SwiftProtobuf runtime.
fileprivate let _protobuf_package = "meshtastic"

View file

@ -1,5 +1,6 @@
// DO NOT EDIT.
// swift-format-ignore-file
// swiftlint:disable all
//
// Generated by the Swift generator plugin for the protocol buffer compiler.
// Source: meshtastic/rtttl.proto
@ -7,7 +8,6 @@
// For information on using the generated types, please see the documentation:
// https://github.com/apple/swift-protobuf/
import Foundation
import SwiftProtobuf
// If the compiler emits an error on this type, it is because this file
@ -22,7 +22,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP
///
/// Canned message module configuration.
public struct RTTTLConfig {
public struct RTTTLConfig: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -36,10 +36,6 @@ public struct RTTTLConfig {
public init() {}
}
#if swift(>=5.5) && canImport(_Concurrency)
extension RTTTLConfig: @unchecked Sendable {}
#endif // swift(>=5.5) && canImport(_Concurrency)
// MARK: - Code below here is support for the SwiftProtobuf runtime.
fileprivate let _protobuf_package = "meshtastic"

View file

@ -1,5 +1,6 @@
// DO NOT EDIT.
// swift-format-ignore-file
// swiftlint:disable all
//
// Generated by the Swift generator plugin for the protocol buffer compiler.
// Source: meshtastic/storeforward.proto
@ -22,7 +23,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP
///
/// TODO: REPLACE
public struct StoreAndForward {
public struct StoreAndForward: @unchecked Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -79,7 +80,7 @@ public struct StoreAndForward {
///
/// TODO: REPLACE
public enum OneOf_Variant: Equatable {
public enum OneOf_Variant: Equatable, @unchecked Sendable {
///
/// TODO: REPLACE
case stats(StoreAndForward.Statistics)
@ -93,38 +94,12 @@ public struct StoreAndForward {
/// Text from history message.
case text(Data)
#if !swift(>=4.1)
public static func ==(lhs: StoreAndForward.OneOf_Variant, rhs: StoreAndForward.OneOf_Variant) -> Bool {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every case branch when no optimizations are
// enabled. https://github.com/apple/swift-protobuf/issues/1034
switch (lhs, rhs) {
case (.stats, .stats): return {
guard case .stats(let l) = lhs, case .stats(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.history, .history): return {
guard case .history(let l) = lhs, case .history(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.heartbeat, .heartbeat): return {
guard case .heartbeat(let l) = lhs, case .heartbeat(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.text, .text): return {
guard case .text(let l) = lhs, case .text(let r) = rhs else { preconditionFailure() }
return l == r
}()
default: return false
}
}
#endif
}
///
/// 001 - 063 = From Router
/// 064 - 127 = From Client
public enum RequestResponse: SwiftProtobuf.Enum {
public enum RequestResponse: SwiftProtobuf.Enum, Swift.CaseIterable {
public typealias RawValue = Int
///
@ -242,11 +217,31 @@ public struct StoreAndForward {
}
}
// The compiler won't synthesize support with the UNRECOGNIZED case.
public static let allCases: [StoreAndForward.RequestResponse] = [
.unset,
.routerError,
.routerHeartbeat,
.routerPing,
.routerPong,
.routerBusy,
.routerHistory,
.routerStats,
.routerTextDirect,
.routerTextBroadcast,
.clientError,
.clientHistory,
.clientStats,
.clientPing,
.clientPong,
.clientAbort,
]
}
///
/// TODO: REPLACE
public struct Statistics {
public struct Statistics: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -294,7 +289,7 @@ public struct StoreAndForward {
///
/// TODO: REPLACE
public struct History {
public struct History: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -319,7 +314,7 @@ public struct StoreAndForward {
///
/// TODO: REPLACE
public struct Heartbeat {
public struct Heartbeat: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -340,41 +335,6 @@ public struct StoreAndForward {
public init() {}
}
#if swift(>=4.2)
extension StoreAndForward.RequestResponse: CaseIterable {
// The compiler won't synthesize support with the UNRECOGNIZED case.
public static let allCases: [StoreAndForward.RequestResponse] = [
.unset,
.routerError,
.routerHeartbeat,
.routerPing,
.routerPong,
.routerBusy,
.routerHistory,
.routerStats,
.routerTextDirect,
.routerTextBroadcast,
.clientError,
.clientHistory,
.clientStats,
.clientPing,
.clientPong,
.clientAbort,
]
}
#endif // swift(>=4.2)
#if swift(>=5.5) && canImport(_Concurrency)
extension StoreAndForward: @unchecked Sendable {}
extension StoreAndForward.OneOf_Variant: @unchecked Sendable {}
extension StoreAndForward.RequestResponse: @unchecked Sendable {}
extension StoreAndForward.Statistics: @unchecked Sendable {}
extension StoreAndForward.History: @unchecked Sendable {}
extension StoreAndForward.Heartbeat: @unchecked Sendable {}
#endif // swift(>=5.5) && canImport(_Concurrency)
// MARK: - Code below here is support for the SwiftProtobuf runtime.
fileprivate let _protobuf_package = "meshtastic"

View file

@ -1,5 +1,6 @@
// DO NOT EDIT.
// swift-format-ignore-file
// swiftlint:disable all
//
// Generated by the Swift generator plugin for the protocol buffer compiler.
// Source: meshtastic/telemetry.proto
@ -7,7 +8,6 @@
// For information on using the generated types, please see the documentation:
// https://github.com/apple/swift-protobuf/
import Foundation
import SwiftProtobuf
// If the compiler emits an error on this type, it is because this file
@ -22,7 +22,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP
///
/// Supported I2C Sensors for telemetry in Meshtastic
public enum TelemetrySensorType: SwiftProtobuf.Enum {
public enum TelemetrySensorType: SwiftProtobuf.Enum, Swift.CaseIterable {
public typealias RawValue = Int
///
@ -176,6 +176,10 @@ public enum TelemetrySensorType: SwiftProtobuf.Enum {
///
/// RAKWireless RAK12035 Soil Moisture Sensor Module
case rak12035 // = 37
///
/// MAX17261 lipo battery gauge
case max17261 // = 38
case UNRECOGNIZED(Int)
public init() {
@ -222,6 +226,7 @@ public enum TelemetrySensorType: SwiftProtobuf.Enum {
case 35: self = .dfrobotRain
case 36: self = .dps310
case 37: self = .rak12035
case 38: self = .max17261
default: self = .UNRECOGNIZED(rawValue)
}
}
@ -266,15 +271,11 @@ public enum TelemetrySensorType: SwiftProtobuf.Enum {
case .dfrobotRain: return 35
case .dps310: return 36
case .rak12035: return 37
case .max17261: return 38
case .UNRECOGNIZED(let i): return i
}
}
}
#if swift(>=4.2)
extension TelemetrySensorType: CaseIterable {
// The compiler won't synthesize support with the UNRECOGNIZED case.
public static let allCases: [TelemetrySensorType] = [
.sensorUnset,
@ -315,14 +316,14 @@ extension TelemetrySensorType: CaseIterable {
.dfrobotRain,
.dps310,
.rak12035,
.max17261,
]
}
#endif // swift(>=4.2)
}
///
/// Key native device metrics such as battery level
public struct DeviceMetrics {
public struct DeviceMetrics: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -395,7 +396,7 @@ public struct DeviceMetrics {
///
/// Weather station or other environmental metrics
public struct EnvironmentMetrics {
public struct EnvironmentMetrics: @unchecked Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -653,7 +654,7 @@ public struct EnvironmentMetrics {
///
/// Power Metrics (voltage / current / etc)
public struct PowerMetrics {
public struct PowerMetrics: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -738,7 +739,7 @@ public struct PowerMetrics {
///
/// Air quality metrics
public struct AirQualityMetrics {
public struct AirQualityMetrics: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -907,7 +908,7 @@ public struct AirQualityMetrics {
///
/// Local device mesh statistics
public struct LocalStats {
public struct LocalStats: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -965,7 +966,7 @@ public struct LocalStats {
///
/// Health telemetry metrics
public struct HealthMetrics {
public struct HealthMetrics: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -1014,7 +1015,7 @@ public struct HealthMetrics {
///
/// Types of Measurements the telemetry module is equipped to handle
public struct Telemetry {
public struct Telemetry: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -1087,7 +1088,7 @@ public struct Telemetry {
public var unknownFields = SwiftProtobuf.UnknownStorage()
public enum OneOf_Variant: Equatable {
public enum OneOf_Variant: Equatable, Sendable {
///
/// Key native device metrics such as battery level
case deviceMetrics(DeviceMetrics)
@ -1107,40 +1108,6 @@ public struct Telemetry {
/// Health telemetry metrics
case healthMetrics(HealthMetrics)
#if !swift(>=4.1)
public static func ==(lhs: Telemetry.OneOf_Variant, rhs: Telemetry.OneOf_Variant) -> Bool {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every case branch when no optimizations are
// enabled. https://github.com/apple/swift-protobuf/issues/1034
switch (lhs, rhs) {
case (.deviceMetrics, .deviceMetrics): return {
guard case .deviceMetrics(let l) = lhs, case .deviceMetrics(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.environmentMetrics, .environmentMetrics): return {
guard case .environmentMetrics(let l) = lhs, case .environmentMetrics(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.airQualityMetrics, .airQualityMetrics): return {
guard case .airQualityMetrics(let l) = lhs, case .airQualityMetrics(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.powerMetrics, .powerMetrics): return {
guard case .powerMetrics(let l) = lhs, case .powerMetrics(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.localStats, .localStats): return {
guard case .localStats(let l) = lhs, case .localStats(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.healthMetrics, .healthMetrics): return {
guard case .healthMetrics(let l) = lhs, case .healthMetrics(let r) = rhs else { preconditionFailure() }
return l == r
}()
default: return false
}
}
#endif
}
public init() {}
@ -1148,7 +1115,7 @@ public struct Telemetry {
///
/// NAU7802 Telemetry configuration, for saving to flash
public struct Nau7802Config {
public struct Nau7802Config: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -1166,19 +1133,6 @@ public struct Nau7802Config {
public init() {}
}
#if swift(>=5.5) && canImport(_Concurrency)
extension TelemetrySensorType: @unchecked Sendable {}
extension DeviceMetrics: @unchecked Sendable {}
extension EnvironmentMetrics: @unchecked Sendable {}
extension PowerMetrics: @unchecked Sendable {}
extension AirQualityMetrics: @unchecked Sendable {}
extension LocalStats: @unchecked Sendable {}
extension HealthMetrics: @unchecked Sendable {}
extension Telemetry: @unchecked Sendable {}
extension Telemetry.OneOf_Variant: @unchecked Sendable {}
extension Nau7802Config: @unchecked Sendable {}
#endif // swift(>=5.5) && canImport(_Concurrency)
// MARK: - Code below here is support for the SwiftProtobuf runtime.
fileprivate let _protobuf_package = "meshtastic"
@ -1223,6 +1177,7 @@ extension TelemetrySensorType: SwiftProtobuf._ProtoNameProviding {
35: .same(proto: "DFROBOT_RAIN"),
36: .same(proto: "DPS310"),
37: .same(proto: "RAK12035"),
38: .same(proto: "MAX17261"),
]
}
@ -1746,10 +1701,10 @@ extension LocalStats: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio
if self.uptimeSeconds != 0 {
try visitor.visitSingularUInt32Field(value: self.uptimeSeconds, fieldNumber: 1)
}
if self.channelUtilization != 0 {
if self.channelUtilization.bitPattern != 0 {
try visitor.visitSingularFloatField(value: self.channelUtilization, fieldNumber: 2)
}
if self.airUtilTx != 0 {
if self.airUtilTx.bitPattern != 0 {
try visitor.visitSingularFloatField(value: self.airUtilTx, fieldNumber: 3)
}
if self.numPacketsTx != 0 {
@ -2016,7 +1971,7 @@ extension Nau7802Config: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementa
if self.zeroOffset != 0 {
try visitor.visitSingularInt32Field(value: self.zeroOffset, fieldNumber: 1)
}
if self.calibrationFactor != 0 {
if self.calibrationFactor.bitPattern != 0 {
try visitor.visitSingularFloatField(value: self.calibrationFactor, fieldNumber: 2)
}
try unknownFields.traverse(visitor: &visitor)

View file

@ -1,5 +1,6 @@
// DO NOT EDIT.
// swift-format-ignore-file
// swiftlint:disable all
//
// Generated by the Swift generator plugin for the protocol buffer compiler.
// Source: meshtastic/xmodem.proto
@ -20,7 +21,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP
typealias Version = _2
}
public struct XModem {
public struct XModem: @unchecked Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
@ -35,7 +36,7 @@ public struct XModem {
public var unknownFields = SwiftProtobuf.UnknownStorage()
public enum Control: SwiftProtobuf.Enum {
public enum Control: SwiftProtobuf.Enum, Swift.CaseIterable {
public typealias RawValue = Int
case nul // = 0
case soh // = 1
@ -79,34 +80,23 @@ public struct XModem {
}
}
// The compiler won't synthesize support with the UNRECOGNIZED case.
public static let allCases: [XModem.Control] = [
.nul,
.soh,
.stx,
.eot,
.ack,
.nak,
.can,
.ctrlz,
]
}
public init() {}
}
#if swift(>=4.2)
extension XModem.Control: CaseIterable {
// The compiler won't synthesize support with the UNRECOGNIZED case.
public static let allCases: [XModem.Control] = [
.nul,
.soh,
.stx,
.eot,
.ack,
.nak,
.can,
.ctrlz,
]
}
#endif // swift(>=4.2)
#if swift(>=5.5) && canImport(_Concurrency)
extension XModem: @unchecked Sendable {}
extension XModem.Control: @unchecked Sendable {}
#endif // swift(>=5.5) && canImport(_Concurrency)
// MARK: - Code below here is support for the SwiftProtobuf runtime.
fileprivate let _protobuf_package = "meshtastic"

@ -1 +1 @@
Subproject commit 068646653e8375fc145988026ad242a3cf70f7ab
Subproject commit 816595c8bbdfc3b4388e11348ccd043294d58705