mirror of
https://github.com/meshtastic/Meshtastic-Apple.git
synced 2026-04-20 22:13:56 +00:00
Make BLE Transport an actor to fix background discovery crashes (#1554)
* Bump version * update the translations (#1540) update the translations * Don't alert (with sound: .default) when updating Live Activity (#1536) * Fix adding channels (#1532) * Full translation into Spanish (#1529) * tapback with any emoji (#1538) * Call clearStaleNodes at start of sendWantConfig (#1535) * NFC Tag contact (#1537) * Accessorymanager background discovery (#1542) * Don't add new BLE devices to the device list in the backgournd * Bump version * Update Meshtastic/MeshtasticApp.swift Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update Meshtastic/MeshtasticApp.swift Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Revert "Full translation into Spanish (#1529)" (#1543) This reverts commitf25fdfb89f. * Revert "update the translations (#1540)" (#1544) This reverts commitcb2fd8cc15. * Revert "NFC Tag contact (#1537)" (#1545) This reverts commit5c22b8b6e0. * Update Meshtastic/Views/Messages/TapbackInputView.swift Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update Meshtastic/Helpers/EmojiOnlyTextField.swift Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Revert "Accessorymanager background discovery (#1542)" (#1553) This reverts commit487f24b99a. * Make BLE Transport an actor to fix background discovery crashes * Update protobufs * Protobufs * Remove UI Kit code, clean up waypoint form emoji picker * Remove redundant nested Task in tapback emoji handler (#1552) * Initial plan * Remove nested Task block in tapback handler Co-authored-by: garthvh <1795163+garthvh@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: garthvh <1795163+garthvh@users.noreply.github.com> * Delete empty file * Handle nil for emoji keyboard type extension * Remove UI kit method from waypoint form emoji picker * Remove UI kit emoji picker from tapback * Update Meshtastic/Accessory/Transports/Bluetooth Low Energy/BLETransport.swift Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Throw too many retries error again, remove return --------- Co-authored-by: MGJ <62177301+MGJ520@users.noreply.github.com> Co-authored-by: Mike Robbins <mrobbins@alum.mit.edu> Co-authored-by: Benjamin Faershtein <119711889+RCGV1@users.noreply.github.com> Co-authored-by: Alvaro Samudio <alvarosamudio@protonmail.com> Co-authored-by: Mathew Kamkar <578302+matkam@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Ben Meadors <benmmeadors@gmail.com> Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: garthvh <1795163+garthvh@users.noreply.github.com>
This commit is contained in:
parent
b30dc8645a
commit
bc39cbd2b7
23 changed files with 499 additions and 140 deletions
|
|
@ -14315,6 +14315,7 @@
|
|||
}
|
||||
},
|
||||
"Favorited and ignored nodes are always retained. Nodes without PKC keys are cleared from the app database on the schedule set by the user, nodes with PKC keys are cleared only if the interval is set to 7 days or longer. This feature only purges nodes from the app that are not stored in the device node database." : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
|
|
@ -14335,6 +14336,9 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Favorited and ignored nodes are always retained. Other nodes are cleared from the app database on the schedule set by the user. (Nodes with PKC keys are always retained for at least 7 days.) This feature only purges nodes from the app that are not stored in the device node database." : {
|
||||
|
||||
},
|
||||
"Favorites" : {
|
||||
"localizations" : {
|
||||
|
|
@ -31617,6 +31621,9 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Select an emoji" : {
|
||||
|
||||
},
|
||||
"Select Channel" : {
|
||||
"localizations" : {
|
||||
|
|
@ -35547,6 +35554,9 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Tap to enter emoji" : {
|
||||
|
||||
},
|
||||
"Tapback" : {
|
||||
"localizations" : {
|
||||
|
|
@ -42321,4 +42331,4 @@
|
|||
}
|
||||
},
|
||||
"version" : "1.1"
|
||||
}
|
||||
}
|
||||
|
|
@ -115,6 +115,7 @@
|
|||
D93068D52B812B700066FBC8 /* MessageDestination.swift in Sources */ = {isa = PBXBuildFile; fileRef = D93068D42B812B700066FBC8 /* MessageDestination.swift */; };
|
||||
D93068D72B8146690066FBC8 /* MessageText.swift in Sources */ = {isa = PBXBuildFile; fileRef = D93068D62B8146690066FBC8 /* MessageText.swift */; };
|
||||
D93068D92B81509C0066FBC8 /* TapbackResponses.swift in Sources */ = {isa = PBXBuildFile; fileRef = D93068D82B81509C0066FBC8 /* TapbackResponses.swift */; };
|
||||
D93068DA2B81509D0066FBC8 /* TapbackInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D93068D92B81509D0066FBC8 /* TapbackInputView.swift */; };
|
||||
D93068DB2B81C85E0066FBC8 /* PowerConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = D93068DA2B81C85E0066FBC8 /* PowerConfig.swift */; };
|
||||
D93068DD2B81CA820066FBC8 /* ConfigHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = D93068DC2B81CA820066FBC8 /* ConfigHeader.swift */; };
|
||||
D93069082B81DF040066FBC8 /* SaveConfigButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D93069072B81DF040066FBC8 /* SaveConfigButton.swift */; };
|
||||
|
|
@ -201,7 +202,6 @@
|
|||
DD93800B2BA3F968008BEC06 /* NodeMapContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD93800A2BA3F968008BEC06 /* NodeMapContent.swift */; };
|
||||
DD93800E2BA74D0C008BEC06 /* ChannelForm.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD93800D2BA74D0C008BEC06 /* ChannelForm.swift */; };
|
||||
DD94B7402ACCE3BE00DCD1D1 /* MapSettingsForm.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD94B73F2ACCE3BE00DCD1D1 /* MapSettingsForm.swift */; };
|
||||
DD964FBD296E6B01007C176F /* EmojiOnlyTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD964FBC296E6B01007C176F /* EmojiOnlyTextField.swift */; };
|
||||
DD964FC2297272AE007C176F /* WaypointEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD964FC1297272AE007C176F /* WaypointEntityExtension.swift */; };
|
||||
DD964FC62975DBFD007C176F /* QueryCoreData.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD964FC52975DBFD007C176F /* QueryCoreData.swift */; };
|
||||
DD97E96628EFD9820056DDA4 /* MeshtasticLogo.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD97E96528EFD9820056DDA4 /* MeshtasticLogo.swift */; };
|
||||
|
|
@ -215,6 +215,7 @@
|
|||
DD9C70112E916EBD00106227 /* UpdateIntervalPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD9C70102E916EA200106227 /* UpdateIntervalPicker.swift */; };
|
||||
DDA0B6B2294CDC55001356EC /* Channels.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA0B6B1294CDC55001356EC /* Channels.swift */; };
|
||||
DDA1C48E28DB49D3009933EC /* ChannelRoles.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA1C48D28DB49D3009933EC /* ChannelRoles.swift */; };
|
||||
DDA3DFDA2F10B39600D8F103 /* UIKeyboardType.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA3DFD92F10B39600D8F103 /* UIKeyboardType.swift */; };
|
||||
DDA6B2E928419CF2003E8C16 /* MeshPackets.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA6B2E828419CF2003E8C16 /* MeshPackets.swift */; };
|
||||
DDA9515A2BC6624100CEA535 /* TelemetryWeather.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA951592BC6624100CEA535 /* TelemetryWeather.swift */; };
|
||||
DDA9515C2BC6631200CEA535 /* TelemetryEnums.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA9515B2BC6631200CEA535 /* TelemetryEnums.swift */; };
|
||||
|
|
@ -427,6 +428,7 @@
|
|||
D93068D42B812B700066FBC8 /* MessageDestination.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageDestination.swift; sourceTree = "<group>"; };
|
||||
D93068D62B8146690066FBC8 /* MessageText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageText.swift; sourceTree = "<group>"; };
|
||||
D93068D82B81509C0066FBC8 /* TapbackResponses.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TapbackResponses.swift; sourceTree = "<group>"; };
|
||||
D93068D92B81509D0066FBC8 /* TapbackInputView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TapbackInputView.swift; sourceTree = "<group>"; };
|
||||
D93068DA2B81C85E0066FBC8 /* PowerConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PowerConfig.swift; sourceTree = "<group>"; };
|
||||
D93068DC2B81CA820066FBC8 /* ConfigHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigHeader.swift; sourceTree = "<group>"; };
|
||||
D93069062B81D8900066FBC8 /* MeshtasticDataModelV 27.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 27.xcdatamodel"; sourceTree = "<group>"; };
|
||||
|
|
@ -545,7 +547,6 @@
|
|||
DD93800A2BA3F968008BEC06 /* NodeMapContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeMapContent.swift; sourceTree = "<group>"; };
|
||||
DD93800D2BA74D0C008BEC06 /* ChannelForm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelForm.swift; sourceTree = "<group>"; };
|
||||
DD94B73F2ACCE3BE00DCD1D1 /* MapSettingsForm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapSettingsForm.swift; sourceTree = "<group>"; };
|
||||
DD964FBC296E6B01007C176F /* EmojiOnlyTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiOnlyTextField.swift; sourceTree = "<group>"; };
|
||||
DD964FC029724F6D007C176F /* MeshtasticDataModelV6.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV6.xcdatamodel; sourceTree = "<group>"; };
|
||||
DD964FC1297272AE007C176F /* WaypointEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaypointEntityExtension.swift; sourceTree = "<group>"; };
|
||||
DD964FC52975DBFD007C176F /* QueryCoreData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueryCoreData.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -563,6 +564,7 @@
|
|||
DDA0B6B1294CDC55001356EC /* Channels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Channels.swift; sourceTree = "<group>"; };
|
||||
DDA1C48D28DB49D3009933EC /* ChannelRoles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelRoles.swift; sourceTree = "<group>"; };
|
||||
DDA28B1B2D32C89200EF726F /* MeshtasticDataModelV 48.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 48.xcdatamodel"; sourceTree = "<group>"; };
|
||||
DDA3DFD92F10B39600D8F103 /* UIKeyboardType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIKeyboardType.swift; sourceTree = "<group>"; };
|
||||
DDA6B2E828419CF2003E8C16 /* MeshPackets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshPackets.swift; sourceTree = "<group>"; };
|
||||
DDA951592BC6624100CEA535 /* TelemetryWeather.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelemetryWeather.swift; sourceTree = "<group>"; };
|
||||
DDA9515B2BC6631200CEA535 /* TelemetryEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TelemetryEnums.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -1250,6 +1252,7 @@
|
|||
D93068D62B8146690066FBC8 /* MessageText.swift */,
|
||||
D93068D22B8129510066FBC8 /* MessageContextMenuItems.swift */,
|
||||
D93068D82B81509C0066FBC8 /* TapbackResponses.swift */,
|
||||
D93068D92B81509D0066FBC8 /* TapbackInputView.swift */,
|
||||
);
|
||||
path = Messages;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -1291,7 +1294,6 @@
|
|||
DD1BEF492E0292220090CE24 /* KeychainHelper.swift */,
|
||||
DD913638270DFF4C00D7ACF3 /* LocalNotificationManager.swift */,
|
||||
DDA6B2E828419CF2003E8C16 /* MeshPackets.swift */,
|
||||
DD964FBC296E6B01007C176F /* EmojiOnlyTextField.swift */,
|
||||
DD3619142B1EF9F900C41C8C /* LocationsHandler.swift */,
|
||||
6D825E612C34786C008DBEE4 /* CommonRegex.swift */,
|
||||
3D3417B32E2730EC006A988B /* GeoJSONOverlayManager.swift */,
|
||||
|
|
@ -1371,6 +1373,7 @@
|
|||
DDDB444729F8A9C900EE2349 /* String.swift */,
|
||||
DD77093E2AA1B146007A8BF0 /* UIColor.swift */,
|
||||
DDDB444F29F8AC9C00EE2349 /* UIImage.swift */,
|
||||
DDA3DFD92F10B39600D8F103 /* UIKeyboardType.swift */,
|
||||
DDDB443F29F79AB000EE2349 /* UserDefaults.swift */,
|
||||
DDB75A0E2A05920E006ED576 /* FileManager.swift */,
|
||||
DDB75A102A059258006ED576 /* Url.swift */,
|
||||
|
|
@ -1668,7 +1671,6 @@
|
|||
DD77093B2AA1ABB8007A8BF0 /* BluetoothTips.swift in Sources */,
|
||||
D9C9839D2B79CFD700BDBE6A /* TextMessageSize.swift in Sources */,
|
||||
DDC94FCE29CF55310082EA6E /* RtttlConfig.swift in Sources */,
|
||||
DD964FBD296E6B01007C176F /* EmojiOnlyTextField.swift in Sources */,
|
||||
DD1BEF4E2E03916A0090CE24 /* ChannelsHelp.swift in Sources */,
|
||||
DD8169FF272476C700F4AB02 /* LogDocument.swift in Sources */,
|
||||
BCD93CBA2D9E11A2006C9214 /* DisconnectNodeIntent.swift in Sources */,
|
||||
|
|
@ -1801,6 +1803,7 @@
|
|||
DDA6B2E928419CF2003E8C16 /* MeshPackets.swift in Sources */,
|
||||
DDCE4E2C2869F92900BE9F8F /* UserConfig.swift in Sources */,
|
||||
BCB613852C68703800485544 /* NodePositionIntent.swift in Sources */,
|
||||
DDA3DFDA2F10B39600D8F103 /* UIKeyboardType.swift in Sources */,
|
||||
DDB75A212A12B954006ED576 /* LoRaSignalStrength.swift in Sources */,
|
||||
DD6193752862F6E600E59241 /* ExternalNotificationConfig.swift in Sources */,
|
||||
DD268D8E2BCC90E2008073AE /* RouteEnums.swift in Sources */,
|
||||
|
|
@ -1809,6 +1812,7 @@
|
|||
DD4975A52B147BA90026544E /* AmbientLightingConfig.swift in Sources */,
|
||||
3D3417D22E2DC260006A988B /* MapDataManager.swift in Sources */,
|
||||
D93068D92B81509C0066FBC8 /* TapbackResponses.swift in Sources */,
|
||||
D93068DA2B81509D0066FBC8 /* TapbackInputView.swift in Sources */,
|
||||
DDA9F5E82E77FAC100E70DEB /* AnimatedNodePin.swift in Sources */,
|
||||
DDF82CBD2D5BC69200DC25EC /* NavigateToButton.swift in Sources */,
|
||||
8D3F8A3F2D44BB02009EAAA4 /* PowerMetrics.swift in Sources */,
|
||||
|
|
@ -2102,7 +2106,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 14.6;
|
||||
MARKETING_VERSION = 2.7.6;
|
||||
MARKETING_VERSION = 2.7.7;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
|
|
@ -2137,7 +2141,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 14.6;
|
||||
MARKETING_VERSION = 2.7.6;
|
||||
MARKETING_VERSION = 2.7.7;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
|
|
@ -2169,7 +2173,7 @@
|
|||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 14.6;
|
||||
MARKETING_VERSION = 2.7.6;
|
||||
MARKETING_VERSION = 2.7.7;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
|
|
@ -2202,7 +2206,7 @@
|
|||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 14.6;
|
||||
MARKETING_VERSION = 2.7.6;
|
||||
MARKETING_VERSION = 2.7.7;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
|
|
|
|||
|
|
@ -352,7 +352,6 @@ actor SequentialSteps {
|
|||
return
|
||||
}
|
||||
isRunning = false
|
||||
return
|
||||
throw AccessoryError.tooManyRetries
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ extension AccessoryManager {
|
|||
let tasks = transports.map { transport in
|
||||
Task {
|
||||
Logger.transport.info("🔎 [Discovery] Discovery stream started for transport \(String(describing: transport.type), privacy: .public)")
|
||||
for await event in transport.discoverDevices() {
|
||||
for await event in await transport.discoverDevices() {
|
||||
continuation.yield(event)
|
||||
}
|
||||
Logger.transport.info("🔎 [Discovery] Discovery stream closed for transport \(String(describing: transport.type), privacy: .public)")
|
||||
|
|
|
|||
|
|
@ -441,8 +441,6 @@ extension AccessoryManager {
|
|||
Logger.services.error("Error while sending saveChannelSet request. No active device.")
|
||||
throw AccessoryError.ioFailed("No active device")
|
||||
}
|
||||
var i: Int32 = 0
|
||||
var myInfo: MyInfoEntity
|
||||
// Before we get started delete the existing channels from the myNodeInfo
|
||||
if !addChannels {
|
||||
tryClearExistingChannels()
|
||||
|
|
@ -451,64 +449,74 @@ extension AccessoryManager {
|
|||
let decodedString = base64UrlString.base64urlToBase64()
|
||||
if let decodedData = Data(base64Encoded: decodedString) {
|
||||
let channelSet: ChannelSet = try ChannelSet(serializedBytes: decodedData)
|
||||
|
||||
var myInfo: MyInfoEntity!
|
||||
var i: Int32 = 0
|
||||
|
||||
if addChannels {
|
||||
let fetchMyInfoRequest = MyInfoEntity.fetchRequest()
|
||||
fetchMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %lld", Int64(deviceNum))
|
||||
|
||||
let fetchedMyInfo = try context.fetch(fetchMyInfoRequest)
|
||||
if fetchedMyInfo.count != 1 {
|
||||
throw AccessoryError.appError("MyInfo not found")
|
||||
}
|
||||
|
||||
// We are trying to add a channel so lets get the last index
|
||||
myInfo = fetchedMyInfo[0]
|
||||
i = Int32(myInfo.channels?.count ?? -1)
|
||||
|
||||
// Bail out if the index is negative or bigger than our max of 8
|
||||
if i < 0 || i > 8 {
|
||||
throw AccessoryError.appError("Index out of range \(i)")
|
||||
}
|
||||
}
|
||||
|
||||
for cs in channelSet.settings {
|
||||
|
||||
if addChannels {
|
||||
// We are trying to add a channel so lets get the last index
|
||||
let fetchMyInfoRequest = MyInfoEntity.fetchRequest()
|
||||
fetchMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %lld", Int64(deviceNum))
|
||||
do {
|
||||
let fetchedMyInfo = try context.fetch(fetchMyInfoRequest)
|
||||
if fetchedMyInfo.count == 1 {
|
||||
i = Int32(fetchedMyInfo[0].channels?.count ?? -1)
|
||||
myInfo = fetchedMyInfo[0]
|
||||
// Bail out if the index is negative or bigger than our max of 8
|
||||
if i < 0 || i > 8 {
|
||||
throw AccessoryError.appError("Index out of range \(i)")
|
||||
}
|
||||
// Bail out if there are no channels or if the same channel name already exists
|
||||
guard let mutableChannels = myInfo.channels!.mutableCopy() as? NSMutableOrderedSet else {
|
||||
throw AccessoryError.appError("No channels or channel")
|
||||
}
|
||||
if mutableChannels.first(where: {($0 as AnyObject).name == cs.name }) is ChannelEntity {
|
||||
throw AccessoryError.appError("Channel already exists")
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
Logger.data.error("Failed to find a node MyInfo to save these channels to: \(error.localizedDescription, privacy: .public)")
|
||||
guard let mutableChannels = myInfo.channels?.mutableCopy() as? NSMutableOrderedSet else {
|
||||
throw AccessoryError.appError("No channels or channel")
|
||||
}
|
||||
|
||||
// Bail out if there are no channels or if the same channel name already exists
|
||||
if mutableChannels.first(where: { ($0 as AnyObject).name == cs.name }) is ChannelEntity {
|
||||
throw AccessoryError.appError("Channel already exists")
|
||||
}
|
||||
}
|
||||
|
||||
var chan = Channel()
|
||||
if i == 0 {
|
||||
chan.role = Channel.Role.primary
|
||||
} else {
|
||||
chan.role = Channel.Role.secondary
|
||||
}
|
||||
chan.role = (i == 0) ? .primary : .secondary
|
||||
chan.settings = cs
|
||||
chan.index = i
|
||||
i += 1
|
||||
|
||||
var adminPacket = AdminMessage()
|
||||
adminPacket.setChannel = chan
|
||||
var meshPacket: MeshPacket = MeshPacket()
|
||||
|
||||
var meshPacket = MeshPacket()
|
||||
meshPacket.to = UInt32(deviceNum)
|
||||
meshPacket.from = UInt32(deviceNum)
|
||||
meshPacket.from = UInt32(deviceNum)
|
||||
meshPacket.id = UInt32.random(in: UInt32(UInt8.max)..<UInt32.max)
|
||||
meshPacket.priority = MeshPacket.Priority.reliable
|
||||
meshPacket.priority = MeshPacket.Priority.reliable
|
||||
meshPacket.wantAck = true
|
||||
meshPacket.channel = 0
|
||||
guard let adminData: Data = try? adminPacket.serializedData() else {
|
||||
|
||||
guard let adminData = try? adminPacket.serializedData() else {
|
||||
throw AccessoryError.ioFailed("saveChannelSet: Unable to serialize Admin packet")
|
||||
}
|
||||
|
||||
var dataMessage = DataMessage()
|
||||
dataMessage.payload = adminData
|
||||
dataMessage.portnum = PortNum.adminApp
|
||||
meshPacket.decoded = dataMessage
|
||||
var toRadio: ToRadio!
|
||||
toRadio = ToRadio()
|
||||
|
||||
var toRadio = ToRadio()
|
||||
toRadio.packet = meshPacket
|
||||
|
||||
let logString = String.localizedStringWithFormat("Sent a Channel for: %@ Channel Index %d".localized, String(deviceNum), chan.index)
|
||||
try await send(toRadio, debugDescription: logString)
|
||||
channelPacket(channel: chan, fromNum: self.activeDeviceNum ?? 0, context: context)
|
||||
}
|
||||
if !addChannels {
|
||||
// Save the LoRa Config and the device will reboot
|
||||
|
|
|
|||
|
|
@ -196,6 +196,8 @@ class AccessoryManager: ObservableObject, MqttClientProxyManagerDelegate {
|
|||
Logger.transport.error("Unable to send wantConfig (config): No device connected")
|
||||
return
|
||||
}
|
||||
|
||||
_ = clearStaleNodes(nodeExpireDays: Int(UserDefaults.purgeStaleNodeDays), context: self.context)
|
||||
|
||||
try await withTaskCancellationHandler {
|
||||
var toRadio: ToRadio = ToRadio()
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ protocol Connection: Actor {
|
|||
var isConnected: Bool { get }
|
||||
func send(_ data: ToRadio) async throws
|
||||
func connect() async throws -> AsyncStream<ConnectionEvent>
|
||||
func disconnect(withError: Error?, shouldReconnect: Bool) throws
|
||||
func disconnect(withError: Error?, shouldReconnect: Bool) async throws
|
||||
func drainPendingPackets() async throws
|
||||
func startDrainPendingPackets() throws
|
||||
|
||||
|
|
|
|||
|
|
@ -42,10 +42,10 @@ enum DiscoveryEvent {
|
|||
|
||||
protocol Transport {
|
||||
var type: TransportType { get }
|
||||
var status: TransportStatus { get }
|
||||
var status: TransportStatus { get async }
|
||||
|
||||
// Discovers devices asynchronously. For ongoing scans (e.g., BLE), this can yield via AsyncStream.
|
||||
func discoverDevices() -> AsyncStream<DiscoveryEvent>
|
||||
func discoverDevices() async -> AsyncStream<DiscoveryEvent>
|
||||
|
||||
// Connects to a device and returns a Connection.
|
||||
func connect(to device: Device) async throws -> any Connection
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ actor BLEConnection: Connection {
|
|||
self.delegate.setConnection(self)
|
||||
}
|
||||
|
||||
func disconnect(withError error: Error? = nil, shouldReconnect: Bool) throws {
|
||||
func disconnect(withError error: Error? = nil, shouldReconnect: Bool) async throws {
|
||||
if peripheral.state == .connected {
|
||||
if let characteristic = FROMRADIO_characteristic {
|
||||
peripheral.setNotifyValue(false, for: characteristic)
|
||||
|
|
@ -82,7 +82,7 @@ actor BLEConnection: Connection {
|
|||
}
|
||||
}
|
||||
|
||||
transport?.connectionDidDisconnect(fromPeripheral: peripheral)
|
||||
await transport?.connectionDidDisconnect(fromPeripheral: peripheral)
|
||||
|
||||
central.cancelPeripheralConnection(peripheral)
|
||||
peripheral.delegate = nil
|
||||
|
|
@ -217,8 +217,8 @@ actor BLEConnection: Connection {
|
|||
self.connectContinuation = nil
|
||||
}
|
||||
|
||||
private func notifyTransportOfDisconnect() {
|
||||
transport?.connectionDidDisconnect(fromPeripheral: peripheral)
|
||||
private func notifyTransportOfDisconnect() async {
|
||||
await transport?.connectionDidDisconnect(fromPeripheral: peripheral)
|
||||
}
|
||||
|
||||
func startRSSITask() {
|
||||
|
|
@ -450,7 +450,7 @@ actor BLEConnection: Connection {
|
|||
}
|
||||
|
||||
// Inform the active connection that there was an error and it should disconnect
|
||||
try self.disconnect(withError: error, shouldReconnect: shouldReconnect)
|
||||
try await self.disconnect(withError: error, shouldReconnect: shouldReconnect)
|
||||
}
|
||||
|
||||
func appDidEnterBackground() {
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import Foundation
|
|||
import SwiftUI
|
||||
import OSLog
|
||||
|
||||
class BLETransport: Transport {
|
||||
actor BLETransport: Transport {
|
||||
|
||||
let meshtasticServiceCBUUID = CBUUID(string: "0x6BA1B218-15A8-461F-9FA8-5DCAE273EAFD")
|
||||
private let kCentralRestoreID = "com.meshtastic.central"
|
||||
|
|
@ -31,7 +31,7 @@ class BLETransport: Transport {
|
|||
private var cleanupTask: Task<Void, Never>?
|
||||
|
||||
// Transport properties
|
||||
var supportsManualConnection: Bool = false
|
||||
let supportsManualConnection: Bool = false
|
||||
let requiresPeriodicHeartbeat = false
|
||||
|
||||
init() {
|
||||
|
|
@ -46,19 +46,24 @@ class BLETransport: Transport {
|
|||
self.delegate.setTransport(self)
|
||||
}
|
||||
|
||||
nonisolated func discoverDevices() -> AsyncStream<DiscoveryEvent> {
|
||||
private func setDiscoveredDeviceContinuation(_ cont: AsyncStream<DiscoveryEvent>.Continuation?) {
|
||||
self.discoveredDeviceContinuation = cont
|
||||
}
|
||||
|
||||
func discoverDevices() -> AsyncStream<DiscoveryEvent> {
|
||||
AsyncStream { cont in
|
||||
Task {
|
||||
self.discoveredDeviceContinuation = cont
|
||||
await self.setDiscoveredDeviceContinuation(cont)
|
||||
|
||||
// This gate is opened when the CBCentralManager is in poweredOn state.
|
||||
// Its probably open already, but just to be sure in case we get here too quickly.
|
||||
try await self.setupCompleteGate.wait()
|
||||
|
||||
if !restoreInProgress {
|
||||
if await !self.restoreInProgress {
|
||||
centralManager.scanForPeripherals(withServices: [meshtasticServiceCBUUID], options: [CBCentralManagerScanOptionAllowDuplicatesKey: true])
|
||||
|
||||
for alreadyDiscoveredPeripheral in self.discoveredPeripherals.values.map({$0.peripheral}) {
|
||||
let peripherals = await self.discoveredPeripherals.values.map({$0.peripheral})
|
||||
for alreadyDiscoveredPeripheral in peripherals {
|
||||
let device = Device(id: alreadyDiscoveredPeripheral.identifier,
|
||||
name: alreadyDiscoveredPeripheral.name ?? "Unknown",
|
||||
transportType: .ble,
|
||||
|
|
@ -66,11 +71,13 @@ class BLETransport: Transport {
|
|||
cont.yield(.deviceFound(device))
|
||||
}
|
||||
}
|
||||
setupCleanupTask()
|
||||
await setupCleanupTask()
|
||||
}
|
||||
cont.onTermination = { _ in
|
||||
Logger.transport.error("🛜 [BLE] Discovery event stream has been canecelled.")
|
||||
self.stopScanning()
|
||||
Task {
|
||||
await self.stopScanning()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -188,6 +195,12 @@ class BLETransport: Transport {
|
|||
}
|
||||
}
|
||||
|
||||
private func cancelConnectContinuation(for peripheral: CBPeripheral) {
|
||||
self.connectContinuation?.resume(throwing: CancellationError())
|
||||
self.connectContinuation = nil
|
||||
self.connectionDidDisconnect(fromPeripheral: peripheral)
|
||||
}
|
||||
|
||||
func connect(to device: Device) async throws -> any Connection {
|
||||
guard let peripheral = discoveredPeripherals[UUID(uuidString: device.identifier)!] else {
|
||||
throw AccessoryError.connectionFailed("Peripheral not found")
|
||||
|
|
@ -211,9 +224,9 @@ class BLETransport: Transport {
|
|||
self.activeConnection = newConnection
|
||||
return newConnection
|
||||
} onCancel: {
|
||||
self.connectContinuation?.resume(throwing: CancellationError())
|
||||
self.connectContinuation = nil
|
||||
self.connectionDidDisconnect(fromPeripheral: peripheral.peripheral)
|
||||
Task {
|
||||
await self.cancelConnectContinuation(for: peripheral.peripheral)
|
||||
}
|
||||
}
|
||||
Logger.transport.debug("🛜 [BLE] Connect complete.")
|
||||
return returnConnection
|
||||
|
|
@ -226,7 +239,7 @@ class BLETransport: Transport {
|
|||
Task {
|
||||
if await connection.peripheral.identifier == peripheral.identifier {
|
||||
try await connection.disconnect(withError: AccessoryError.disconnected("BLE connection lost"), shouldReconnect: true)
|
||||
self.connectionDidDisconnect(fromPeripheral: peripheral)
|
||||
await self.connectionDidDisconnect(fromPeripheral: peripheral)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -264,7 +277,7 @@ class BLETransport: Transport {
|
|||
Logger.transport.debug("🛜 [BLETransport] Error while connecting. Disconnecting the active connection.")
|
||||
Task {
|
||||
try? await activeConnection.disconnect(withError: error, shouldReconnect: shouldReconnect)
|
||||
self.connectionDidDisconnect(fromPeripheral: peripheral)
|
||||
await self.connectionDidDisconnect(fromPeripheral: peripheral)
|
||||
}
|
||||
} else {
|
||||
Logger.transport.error("🚨 [BLETransport] unhandled error. May be in an inconsistent state.")
|
||||
|
|
@ -372,15 +385,20 @@ class BLETransport: Transport {
|
|||
}
|
||||
|
||||
Logger.transport.error("🛜 [BLE] Restoring peripheral in connecting state. ✅ didConnect Received!")
|
||||
Task { @MainActor in
|
||||
// In this case we need a full reconnect, so do the wantConfig, wantDatabase, and versionCheck
|
||||
try? await AccessoryManager.shared.connect(to: device, withConnection: restoredConnection, wantConfig: true, wantDatabase: true, versionCheck: true)
|
||||
restoreInProgress = false
|
||||
let connectTask = Task { @MainActor in
|
||||
try await AccessoryManager.shared.connect(to: device, withConnection: restoredConnection, wantConfig: true, wantDatabase: true, versionCheck: true)
|
||||
}
|
||||
|
||||
do {
|
||||
try await connectTask.value
|
||||
} catch {
|
||||
Logger.transport.error("🛜 [BLE] Error connecting during state restoration: \(error, privacy: .public)")
|
||||
}
|
||||
self.restoreInProgress = false
|
||||
} catch {
|
||||
// We had a conneciton failure during restoration.
|
||||
// We had a connection failure during restoration.
|
||||
Logger.transport.error("🛜 [BLE] Error restoring peripheral in connecting state. \(error, privacy: .public)")
|
||||
restoreInProgress = false
|
||||
self.restoreInProgress = false
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -388,22 +406,28 @@ class BLETransport: Transport {
|
|||
let restoredConnection = BLEConnection(peripheral: peripheral, central: central, transport: self)
|
||||
self.activeConnection = restoredConnection
|
||||
Logger.transport.error("🛜 [BLE] Peripheral Connection found and state is connected setting this connection as the activeConnection.")
|
||||
Task { @MainActor in
|
||||
let connectTask = Task { @MainActor in
|
||||
// In this case we need a full reconnect, so do the wantConfig, wantDatabase, and versionCheck
|
||||
try? await AccessoryManager.shared.connect(to: device, withConnection: restoredConnection, wantConfig: false, wantDatabase: false, versionCheck: false)
|
||||
restoreInProgress = false
|
||||
try await AccessoryManager.shared.connect(to: device, withConnection: restoredConnection, wantConfig: false, wantDatabase: false, versionCheck: false)
|
||||
}
|
||||
do {
|
||||
try await connectTask.value
|
||||
} catch {
|
||||
Logger.transport.error("🛜 [BLE] Error connecting during state restoration: \(error, privacy: .public)")
|
||||
}
|
||||
|
||||
self.restoreInProgress = false
|
||||
Logger.transport.error("🛜 [BLE] Connection state successfully restored in the background.")
|
||||
default:
|
||||
// Since we're not going to attempt to reconnect in then allow normal device discovery
|
||||
Logger.transport.error("🛜 [BLE] Unhandled state restoration for state: \(cbPeripheralStateDescription(peripheral.state), privacy: .public).")
|
||||
restoreInProgress = false
|
||||
self.restoreInProgress = false
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func device(forManualConnection: String) -> Device? {
|
||||
nonisolated func device(forManualConnection: String) -> Device? {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -438,33 +462,33 @@ class BLEDelegate: NSObject, CBCentralManagerDelegate {
|
|||
}
|
||||
|
||||
func centralManagerDidUpdateState(_ central: CBCentralManager) {
|
||||
transport?.handleCentralState(central.state, central: central)
|
||||
Task { await transport?.handleCentralState(central.state, central: central) }
|
||||
}
|
||||
|
||||
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String: Any], rssi RSSI: NSNumber) {
|
||||
transport?.didDiscover(peripheral: peripheral, rssi: RSSI)
|
||||
Task { await transport?.didDiscover(peripheral: peripheral, rssi: RSSI) }
|
||||
}
|
||||
|
||||
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
|
||||
transport?.handleDidConnect(peripheral: peripheral, central: central)
|
||||
Task { await transport?.handleDidConnect(peripheral: peripheral, central: central) }
|
||||
}
|
||||
|
||||
func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
|
||||
transport?.handleDidFailToConnect(peripheral: peripheral, error: error)
|
||||
Task { await transport?.handleDidFailToConnect(peripheral: peripheral, error: error) }
|
||||
}
|
||||
|
||||
func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
|
||||
if let error = error as? NSError {
|
||||
Logger.transport.error("🛜 [BLETransport] Error while disconnecting peripheral: \(peripheral.name ?? ""): \(error)")
|
||||
transport?.handlePeripheralDisconnectError(peripheral: peripheral, error: error)
|
||||
Task { await transport?.handlePeripheralDisconnectError(peripheral: peripheral, error: error) }
|
||||
} else {
|
||||
Logger.transport.error("🛜 [BLETransport] Did succesfully disconnect peripheral: \(peripheral.name ?? "")")
|
||||
transport?.handlePeripheralDisconnect(peripheral: peripheral)
|
||||
Task { await transport?.handlePeripheralDisconnect(peripheral: peripheral) }
|
||||
}
|
||||
}
|
||||
|
||||
func centralManager(_ central: CBCentralManager, willRestoreState dict: [String: Any]) {
|
||||
self.transport?.handleWillRestoreState(dict: dict, central: central)
|
||||
Task { await self.transport?.handleWillRestoreState(dict: dict, central: central) }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
13
Meshtastic/Extensions/UIKeyboardType.swift
Normal file
13
Meshtastic/Extensions/UIKeyboardType.swift
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
//
|
||||
// UIKeyboard.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Copyright(c) Garth Vander Houwen 1/7/26.
|
||||
//
|
||||
import UIKit
|
||||
|
||||
extension UIKeyboardType {
|
||||
static var emoji: UIKeyboardType {
|
||||
return UIKeyboardType(rawValue: 124) ?? .default
|
||||
}
|
||||
}
|
||||
|
|
@ -80,6 +80,7 @@ extension UserDefaults {
|
|||
case showDeviceOnboarding
|
||||
case usageDataAndCrashReporting
|
||||
case autoconnectOnDiscovery
|
||||
case purgeStaleNodeDays
|
||||
case manualConnections
|
||||
case testIntEnum
|
||||
}
|
||||
|
|
@ -178,6 +179,9 @@ extension UserDefaults {
|
|||
@UserDefault(.autoconnectOnDiscovery, defaultValue: true)
|
||||
static var autoconnectOnDiscovery: Bool
|
||||
|
||||
@UserDefault(.purgeStaleNodeDays, defaultValue: 0)
|
||||
static var purgeStaleNodeDays: Double
|
||||
|
||||
@UserDefault(.testIntEnum, defaultValue: .one)
|
||||
static var testIntEnum: TestIntEnum
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
import SwiftUI
|
||||
|
||||
class SwiftUIEmojiTextField: UITextField {
|
||||
var shouldBecomeFirstResponderOnAppear = false
|
||||
|
||||
func setEmoji() {
|
||||
_ = self.textInputMode
|
||||
|
|
@ -23,22 +24,39 @@ class SwiftUIEmojiTextField: UITextField {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
override func didMoveToWindow() {
|
||||
super.didMoveToWindow()
|
||||
if shouldBecomeFirstResponderOnAppear && window != nil {
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
self?.becomeFirstResponder()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct EmojiOnlyTextField: UIViewRepresentable {
|
||||
@Binding var text: String
|
||||
var placeholder: String = ""
|
||||
var onBecomeFirstResponder: (() -> Void)?
|
||||
var onKeyboardTypeChanged: ((Bool) -> Void)? // true if NOT emoji (should dismiss), false if emoji
|
||||
var onKeyboardDismissed: (() -> Void)? // Called when keyboard is dismissed
|
||||
|
||||
func makeUIView(context: Context) -> SwiftUIEmojiTextField {
|
||||
let emojiTextField = SwiftUIEmojiTextField()
|
||||
emojiTextField.placeholder = placeholder
|
||||
emojiTextField.text = text
|
||||
emojiTextField.delegate = context.coordinator
|
||||
emojiTextField.shouldBecomeFirstResponderOnAppear = true
|
||||
context.coordinator.textField = emojiTextField
|
||||
return emojiTextField
|
||||
}
|
||||
|
||||
func updateUIView(_ uiView: SwiftUIEmojiTextField, context: Context) {
|
||||
uiView.text = text
|
||||
context.coordinator.onBecomeFirstResponder = onBecomeFirstResponder
|
||||
context.coordinator.onKeyboardTypeChanged = onKeyboardTypeChanged
|
||||
context.coordinator.onKeyboardDismissed = onKeyboardDismissed
|
||||
}
|
||||
|
||||
func makeCoordinator() -> Coordinator {
|
||||
|
|
@ -47,13 +65,41 @@ struct EmojiOnlyTextField: UIViewRepresentable {
|
|||
|
||||
class Coordinator: NSObject, UITextFieldDelegate {
|
||||
var parent: EmojiOnlyTextField
|
||||
var textField: SwiftUIEmojiTextField?
|
||||
var onBecomeFirstResponder: (() -> Void)?
|
||||
var onKeyboardTypeChanged: ((Bool) -> Void)?
|
||||
var onKeyboardDismissed: (() -> Void)?
|
||||
var previousInputMode: String?
|
||||
|
||||
init(parent: EmojiOnlyTextField) {
|
||||
self.parent = parent
|
||||
}
|
||||
|
||||
func textFieldDidBeginEditing(_ textField: UITextField) {
|
||||
onBecomeFirstResponder?()
|
||||
checkInputMode(textField)
|
||||
}
|
||||
|
||||
func textFieldDidEndEditing(_ textField: UITextField) {
|
||||
// Keyboard was dismissed
|
||||
onKeyboardDismissed?()
|
||||
}
|
||||
|
||||
func textFieldDidChangeSelection(_ textField: UITextField) {
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
self?.parent.text = textField.text ?? ""
|
||||
}
|
||||
checkInputMode(textField)
|
||||
}
|
||||
|
||||
private func checkInputMode(_ textField: UITextField) {
|
||||
if let inputMode = textField.textInputMode {
|
||||
let isEmoji = inputMode.primaryLanguage == "emoji"
|
||||
if previousInputMode != inputMode.primaryLanguage {
|
||||
previousInputMode = inputMode.primaryLanguage
|
||||
onKeyboardTypeChanged?(!isEmoji) // true if NOT emoji (should dismiss)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -881,8 +881,8 @@ func telemetryPacket(packet: MeshPacket, connectedNode: Int64, context: NSManage
|
|||
let meshActivity = Activity<MeshActivityAttributes>.activities.first(where: { $0.attributes.nodeNum == connectedNode })
|
||||
if meshActivity != nil {
|
||||
Task {
|
||||
await meshActivity?.update(updatedContent, alertConfiguration: alertConfiguration)
|
||||
// await meshActivity?.update(updatedContent)
|
||||
// await meshActivity?.update(updatedContent, alertConfiguration: alertConfiguration)
|
||||
await meshActivity?.update(updatedContent)
|
||||
Logger.services.debug("Updated live activity.")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ struct MessageContextMenuItems: View {
|
|||
let tapBackDestination: MessageDestination
|
||||
let isCurrentUser: Bool
|
||||
@Binding var isShowingDeleteConfirmation: Bool
|
||||
@Binding var isShowingTapbackInput: Bool
|
||||
let onReply: () -> Void
|
||||
@State var relayDisplay: String? = nil
|
||||
|
||||
|
|
@ -29,30 +30,8 @@ struct MessageContextMenuItems: View {
|
|||
}
|
||||
}
|
||||
|
||||
Menu("Tapback") {
|
||||
ForEach(Tapbacks.allCases) { tb in
|
||||
Button {
|
||||
Task {
|
||||
do {
|
||||
try await accessoryManager.sendMessage(
|
||||
message: tb.emojiString,
|
||||
toUserNum: tapBackDestination.userNum,
|
||||
channel: tapBackDestination.channelNum,
|
||||
isEmoji: true,
|
||||
replyID: message.messageId
|
||||
)
|
||||
Task { @MainActor in
|
||||
self.context.refresh(tapBackDestination.managedObject, mergeChanges: true)
|
||||
}
|
||||
} catch {
|
||||
Logger.services.warning("Failed to send tapback.")
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
Text(tb.description)
|
||||
Image(uiImage: tb.emojiString.image()!)
|
||||
}
|
||||
}
|
||||
Button("Tapback") {
|
||||
isShowingTapbackInput = true
|
||||
}
|
||||
|
||||
Button(action: onReply) {
|
||||
|
|
|
|||
|
|
@ -27,13 +27,14 @@ struct MessageText: View {
|
|||
// State for handling channel URL sheet
|
||||
@State private var saveChannelLink: SaveChannelLinkData?
|
||||
@State private var isShowingDeleteConfirmation = false
|
||||
@State private var isShowingTapbackInput = false
|
||||
@State private var tapbackText = ""
|
||||
|
||||
var body: some View {
|
||||
|
||||
SessionReplayPrivacyView(textAndInputPrivacy: .maskAll) {
|
||||
|
||||
let markdownText = LocalizedStringKey(message.messagePayloadMarkdown ?? (message.messagePayload ?? "EMPTY MESSAGE"))
|
||||
return Text(markdownText)
|
||||
Text(markdownText)
|
||||
.tint(Self.linkBlue)
|
||||
.padding(.vertical, 10)
|
||||
.padding(.horizontal, 8)
|
||||
|
|
@ -91,6 +92,7 @@ struct MessageText: View {
|
|||
tapBackDestination: tapBackDestination,
|
||||
isCurrentUser: isCurrentUser,
|
||||
isShowingDeleteConfirmation: $isShowingDeleteConfirmation,
|
||||
isShowingTapbackInput: $isShowingTapbackInput,
|
||||
onReply: onReply
|
||||
)
|
||||
}
|
||||
|
|
@ -132,6 +134,36 @@ struct MessageText: View {
|
|||
.presentationDetents([.large])
|
||||
.presentationDragIndicator(.visible)
|
||||
}
|
||||
.sheet(isPresented: $isShowingTapbackInput) {
|
||||
TapbackInputView(
|
||||
text: $tapbackText,
|
||||
isPresented: $isShowingTapbackInput,
|
||||
onEmojiSelected: { emoji in
|
||||
Task {
|
||||
do {
|
||||
try await accessoryManager.sendMessage(
|
||||
message: emoji,
|
||||
toUserNum: tapBackDestination.userNum,
|
||||
channel: tapBackDestination.channelNum,
|
||||
isEmoji: true,
|
||||
replyID: message.messageId
|
||||
)
|
||||
await MainActor.run {
|
||||
switch tapBackDestination {
|
||||
case let .channel(channel):
|
||||
context.refresh(channel, mergeChanges: true)
|
||||
case let .user(user):
|
||||
context.refresh(user, mergeChanges: true)
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
Logger.services.warning("Failed to send tapback.")
|
||||
}
|
||||
}
|
||||
isShowingTapbackInput = false
|
||||
}
|
||||
)
|
||||
}
|
||||
.confirmationDialog(
|
||||
"Are you sure you want to delete this message?",
|
||||
isPresented: $isShowingDeleteConfirmation,
|
||||
|
|
|
|||
80
Meshtastic/Views/Messages/TapbackInputView.swift
Normal file
80
Meshtastic/Views/Messages/TapbackInputView.swift
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
import SwiftUI
|
||||
import UIKit
|
||||
|
||||
struct TapbackInputView: View {
|
||||
@Binding var text: String
|
||||
@Binding var isPresented: Bool
|
||||
let onEmojiSelected: (String) -> Void
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
VStack(spacing: 0) {
|
||||
TextField("Tap to enter emoji", text: $text)
|
||||
.keyboardType(.emoji)
|
||||
.frame(height: 50)
|
||||
.padding(.horizontal)
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 10)
|
||||
.strokeBorder(.tertiary, lineWidth: 1)
|
||||
.background(RoundedRectangle(cornerRadius: 10).fill(Color(.systemBackground)))
|
||||
)
|
||||
.padding(.horizontal)
|
||||
.padding(.top, 8)
|
||||
.onChange(of: text) { oldValue, newValue in
|
||||
// Extract first emoji character and send it
|
||||
if !newValue.isEmpty, let firstEmoji = extractFirstEmoji(from: newValue) {
|
||||
onEmojiSelected(firstEmoji)
|
||||
// Clear the text box after getting the emoji
|
||||
text = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("Tapback")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
Button("Cancel") {
|
||||
isPresented = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.presentationDetents([.height(120)])
|
||||
}
|
||||
|
||||
private func extractFirstEmoji(from string: String) -> String? {
|
||||
// Extract the first emoji character(s) - handle both single and multi-scalar emojis
|
||||
guard !string.isEmpty else { return nil }
|
||||
|
||||
// Try to get the first character
|
||||
let firstChar = string[string.startIndex]
|
||||
|
||||
// Check if it's an emoji using the existing extension
|
||||
if firstChar.isEmoji {
|
||||
// For multi-scalar emojis (like emojis with skin tones), we need to find the full emoji sequence
|
||||
var emojiEnd = string.index(after: string.startIndex)
|
||||
|
||||
// Check if there are continuation scalars (for emojis with skin tones, variation selectors, etc.)
|
||||
while emojiEnd < string.endIndex {
|
||||
let nextChar = string[emojiEnd]
|
||||
// Check if this is a continuation (variation selector, skin tone modifier, zero-width joiner, etc.)
|
||||
if let scalar = nextChar.unicodeScalars.first,
|
||||
(scalar.properties.isVariationSelector ||
|
||||
scalar.value == 0xFE0F || // Variation selector
|
||||
(scalar.value >= 0x1F3FB && scalar.value <= 0x1F3FF) || // Skin tone modifiers
|
||||
scalar.value == 0x200D) { // Zero-width joiner
|
||||
emojiEnd = string.index(after: emojiEnd)
|
||||
} else if nextChar.isEmoji {
|
||||
// If it's another emoji, include it (for compound emojis like flags)
|
||||
emojiEnd = string.index(after: emojiEnd)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return String(string[string.startIndex..<emojiEnd])
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
|
@ -29,7 +29,6 @@ struct WaypointForm: View {
|
|||
@State private var expire: Date = Date.now.addingTimeInterval(60 * 480) // 1 minute * 480 = 8 Hours
|
||||
@State private var locked: Bool = false
|
||||
@State private var lockedTo: Int64 = 0
|
||||
@State private var detents: Set<PresentationDetent> = [.medium, .fraction(0.85)]
|
||||
@State private var selectedDetent: PresentationDetent = .medium
|
||||
@State private var waypointFailedAlert: Bool = false
|
||||
|
||||
|
|
@ -111,26 +110,19 @@ struct WaypointForm: View {
|
|||
HStack {
|
||||
Text("Icon")
|
||||
Spacer()
|
||||
EmojiOnlyTextField(text: $icon, placeholder: "Select an emoji")
|
||||
TextField("Select an emoji", text: $icon)
|
||||
.keyboardType(.emoji)
|
||||
.font(.title)
|
||||
.focused($iconIsFocused)
|
||||
.onChange(of: icon) { _, value in
|
||||
|
||||
// If you have anything other than emojis in your string make it empty
|
||||
if !value.onlyEmojis() {
|
||||
icon = ""
|
||||
}
|
||||
// If a second emoji is entered delete the first one
|
||||
if value.count >= 1 {
|
||||
|
||||
if value.count > 1 {
|
||||
let index = value.index(value.startIndex, offsetBy: 1)
|
||||
icon = String(value[index])
|
||||
}
|
||||
iconIsFocused = false
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Toggle(isOn: $expires) {
|
||||
Label("Expires", systemImage: "clock.badge.xmark")
|
||||
|
|
@ -458,7 +450,6 @@ struct WaypointForm: View {
|
|||
longitude = waypoint.coordinate.longitude
|
||||
}
|
||||
}
|
||||
.presentationDetents(detents, selection: $selectedDetent)
|
||||
.presentationBackgroundInteraction(.enabled(upThrough: .fraction(0.85)))
|
||||
.presentationDragIndicator(.visible)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -120,16 +120,14 @@ struct MeshMap: View {
|
|||
}
|
||||
.sheet(item: $selectedWaypoint) { selection in
|
||||
WaypointForm(waypoint: selection)
|
||||
.padding()
|
||||
.presentationDetents([.large])
|
||||
}
|
||||
.sheet(item: $editingWaypoint) { selection in
|
||||
WaypointForm(waypoint: selection, editMode: true)
|
||||
.padding()
|
||||
.presentationDetents([.large])
|
||||
}
|
||||
.sheet(isPresented: $editingSettings) {
|
||||
MapSettingsForm(traffic: $showTraffic, pointsOfInterest: $showPointsOfInterest, mapLayer: $selectedMapLayer, meshMap: $isMeshMap, enabledOverlayConfigs: $enabledOverlayConfigs)
|
||||
.presentationDetents([.large])
|
||||
|
||||
}
|
||||
.onChange(of: router.navigationState) {
|
||||
guard case .map = router.navigationState.selectedTab else { return }
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@ struct AppSettings: View {
|
|||
Text("180")
|
||||
}
|
||||
}
|
||||
Text("Favorited and ignored nodes are always retained. Nodes without PKC keys are cleared from the app database on the schedule set by the user, nodes with PKC keys are cleared only if the interval is set to 7 days or longer. This feature only purges nodes from the app that are not stored in the device node database.")
|
||||
Text("Favorited and ignored nodes are always retained. Other nodes are cleared from the app database on the schedule set by the user. (Nodes with PKC keys are always retained for at least 7 days.) This feature only purges nodes from the app that are not stored in the device node database.")
|
||||
.foregroundStyle(.secondary)
|
||||
.font(idiom == .phone ? .caption : .callout)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,55 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP
|
|||
typealias Version = _2
|
||||
}
|
||||
|
||||
///
|
||||
/// Firmware update mode for OTA updates
|
||||
public enum OTAMode: SwiftProtobuf.Enum, Swift.CaseIterable {
|
||||
public typealias RawValue = Int
|
||||
|
||||
///
|
||||
/// Do not reboot into OTA mode
|
||||
case noRebootOta // = 0
|
||||
|
||||
///
|
||||
/// Reboot into OTA mode for BLE firmware update
|
||||
case otaBle // = 1
|
||||
|
||||
///
|
||||
/// Reboot into OTA mode for WiFi firmware update
|
||||
case otaWifi // = 2
|
||||
case UNRECOGNIZED(Int)
|
||||
|
||||
public init() {
|
||||
self = .noRebootOta
|
||||
}
|
||||
|
||||
public init?(rawValue: Int) {
|
||||
switch rawValue {
|
||||
case 0: self = .noRebootOta
|
||||
case 1: self = .otaBle
|
||||
case 2: self = .otaWifi
|
||||
default: self = .UNRECOGNIZED(rawValue)
|
||||
}
|
||||
}
|
||||
|
||||
public var rawValue: Int {
|
||||
switch self {
|
||||
case .noRebootOta: return 0
|
||||
case .otaBle: return 1
|
||||
case .otaWifi: return 2
|
||||
case .UNRECOGNIZED(let i): return i
|
||||
}
|
||||
}
|
||||
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
public static let allCases: [OTAMode] = [
|
||||
.noRebootOta,
|
||||
.otaBle,
|
||||
.otaWifi,
|
||||
]
|
||||
|
||||
}
|
||||
|
||||
///
|
||||
/// 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.
|
||||
|
|
@ -532,6 +581,9 @@ public struct AdminMessage: Sendable {
|
|||
///
|
||||
/// Tell the node to reboot into the OTA Firmware in this many seconds (or <0 to cancel reboot)
|
||||
/// Only Implemented for ESP32 Devices. This needs to be issued to send a new main firmware via bluetooth.
|
||||
/// Deprecated in favor of reboot_ota_mode in 2.7.17
|
||||
///
|
||||
/// NOTE: This field was marked as deprecated in the .proto file.
|
||||
public var rebootOtaSeconds: Int32 {
|
||||
get {
|
||||
if case .rebootOtaSeconds(let v)? = payloadVariant {return v}
|
||||
|
|
@ -592,6 +644,16 @@ public struct AdminMessage: Sendable {
|
|||
set {payloadVariant = .nodedbReset(newValue)}
|
||||
}
|
||||
|
||||
///
|
||||
/// Tell the node to reset into the OTA Loader
|
||||
public var otaRequest: AdminMessage.OTAEvent {
|
||||
get {
|
||||
if case .otaRequest(let v)? = payloadVariant {return v}
|
||||
return AdminMessage.OTAEvent()
|
||||
}
|
||||
set {payloadVariant = .otaRequest(newValue)}
|
||||
}
|
||||
|
||||
public var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
///
|
||||
|
|
@ -753,6 +815,9 @@ public struct AdminMessage: Sendable {
|
|||
///
|
||||
/// Tell the node to reboot into the OTA Firmware in this many seconds (or <0 to cancel reboot)
|
||||
/// Only Implemented for ESP32 Devices. This needs to be issued to send a new main firmware via bluetooth.
|
||||
/// Deprecated in favor of reboot_ota_mode in 2.7.17
|
||||
///
|
||||
/// NOTE: This field was marked as deprecated in the .proto file.
|
||||
case rebootOtaSeconds(Int32)
|
||||
///
|
||||
/// This message is only supported for the simulator Portduino build.
|
||||
|
|
@ -771,6 +836,9 @@ public struct AdminMessage: Sendable {
|
|||
/// Tell the node to reset the nodedb.
|
||||
/// When true, favorites are preserved through reset.
|
||||
case nodedbReset(Bool)
|
||||
///
|
||||
/// Tell the node to reset into the OTA Loader
|
||||
case otaRequest(AdminMessage.OTAEvent)
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -1059,6 +1127,28 @@ public struct AdminMessage: Sendable {
|
|||
public init() {}
|
||||
}
|
||||
|
||||
///
|
||||
/// User is requesting an over the air update.
|
||||
/// Node will reboot into the OTA loader
|
||||
public struct OTAEvent: 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.
|
||||
|
||||
///
|
||||
/// Tell the node to reboot into OTA mode for firmware update via BLE or WiFi (ESP32 only for now)
|
||||
public var rebootOtaMode: OTAMode = .noRebootOta
|
||||
|
||||
///
|
||||
/// A 32 byte hash of the OTA firmware.
|
||||
/// Used to verify the integrity of the firmware before applying an update.
|
||||
public var otaHash: Data = Data()
|
||||
|
||||
public var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
public init() {}
|
||||
}
|
||||
|
||||
public init() {}
|
||||
}
|
||||
|
||||
|
|
@ -1239,9 +1329,13 @@ public struct KeyVerificationAdmin: Sendable {
|
|||
|
||||
fileprivate let _protobuf_package = "meshtastic"
|
||||
|
||||
extension OTAMode: SwiftProtobuf._ProtoNameProviding {
|
||||
public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0NO_REBOOT_OTA\0\u{1}OTA_BLE\0\u{1}OTA_WIFI\0")
|
||||
}
|
||||
|
||||
extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
||||
public static let protoMessageName: String = _protobuf_package + ".AdminMessage"
|
||||
public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}get_channel_request\0\u{3}get_channel_response\0\u{3}get_owner_request\0\u{3}get_owner_response\0\u{3}get_config_request\0\u{3}get_config_response\0\u{3}get_module_config_request\0\u{3}get_module_config_response\0\u{4}\u{2}get_canned_message_module_messages_request\0\u{3}get_canned_message_module_messages_response\0\u{3}get_device_metadata_request\0\u{3}get_device_metadata_response\0\u{3}get_ringtone_request\0\u{3}get_ringtone_response\0\u{3}get_device_connection_status_request\0\u{3}get_device_connection_status_response\0\u{3}set_ham_mode\0\u{3}get_node_remote_hardware_pins_request\0\u{3}get_node_remote_hardware_pins_response\0\u{3}enter_dfu_mode_request\0\u{3}delete_file_request\0\u{3}set_scale\0\u{3}backup_preferences\0\u{3}restore_preferences\0\u{3}remove_backup_preferences\0\u{3}send_input_event\0\u{4}\u{5}set_owner\0\u{3}set_channel\0\u{3}set_config\0\u{3}set_module_config\0\u{3}set_canned_message_module_messages\0\u{3}set_ringtone_message\0\u{3}remove_by_nodenum\0\u{3}set_favorite_node\0\u{3}remove_favorite_node\0\u{3}set_fixed_position\0\u{3}remove_fixed_position\0\u{3}set_time_only\0\u{3}get_ui_config_request\0\u{3}get_ui_config_response\0\u{3}store_ui_config\0\u{3}set_ignored_node\0\u{3}remove_ignored_node\0\u{4}\u{10}begin_edit_settings\0\u{3}commit_edit_settings\0\u{3}add_contact\0\u{3}key_verification\0\u{4}\u{1b}factory_reset_device\0\u{3}reboot_ota_seconds\0\u{3}exit_simulator\0\u{3}reboot_seconds\0\u{3}shutdown_seconds\0\u{3}factory_reset_config\0\u{3}nodedb_reset\0\u{3}session_passkey\0")
|
||||
public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}get_channel_request\0\u{3}get_channel_response\0\u{3}get_owner_request\0\u{3}get_owner_response\0\u{3}get_config_request\0\u{3}get_config_response\0\u{3}get_module_config_request\0\u{3}get_module_config_response\0\u{4}\u{2}get_canned_message_module_messages_request\0\u{3}get_canned_message_module_messages_response\0\u{3}get_device_metadata_request\0\u{3}get_device_metadata_response\0\u{3}get_ringtone_request\0\u{3}get_ringtone_response\0\u{3}get_device_connection_status_request\0\u{3}get_device_connection_status_response\0\u{3}set_ham_mode\0\u{3}get_node_remote_hardware_pins_request\0\u{3}get_node_remote_hardware_pins_response\0\u{3}enter_dfu_mode_request\0\u{3}delete_file_request\0\u{3}set_scale\0\u{3}backup_preferences\0\u{3}restore_preferences\0\u{3}remove_backup_preferences\0\u{3}send_input_event\0\u{4}\u{5}set_owner\0\u{3}set_channel\0\u{3}set_config\0\u{3}set_module_config\0\u{3}set_canned_message_module_messages\0\u{3}set_ringtone_message\0\u{3}remove_by_nodenum\0\u{3}set_favorite_node\0\u{3}remove_favorite_node\0\u{3}set_fixed_position\0\u{3}remove_fixed_position\0\u{3}set_time_only\0\u{3}get_ui_config_request\0\u{3}get_ui_config_response\0\u{3}store_ui_config\0\u{3}set_ignored_node\0\u{3}remove_ignored_node\0\u{4}\u{10}begin_edit_settings\0\u{3}commit_edit_settings\0\u{3}add_contact\0\u{3}key_verification\0\u{4}\u{1b}factory_reset_device\0\u{3}reboot_ota_seconds\0\u{3}exit_simulator\0\u{3}reboot_seconds\0\u{3}shutdown_seconds\0\u{3}factory_reset_config\0\u{3}nodedb_reset\0\u{3}session_passkey\0\u{3}ota_request\0")
|
||||
|
||||
public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||
while let fieldNumber = try decoder.nextFieldNumber() {
|
||||
|
|
@ -1772,6 +1866,19 @@ extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat
|
|||
}
|
||||
}()
|
||||
case 101: try { try decoder.decodeSingularBytesField(value: &self.sessionPasskey) }()
|
||||
case 102: try {
|
||||
var v: AdminMessage.OTAEvent?
|
||||
var hadOneofValue = false
|
||||
if let current = self.payloadVariant {
|
||||
hadOneofValue = true
|
||||
if case .otaRequest(let m) = current {v = m}
|
||||
}
|
||||
try decoder.decodeSingularMessageField(value: &v)
|
||||
if let v = v {
|
||||
if hadOneofValue {try decoder.handleConflictingOneOf()}
|
||||
self.payloadVariant = .otaRequest(v)
|
||||
}
|
||||
}()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
|
@ -1999,11 +2106,14 @@ extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat
|
|||
guard case .nodedbReset(let v)? = self.payloadVariant else { preconditionFailure() }
|
||||
try visitor.visitSingularBoolField(value: v, fieldNumber: 100)
|
||||
}()
|
||||
case nil: break
|
||||
default: break
|
||||
}
|
||||
if !self.sessionPasskey.isEmpty {
|
||||
try visitor.visitSingularBytesField(value: self.sessionPasskey, fieldNumber: 101)
|
||||
}
|
||||
try { if case .otaRequest(let v)? = self.payloadVariant {
|
||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 102)
|
||||
} }()
|
||||
try unknownFields.traverse(visitor: &visitor)
|
||||
}
|
||||
|
||||
|
|
@ -2072,6 +2182,41 @@ extension AdminMessage.InputEvent: SwiftProtobuf.Message, SwiftProtobuf._Message
|
|||
}
|
||||
}
|
||||
|
||||
extension AdminMessage.OTAEvent: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
||||
public static let protoMessageName: String = AdminMessage.protoMessageName + ".OTAEvent"
|
||||
public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}reboot_ota_mode\0\u{3}ota_hash\0")
|
||||
|
||||
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.decodeSingularEnumField(value: &self.rebootOtaMode) }()
|
||||
case 2: try { try decoder.decodeSingularBytesField(value: &self.otaHash) }()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
|
||||
if self.rebootOtaMode != .noRebootOta {
|
||||
try visitor.visitSingularEnumField(value: self.rebootOtaMode, fieldNumber: 1)
|
||||
}
|
||||
if !self.otaHash.isEmpty {
|
||||
try visitor.visitSingularBytesField(value: self.otaHash, fieldNumber: 2)
|
||||
}
|
||||
try unknownFields.traverse(visitor: &visitor)
|
||||
}
|
||||
|
||||
public static func ==(lhs: AdminMessage.OTAEvent, rhs: AdminMessage.OTAEvent) -> Bool {
|
||||
if lhs.rebootOtaMode != rhs.rebootOtaMode {return false}
|
||||
if lhs.otaHash != rhs.otaHash {return false}
|
||||
if lhs.unknownFields != rhs.unknownFields {return false}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
extension HamParameters: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
||||
public static let protoMessageName: String = _protobuf_package + ".HamParameters"
|
||||
public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}call_sign\0\u{3}tx_power\0\u{1}frequency\0\u{3}short_name\0")
|
||||
|
|
|
|||
|
|
@ -166,8 +166,8 @@ public enum HardwareModel: SwiftProtobuf.Enum, Swift.CaseIterable {
|
|||
case loraRelayV1 // = 32
|
||||
|
||||
///
|
||||
/// TODO: REPLACE
|
||||
case nrf52840Dk // = 33
|
||||
/// T-Echo Plus device from LilyGo
|
||||
case tEchoPlus // = 33
|
||||
|
||||
///
|
||||
/// TODO: REPLACE
|
||||
|
|
@ -535,6 +535,10 @@ public enum HardwareModel: SwiftProtobuf.Enum, Swift.CaseIterable {
|
|||
/// Elecrow ThinkNode M6
|
||||
case thinknodeM6 // = 120
|
||||
|
||||
///
|
||||
/// Elecrow Meshstick 1262
|
||||
case meshstick1262 // = 121
|
||||
|
||||
///
|
||||
/// ------------------------------------------------------------------------------------------------------------------------------------------
|
||||
/// Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits.
|
||||
|
|
@ -581,7 +585,7 @@ public enum HardwareModel: SwiftProtobuf.Enum, Swift.CaseIterable {
|
|||
case 30: self = .rp2040Lora
|
||||
case 31: self = .stationG2
|
||||
case 32: self = .loraRelayV1
|
||||
case 33: self = .nrf52840Dk
|
||||
case 33: self = .tEchoPlus
|
||||
case 34: self = .ppr
|
||||
case 35: self = .genieblocks
|
||||
case 36: self = .nrf52Unknown
|
||||
|
|
@ -669,6 +673,7 @@ public enum HardwareModel: SwiftProtobuf.Enum, Swift.CaseIterable {
|
|||
case 118: self = .rak6421
|
||||
case 119: self = .thinknodeM4
|
||||
case 120: self = .thinknodeM6
|
||||
case 121: self = .meshstick1262
|
||||
case 255: self = .privateHw
|
||||
default: self = .UNRECOGNIZED(rawValue)
|
||||
}
|
||||
|
|
@ -709,7 +714,7 @@ public enum HardwareModel: SwiftProtobuf.Enum, Swift.CaseIterable {
|
|||
case .rp2040Lora: return 30
|
||||
case .stationG2: return 31
|
||||
case .loraRelayV1: return 32
|
||||
case .nrf52840Dk: return 33
|
||||
case .tEchoPlus: return 33
|
||||
case .ppr: return 34
|
||||
case .genieblocks: return 35
|
||||
case .nrf52Unknown: return 36
|
||||
|
|
@ -797,6 +802,7 @@ public enum HardwareModel: SwiftProtobuf.Enum, Swift.CaseIterable {
|
|||
case .rak6421: return 118
|
||||
case .thinknodeM4: return 119
|
||||
case .thinknodeM6: return 120
|
||||
case .meshstick1262: return 121
|
||||
case .privateHw: return 255
|
||||
case .UNRECOGNIZED(let i): return i
|
||||
}
|
||||
|
|
@ -837,7 +843,7 @@ public enum HardwareModel: SwiftProtobuf.Enum, Swift.CaseIterable {
|
|||
.rp2040Lora,
|
||||
.stationG2,
|
||||
.loraRelayV1,
|
||||
.nrf52840Dk,
|
||||
.tEchoPlus,
|
||||
.ppr,
|
||||
.genieblocks,
|
||||
.nrf52Unknown,
|
||||
|
|
@ -925,6 +931,7 @@ public enum HardwareModel: SwiftProtobuf.Enum, Swift.CaseIterable {
|
|||
.rak6421,
|
||||
.thinknodeM4,
|
||||
.thinknodeM6,
|
||||
.meshstick1262,
|
||||
.privateHw,
|
||||
]
|
||||
|
||||
|
|
@ -1921,6 +1928,11 @@ public struct Routing: Sendable {
|
|||
/// Airtime fairness rate limit exceeded for a packet
|
||||
/// This typically enforced per portnum and is used to prevent a single node from monopolizing airtime
|
||||
case rateLimitExceeded // = 38
|
||||
|
||||
///
|
||||
/// PKI encryption failed, due to no public key for the remote node
|
||||
/// This is different from PKI_UNKNOWN_PUBKEY which indicates a failure upon receiving a packet
|
||||
case pkiSendFailPublicKey // = 39
|
||||
case UNRECOGNIZED(Int)
|
||||
|
||||
public init() {
|
||||
|
|
@ -1946,6 +1958,7 @@ public struct Routing: Sendable {
|
|||
case 36: self = .adminBadSessionKey
|
||||
case 37: self = .adminPublicKeyUnauthorized
|
||||
case 38: self = .rateLimitExceeded
|
||||
case 39: self = .pkiSendFailPublicKey
|
||||
default: self = .UNRECOGNIZED(rawValue)
|
||||
}
|
||||
}
|
||||
|
|
@ -1969,6 +1982,7 @@ public struct Routing: Sendable {
|
|||
case .adminBadSessionKey: return 36
|
||||
case .adminPublicKeyUnauthorized: return 37
|
||||
case .rateLimitExceeded: return 38
|
||||
case .pkiSendFailPublicKey: return 39
|
||||
case .UNRECOGNIZED(let i): return i
|
||||
}
|
||||
}
|
||||
|
|
@ -1992,6 +2006,7 @@ public struct Routing: Sendable {
|
|||
.adminBadSessionKey,
|
||||
.adminPublicKeyUnauthorized,
|
||||
.rateLimitExceeded,
|
||||
.pkiSendFailPublicKey,
|
||||
]
|
||||
|
||||
}
|
||||
|
|
@ -2136,6 +2151,10 @@ public struct StoreForwardPlusPlus: Sendable {
|
|||
/// The receive time of the message in question
|
||||
public var encapsulatedRxtime: UInt32 = 0
|
||||
|
||||
///
|
||||
/// Used in a LINK_REQUEST to specify the message X spots back from head
|
||||
public var chainCount: UInt32 = 0
|
||||
|
||||
public var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
///
|
||||
|
|
@ -3959,7 +3978,7 @@ public struct ChunkedPayloadResponse: Sendable {
|
|||
fileprivate let _protobuf_package = "meshtastic"
|
||||
|
||||
extension HardwareModel: SwiftProtobuf._ProtoNameProviding {
|
||||
public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0UNSET\0\u{1}TLORA_V2\0\u{1}TLORA_V1\0\u{1}TLORA_V2_1_1P6\0\u{1}TBEAM\0\u{1}HELTEC_V2_0\0\u{1}TBEAM_V0P7\0\u{1}T_ECHO\0\u{1}TLORA_V1_1P3\0\u{1}RAK4631\0\u{1}HELTEC_V2_1\0\u{1}HELTEC_V1\0\u{1}LILYGO_TBEAM_S3_CORE\0\u{1}RAK11200\0\u{1}NANO_G1\0\u{1}TLORA_V2_1_1P8\0\u{1}TLORA_T3_S3\0\u{1}NANO_G1_EXPLORER\0\u{1}NANO_G2_ULTRA\0\u{1}LORA_TYPE\0\u{1}WIPHONE\0\u{1}WIO_WM1110\0\u{1}RAK2560\0\u{1}HELTEC_HRU_3601\0\u{1}HELTEC_WIRELESS_BRIDGE\0\u{1}STATION_G1\0\u{1}RAK11310\0\u{1}SENSELORA_RP2040\0\u{1}SENSELORA_S3\0\u{1}CANARYONE\0\u{1}RP2040_LORA\0\u{1}STATION_G2\0\u{1}LORA_RELAY_V1\0\u{1}NRF52840DK\0\u{1}PPR\0\u{1}GENIEBLOCKS\0\u{1}NRF52_UNKNOWN\0\u{1}PORTDUINO\0\u{1}ANDROID_SIM\0\u{1}DIY_V1\0\u{1}NRF52840_PCA10059\0\u{1}DR_DEV\0\u{1}M5STACK\0\u{1}HELTEC_V3\0\u{1}HELTEC_WSL_V3\0\u{1}BETAFPV_2400_TX\0\u{1}BETAFPV_900_NANO_TX\0\u{1}RPI_PICO\0\u{1}HELTEC_WIRELESS_TRACKER\0\u{1}HELTEC_WIRELESS_PAPER\0\u{1}T_DECK\0\u{1}T_WATCH_S3\0\u{1}PICOMPUTER_S3\0\u{1}HELTEC_HT62\0\u{1}EBYTE_ESP32_S3\0\u{1}ESP32_S3_PICO\0\u{1}CHATTER_2\0\u{1}HELTEC_WIRELESS_PAPER_V1_0\0\u{1}HELTEC_WIRELESS_TRACKER_V1_0\0\u{1}UNPHONE\0\u{1}TD_LORAC\0\u{1}CDEBYTE_EORA_S3\0\u{1}TWC_MESH_V4\0\u{1}NRF52_PROMICRO_DIY\0\u{1}RADIOMASTER_900_BANDIT_NANO\0\u{1}HELTEC_CAPSULE_SENSOR_V3\0\u{1}HELTEC_VISION_MASTER_T190\0\u{1}HELTEC_VISION_MASTER_E213\0\u{1}HELTEC_VISION_MASTER_E290\0\u{1}HELTEC_MESH_NODE_T114\0\u{1}SENSECAP_INDICATOR\0\u{1}TRACKER_T1000_E\0\u{1}RAK3172\0\u{1}WIO_E5\0\u{1}RADIOMASTER_900_BANDIT\0\u{1}ME25LS01_4Y10TD\0\u{1}RP2040_FEATHER_RFM95\0\u{1}M5STACK_COREBASIC\0\u{1}M5STACK_CORE2\0\u{1}RPI_PICO2\0\u{1}M5STACK_CORES3\0\u{1}SEEED_XIAO_S3\0\u{1}MS24SF1\0\u{1}TLORA_C6\0\u{1}WISMESH_TAP\0\u{1}ROUTASTIC\0\u{1}MESH_TAB\0\u{1}MESHLINK\0\u{1}XIAO_NRF52_KIT\0\u{1}THINKNODE_M1\0\u{1}THINKNODE_M2\0\u{1}T_ETH_ELITE\0\u{1}HELTEC_SENSOR_HUB\0\u{1}MUZI_BASE\0\u{1}HELTEC_MESH_POCKET\0\u{1}SEEED_SOLAR_NODE\0\u{1}NOMADSTAR_METEOR_PRO\0\u{1}CROWPANEL\0\u{1}LINK_32\0\u{1}SEEED_WIO_TRACKER_L1\0\u{1}SEEED_WIO_TRACKER_L1_EINK\0\u{1}MUZI_R1_NEO\0\u{1}T_DECK_PRO\0\u{1}T_LORA_PAGER\0\u{1}M5STACK_RESERVED\0\u{1}WISMESH_TAG\0\u{1}RAK3312\0\u{1}THINKNODE_M5\0\u{1}HELTEC_MESH_SOLAR\0\u{1}T_ECHO_LITE\0\u{1}HELTEC_V4\0\u{1}M5STACK_C6L\0\u{1}M5STACK_CARDPUTER_ADV\0\u{1}HELTEC_WIRELESS_TRACKER_V2\0\u{1}T_WATCH_ULTRA\0\u{1}THINKNODE_M3\0\u{1}WISMESH_TAP_V2\0\u{1}RAK3401\0\u{1}RAK6421\0\u{1}THINKNODE_M4\0\u{1}THINKNODE_M6\0\u{2}G\u{2}PRIVATE_HW\0")
|
||||
public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0UNSET\0\u{1}TLORA_V2\0\u{1}TLORA_V1\0\u{1}TLORA_V2_1_1P6\0\u{1}TBEAM\0\u{1}HELTEC_V2_0\0\u{1}TBEAM_V0P7\0\u{1}T_ECHO\0\u{1}TLORA_V1_1P3\0\u{1}RAK4631\0\u{1}HELTEC_V2_1\0\u{1}HELTEC_V1\0\u{1}LILYGO_TBEAM_S3_CORE\0\u{1}RAK11200\0\u{1}NANO_G1\0\u{1}TLORA_V2_1_1P8\0\u{1}TLORA_T3_S3\0\u{1}NANO_G1_EXPLORER\0\u{1}NANO_G2_ULTRA\0\u{1}LORA_TYPE\0\u{1}WIPHONE\0\u{1}WIO_WM1110\0\u{1}RAK2560\0\u{1}HELTEC_HRU_3601\0\u{1}HELTEC_WIRELESS_BRIDGE\0\u{1}STATION_G1\0\u{1}RAK11310\0\u{1}SENSELORA_RP2040\0\u{1}SENSELORA_S3\0\u{1}CANARYONE\0\u{1}RP2040_LORA\0\u{1}STATION_G2\0\u{1}LORA_RELAY_V1\0\u{1}T_ECHO_PLUS\0\u{1}PPR\0\u{1}GENIEBLOCKS\0\u{1}NRF52_UNKNOWN\0\u{1}PORTDUINO\0\u{1}ANDROID_SIM\0\u{1}DIY_V1\0\u{1}NRF52840_PCA10059\0\u{1}DR_DEV\0\u{1}M5STACK\0\u{1}HELTEC_V3\0\u{1}HELTEC_WSL_V3\0\u{1}BETAFPV_2400_TX\0\u{1}BETAFPV_900_NANO_TX\0\u{1}RPI_PICO\0\u{1}HELTEC_WIRELESS_TRACKER\0\u{1}HELTEC_WIRELESS_PAPER\0\u{1}T_DECK\0\u{1}T_WATCH_S3\0\u{1}PICOMPUTER_S3\0\u{1}HELTEC_HT62\0\u{1}EBYTE_ESP32_S3\0\u{1}ESP32_S3_PICO\0\u{1}CHATTER_2\0\u{1}HELTEC_WIRELESS_PAPER_V1_0\0\u{1}HELTEC_WIRELESS_TRACKER_V1_0\0\u{1}UNPHONE\0\u{1}TD_LORAC\0\u{1}CDEBYTE_EORA_S3\0\u{1}TWC_MESH_V4\0\u{1}NRF52_PROMICRO_DIY\0\u{1}RADIOMASTER_900_BANDIT_NANO\0\u{1}HELTEC_CAPSULE_SENSOR_V3\0\u{1}HELTEC_VISION_MASTER_T190\0\u{1}HELTEC_VISION_MASTER_E213\0\u{1}HELTEC_VISION_MASTER_E290\0\u{1}HELTEC_MESH_NODE_T114\0\u{1}SENSECAP_INDICATOR\0\u{1}TRACKER_T1000_E\0\u{1}RAK3172\0\u{1}WIO_E5\0\u{1}RADIOMASTER_900_BANDIT\0\u{1}ME25LS01_4Y10TD\0\u{1}RP2040_FEATHER_RFM95\0\u{1}M5STACK_COREBASIC\0\u{1}M5STACK_CORE2\0\u{1}RPI_PICO2\0\u{1}M5STACK_CORES3\0\u{1}SEEED_XIAO_S3\0\u{1}MS24SF1\0\u{1}TLORA_C6\0\u{1}WISMESH_TAP\0\u{1}ROUTASTIC\0\u{1}MESH_TAB\0\u{1}MESHLINK\0\u{1}XIAO_NRF52_KIT\0\u{1}THINKNODE_M1\0\u{1}THINKNODE_M2\0\u{1}T_ETH_ELITE\0\u{1}HELTEC_SENSOR_HUB\0\u{1}MUZI_BASE\0\u{1}HELTEC_MESH_POCKET\0\u{1}SEEED_SOLAR_NODE\0\u{1}NOMADSTAR_METEOR_PRO\0\u{1}CROWPANEL\0\u{1}LINK_32\0\u{1}SEEED_WIO_TRACKER_L1\0\u{1}SEEED_WIO_TRACKER_L1_EINK\0\u{1}MUZI_R1_NEO\0\u{1}T_DECK_PRO\0\u{1}T_LORA_PAGER\0\u{1}M5STACK_RESERVED\0\u{1}WISMESH_TAG\0\u{1}RAK3312\0\u{1}THINKNODE_M5\0\u{1}HELTEC_MESH_SOLAR\0\u{1}T_ECHO_LITE\0\u{1}HELTEC_V4\0\u{1}M5STACK_C6L\0\u{1}M5STACK_CARDPUTER_ADV\0\u{1}HELTEC_WIRELESS_TRACKER_V2\0\u{1}T_WATCH_ULTRA\0\u{1}THINKNODE_M3\0\u{1}WISMESH_TAP_V2\0\u{1}RAK3401\0\u{1}RAK6421\0\u{1}THINKNODE_M4\0\u{1}THINKNODE_M6\0\u{1}MESHSTICK_1262\0\u{2}F\u{2}PRIVATE_HW\0")
|
||||
}
|
||||
|
||||
extension Constants: SwiftProtobuf._ProtoNameProviding {
|
||||
|
|
@ -4409,7 +4428,7 @@ extension Routing: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBa
|
|||
}
|
||||
|
||||
extension Routing.Error: SwiftProtobuf._ProtoNameProviding {
|
||||
public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0NONE\0\u{1}NO_ROUTE\0\u{1}GOT_NAK\0\u{1}TIMEOUT\0\u{1}NO_INTERFACE\0\u{1}MAX_RETRANSMIT\0\u{1}NO_CHANNEL\0\u{1}TOO_LARGE\0\u{1}NO_RESPONSE\0\u{1}DUTY_CYCLE_LIMIT\0\u{2}\u{17}BAD_REQUEST\0\u{1}NOT_AUTHORIZED\0\u{1}PKI_FAILED\0\u{1}PKI_UNKNOWN_PUBKEY\0\u{1}ADMIN_BAD_SESSION_KEY\0\u{1}ADMIN_PUBLIC_KEY_UNAUTHORIZED\0\u{1}RATE_LIMIT_EXCEEDED\0")
|
||||
public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0NONE\0\u{1}NO_ROUTE\0\u{1}GOT_NAK\0\u{1}TIMEOUT\0\u{1}NO_INTERFACE\0\u{1}MAX_RETRANSMIT\0\u{1}NO_CHANNEL\0\u{1}TOO_LARGE\0\u{1}NO_RESPONSE\0\u{1}DUTY_CYCLE_LIMIT\0\u{2}\u{17}BAD_REQUEST\0\u{1}NOT_AUTHORIZED\0\u{1}PKI_FAILED\0\u{1}PKI_UNKNOWN_PUBKEY\0\u{1}ADMIN_BAD_SESSION_KEY\0\u{1}ADMIN_PUBLIC_KEY_UNAUTHORIZED\0\u{1}RATE_LIMIT_EXCEEDED\0\u{1}PKI_SEND_FAIL_PUBLIC_KEY\0")
|
||||
}
|
||||
|
||||
extension DataMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
||||
|
|
@ -4528,7 +4547,7 @@ extension KeyVerification: SwiftProtobuf.Message, SwiftProtobuf._MessageImplemen
|
|||
|
||||
extension StoreForwardPlusPlus: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
||||
public static let protoMessageName: String = _protobuf_package + ".StoreForwardPlusPlus"
|
||||
public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}sfpp_message_type\0\u{3}message_hash\0\u{3}commit_hash\0\u{3}root_hash\0\u{1}message\0\u{3}encapsulated_id\0\u{3}encapsulated_to\0\u{3}encapsulated_from\0\u{3}encapsulated_rxtime\0")
|
||||
public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}sfpp_message_type\0\u{3}message_hash\0\u{3}commit_hash\0\u{3}root_hash\0\u{1}message\0\u{3}encapsulated_id\0\u{3}encapsulated_to\0\u{3}encapsulated_from\0\u{3}encapsulated_rxtime\0\u{3}chain_count\0")
|
||||
|
||||
public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||
while let fieldNumber = try decoder.nextFieldNumber() {
|
||||
|
|
@ -4545,6 +4564,7 @@ extension StoreForwardPlusPlus: SwiftProtobuf.Message, SwiftProtobuf._MessageImp
|
|||
case 7: try { try decoder.decodeSingularUInt32Field(value: &self.encapsulatedTo) }()
|
||||
case 8: try { try decoder.decodeSingularUInt32Field(value: &self.encapsulatedFrom) }()
|
||||
case 9: try { try decoder.decodeSingularUInt32Field(value: &self.encapsulatedRxtime) }()
|
||||
case 10: try { try decoder.decodeSingularUInt32Field(value: &self.chainCount) }()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
|
@ -4578,6 +4598,9 @@ extension StoreForwardPlusPlus: SwiftProtobuf.Message, SwiftProtobuf._MessageImp
|
|||
if self.encapsulatedRxtime != 0 {
|
||||
try visitor.visitSingularUInt32Field(value: self.encapsulatedRxtime, fieldNumber: 9)
|
||||
}
|
||||
if self.chainCount != 0 {
|
||||
try visitor.visitSingularUInt32Field(value: self.chainCount, fieldNumber: 10)
|
||||
}
|
||||
try unknownFields.traverse(visitor: &visitor)
|
||||
}
|
||||
|
||||
|
|
@ -4591,6 +4614,7 @@ extension StoreForwardPlusPlus: SwiftProtobuf.Message, SwiftProtobuf._MessageImp
|
|||
if lhs.encapsulatedTo != rhs.encapsulatedTo {return false}
|
||||
if lhs.encapsulatedFrom != rhs.encapsulatedFrom {return false}
|
||||
if lhs.encapsulatedRxtime != rhs.encapsulatedRxtime {return false}
|
||||
if lhs.chainCount != rhs.chainCount {return false}
|
||||
if lhs.unknownFields != rhs.unknownFields {return false}
|
||||
return true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit 62ef17b3d1625fc6d78ed661f614d0baad4be9ef
|
||||
Subproject commit 4ed2d1a35e7f486708ead6d06fb2597c9aa87245
|
||||
Loading…
Add table
Add a link
Reference in a new issue