From cfdcc5a2c3e97c74c0a2f4f96959afbee1b73377 Mon Sep 17 00:00:00 2001 From: Benjamin Faershtein <119711889+RCGV1@users.noreply.github.com> Date: Sun, 25 Aug 2024 21:14:25 -0700 Subject: [PATCH] Added Shortcuts provider and Shut Down, Reboot, and Factory Reset Intents. Also added logging for intent errors. --- Localizable.xcstrings | 47 +++++++++++++++---- Meshtastic.xcodeproj/project.pbxproj | 16 +++++++ Meshtastic/AppIntents/AppIntentErrors.swift | 9 +++- .../AppIntents/FactoryResetNodeIntent.swift | 41 ++++++++++++++++ .../AppIntents/MessageChannelIntent.swift | 2 +- .../AppIntents/NodePositionIntent.swift | 1 + Meshtastic/AppIntents/RestartNodeIntent.swift | 42 +++++++++++++++++ .../AppIntents/SendWaypointIntent.swift | 2 +- Meshtastic/AppIntents/ShortcutsProvider.swift | 37 +++++++++++++++ .../AppIntents/ShutDownNodeIntent.swift | 41 ++++++++++++++++ 10 files changed, 226 insertions(+), 12 deletions(-) create mode 100644 Meshtastic/AppIntents/FactoryResetNodeIntent.swift create mode 100644 Meshtastic/AppIntents/RestartNodeIntent.swift create mode 100644 Meshtastic/AppIntents/ShortcutsProvider.swift create mode 100644 Meshtastic/AppIntents/ShutDownNodeIntent.swift diff --git a/Localizable.xcstrings b/Localizable.xcstrings index f1806ddf7..74281fbd9 100644 --- a/Localizable.xcstrings +++ b/Localizable.xcstrings @@ -1166,6 +1166,9 @@ }, "Are you sure you want to delete this message?" : { + }, + "Are you sure you want to factory reset the node?" : { + }, "are.you.sure" : { "localizations" : { @@ -6325,7 +6328,7 @@ "Direct Message Help" : { }, - "Direct messages are using the new public key infrastructure for encryption. Reguires firmware version 2.5 or greater." : { + "Direct messages are using the new public key infrastructure for encryption. Requires firmware version 2.5 or greater." : { }, "Direct messages are using the shared key for the channel." : { @@ -7209,6 +7212,9 @@ }, "Factory Reset" : { + }, + "Factory Reset Node" : { + }, "Factory reset your device and app? " : { @@ -7227,12 +7233,12 @@ }, "Favorites" : { - }, - "Fetch the latest position of a cetain node" : { - }, "Favorites and nodes with recent messages show up at the top of the contact list." : { - + + }, + "Fetch the latest position of a cetain node" : { + }, "Fifteen Minutes" : { @@ -14563,11 +14569,15 @@ }, "Message" : { + }, + "Message Channel" : { + }, "Message content exceeds 228 bytes." : { + }, "Message Status Options" : { - + }, "message.details" : { "localizations" : { @@ -16319,6 +16329,9 @@ } } } + }, + "Perform a factory reset on the node you are connected to" : { + }, "phone.gps" : { "localizations" : { @@ -17024,6 +17037,9 @@ } } } + }, + "Reboot Node?" : { + }, "reboot.node" : { "localizations" : { @@ -17389,6 +17405,12 @@ }, "Reset NodeDB" : { + }, + "Restart Node" : { + + }, + "Restart to the node you are connected to" : { + }, "restore" : { @@ -19131,13 +19153,16 @@ "Send" : { }, - "Send a channel message" : { + "Send a Channel Message" : { }, "Send a message to a certain meshtastic channel" : { }, - "Send a waypoint" : { + "Send a shutdown to the node you are connected to" : { + + }, + "Send a Waypoint" : { }, "Send ASCII bell with alert message. Useful for triggering external notification on bell." : { @@ -19876,6 +19901,12 @@ }, "Show Weather" : { + }, + "Shut Down Node" : { + + }, + "Shut Down Node?" : { + }, "Shutdown Node?" : { diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 6b02e8f19..09d0f827e 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -36,6 +36,10 @@ 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 */; }; + BCE2D3C32C7ADF42008E6199 /* ShutDownNodeIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCE2D3C22C7ADF42008E6199 /* ShutDownNodeIntent.swift */; }; + BCE2D3C52C7AE369008E6199 /* RestartNodeIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCE2D3C42C7AE369008E6199 /* RestartNodeIntent.swift */; }; + BCE2D3C72C7B0D0A008E6199 /* ShortcutsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCE2D3C62C7B0D0A008E6199 /* ShortcutsProvider.swift */; }; + BCE2D3C92C7C377F008E6199 /* FactoryResetNodeIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCE2D3C82C7C377F008E6199 /* FactoryResetNodeIntent.swift */; }; C9697FA527933B8C00250207 /* SQLite in Frameworks */ = {isa = PBXBuildFile; productRef = C9697FA427933B8C00250207 /* SQLite */; }; D93068D32B8129510066FBC8 /* MessageContextMenuItems.swift in Sources */ = {isa = PBXBuildFile; fileRef = D93068D22B8129510066FBC8 /* MessageContextMenuItems.swift */; }; D93068D52B812B700066FBC8 /* MessageDestination.swift in Sources */ = {isa = PBXBuildFile; fileRef = D93068D42B812B700066FBC8 /* MessageDestination.swift */; }; @@ -275,6 +279,10 @@ BCB613822C672A2600485544 /* MessageChannelIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageChannelIntent.swift; sourceTree = ""; }; BCB613842C68703800485544 /* NodePositionIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodePositionIntent.swift; sourceTree = ""; }; BCB613862C69A0FB00485544 /* AppIntentErrors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppIntentErrors.swift; sourceTree = ""; }; + BCE2D3C22C7ADF42008E6199 /* ShutDownNodeIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShutDownNodeIntent.swift; sourceTree = ""; }; + BCE2D3C42C7AE369008E6199 /* RestartNodeIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RestartNodeIntent.swift; sourceTree = ""; }; + BCE2D3C62C7B0D0A008E6199 /* ShortcutsProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShortcutsProvider.swift; sourceTree = ""; }; + BCE2D3C82C7C377F008E6199 /* FactoryResetNodeIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FactoryResetNodeIntent.swift; sourceTree = ""; }; D93068D22B8129510066FBC8 /* MessageContextMenuItems.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageContextMenuItems.swift; sourceTree = ""; }; D93068D42B812B700066FBC8 /* MessageDestination.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageDestination.swift; sourceTree = ""; }; D93068D62B8146690066FBC8 /* MessageText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageText.swift; sourceTree = ""; }; @@ -573,6 +581,10 @@ BCB613822C672A2600485544 /* MessageChannelIntent.swift */, BCB613842C68703800485544 /* NodePositionIntent.swift */, BCB613862C69A0FB00485544 /* AppIntentErrors.swift */, + BCE2D3C22C7ADF42008E6199 /* ShutDownNodeIntent.swift */, + BCE2D3C42C7AE369008E6199 /* RestartNodeIntent.swift */, + BCE2D3C82C7C377F008E6199 /* FactoryResetNodeIntent.swift */, + BCE2D3C62C7B0D0A008E6199 /* ShortcutsProvider.swift */, ); path = AppIntents; sourceTree = ""; @@ -1257,6 +1269,7 @@ 25F26B1F2C2F611300C9CD9D /* AppData.swift in Sources */, 25F26B1E2C2F610D00C9CD9D /* Logger.swift in Sources */, 259792252C2F114500AD1659 /* ChannelEntityExtension.swift in Sources */, + BCE2D3C52C7AE369008E6199 /* RestartNodeIntent.swift in Sources */, 259792262C2F114500AD1659 /* PositionEntityExtension.swift in Sources */, 259792272C2F114500AD1659 /* TraceRouteEntityExtension.swift in Sources */, DDDB444829F8A9C900EE2349 /* String.swift in Sources */, @@ -1361,6 +1374,7 @@ D93068D32B8129510066FBC8 /* MessageContextMenuItems.swift in Sources */, DD8EBF43285058FA00426DCA /* DisplayConfig.swift in Sources */, DD964FC42974767D007C176F /* MapViewFitExtension.swift in Sources */, + BCE2D3C72C7B0D0A008E6199 /* ShortcutsProvider.swift in Sources */, DD47E3D626F17ED900029299 /* CircleText.swift in Sources */, DDC2E18F26CE25FE0042C5E4 /* ContentView.swift in Sources */, DD2553572855B02500E55709 /* LoRaConfig.swift in Sources */, @@ -1421,6 +1435,7 @@ BCB613872C69A0FB00485544 /* AppIntentErrors.swift in Sources */, DD73FD1128750779000852D6 /* PositionLog.swift in Sources */, DD15E4F52B8BFC8E00654F61 /* PaxCounterLog.swift in Sources */, + BCE2D3C32C7ADF42008E6199 /* ShutDownNodeIntent.swift in Sources */, 25F5D5C22C3F6E4B008036E3 /* AppState.swift in Sources */, DD3CC6C028E7A60700FA9159 /* MessagingEnums.swift in Sources */, DD6F657B2C6EC2900053C113 /* LockLegend.swift in Sources */, @@ -1436,6 +1451,7 @@ DDDB26442AAC0206003AFCB7 /* NodeDetail.swift in Sources */, DD77093F2AA1B146007A8BF0 /* UIColor.swift in Sources */, DDF6B2482A9AEBF500BA6931 /* StoreForwardConfig.swift in Sources */, + BCE2D3C92C7C377F008E6199 /* FactoryResetNodeIntent.swift in Sources */, DD8169F9271F1A6100F4AB02 /* MeshLogger.swift in Sources */, DD93800B2BA3F968008BEC06 /* NodeMapContent.swift in Sources */, DD41582A28585C32009B0E59 /* RangeTestConfig.swift in Sources */, diff --git a/Meshtastic/AppIntents/AppIntentErrors.swift b/Meshtastic/AppIntents/AppIntentErrors.swift index c20ead7ca..8e80b6b61 100644 --- a/Meshtastic/AppIntents/AppIntentErrors.swift +++ b/Meshtastic/AppIntents/AppIntentErrors.swift @@ -6,6 +6,7 @@ // import Foundation +import OSLog class AppIntentErrors { enum AppIntentError: Swift.Error, CustomLocalizedStringResourceConvertible { @@ -14,8 +15,12 @@ class AppIntentErrors { var localizedStringResource: LocalizedStringResource { switch self { - case let .message(message): return "Error: \(message)" - case .notConnected: return "No Connected Node" + case let .message(message): + Logger.services.error("App Intent: \(message)") + return "Error: \(message)" + case .notConnected: + Logger.services.error("App Intent: No Connected Node") + return "No Connected Node" } } } diff --git a/Meshtastic/AppIntents/FactoryResetNodeIntent.swift b/Meshtastic/AppIntents/FactoryResetNodeIntent.swift new file mode 100644 index 000000000..f83a507b4 --- /dev/null +++ b/Meshtastic/AppIntents/FactoryResetNodeIntent.swift @@ -0,0 +1,41 @@ +// +// FactoryResetNodeIntent.swift +// Meshtastic +// +// Created by Benjamin Faershtein on 8/25/24. +// + +import Foundation +import AppIntents + +struct FactoryResetNodeIntent: AppIntent { + static var title: LocalizedStringResource = "Factory Reset Node" + static var description: IntentDescription = "Perform a factory reset on the node you are connected to" + + func perform() async throws -> some IntentResult { + // Request user confirmation before performing the factory reset + try await requestConfirmation(result: .result(dialog: "Are you sure you want to factory reset the node?"),confirmationActionName: ConfirmationActionName + .custom(acceptLabel: "Factory Reset", acceptAlternatives: [], denyLabel: "Cancel", denyAlternatives: [], destructive: true)) + + // Ensure the node is connected + if !BLEManager.shared.isConnected { + throw AppIntentErrors.AppIntentError.notConnected + } + + // Safely unwrap the connected node information + if let connectedPeripheralNum = BLEManager.shared.connectedPeripheral?.num, + let connectedNode = getNodeInfo(id: connectedPeripheralNum, context: PersistenceController.shared.container.viewContext), + let fromUser = connectedNode.user, + let toUser = connectedNode.user { + + // Attempt to send a factory reset command, throw an error if it fails + if !BLEManager.shared.sendFactoryReset(fromUser: fromUser, toUser: toUser) { + throw AppIntentErrors.AppIntentError.message("Failed to perform factory reset") + } + } else { + throw AppIntentErrors.AppIntentError.message("Failed to retrieve connected node or required data") + } +// + return .result() + } +} diff --git a/Meshtastic/AppIntents/MessageChannelIntent.swift b/Meshtastic/AppIntents/MessageChannelIntent.swift index 53e202a67..4f1042856 100644 --- a/Meshtastic/AppIntents/MessageChannelIntent.swift +++ b/Meshtastic/AppIntents/MessageChannelIntent.swift @@ -9,7 +9,7 @@ import Foundation import AppIntents struct MessageChannelIntent: AppIntent { - static var title: LocalizedStringResource = "Send a channel message" + static var title: LocalizedStringResource = "Send a Channel Message" static var description: IntentDescription = "Send a message to a certain meshtastic channel" diff --git a/Meshtastic/AppIntents/NodePositionIntent.swift b/Meshtastic/AppIntents/NodePositionIntent.swift index 496ca3e39..a173df0df 100644 --- a/Meshtastic/AppIntents/NodePositionIntent.swift +++ b/Meshtastic/AppIntents/NodePositionIntent.swift @@ -31,6 +31,7 @@ struct NodePositionIntent: AppIntent { } let nodeInfo = fetchedNode[0] + nodeInfo.latestEnvironmentMetrics?.batteryLevel if let latitude = nodeInfo.latestPosition?.coordinate.latitude, let longitude = nodeInfo.latestPosition?.coordinate.longitude { let nodeLocation = CLLocation(latitude: latitude, longitude: longitude) diff --git a/Meshtastic/AppIntents/RestartNodeIntent.swift b/Meshtastic/AppIntents/RestartNodeIntent.swift new file mode 100644 index 000000000..9bf8fd879 --- /dev/null +++ b/Meshtastic/AppIntents/RestartNodeIntent.swift @@ -0,0 +1,42 @@ +// +// RestartNodeIntent.swift +// Meshtastic +// +// Created by Benjamin Faershtein on 8/24/24. +// + +import Foundation +import AppIntents + +struct RestartNodeIntent: AppIntent { + static var title: LocalizedStringResource = "Restart Node" + + static var description: IntentDescription = "Restart to the node you are connected to" + + + func perform() async throws -> some IntentResult { + + try await requestConfirmation(result: .result(dialog: "Reboot Node?")) + + if !BLEManager.shared.isConnected { + throw AppIntentErrors.AppIntentError.notConnected + } + // Safely unwrap the connectedNode using if let + if let connectedPeripheralNum = BLEManager.shared.connectedPeripheral?.num, + let connectedNode = getNodeInfo(id: connectedPeripheralNum, context: PersistenceController.shared.container.viewContext), + let fromUser = connectedNode.user, + let toUser = connectedNode.user, + let adminIndex = connectedNode.myInfo?.adminIndex { + + // Attempt to send shutdown, throw an error if it fails + if !BLEManager.shared.sendReboot(fromUser: fromUser, toUser: toUser, adminIndex: adminIndex) { + throw AppIntentErrors.AppIntentError.message("Failed to restart") + } + } else { + throw AppIntentErrors.AppIntentError.message("Failed to retrieve connected node or required data") + } + + return .result() + } +} + diff --git a/Meshtastic/AppIntents/SendWaypointIntent.swift b/Meshtastic/AppIntents/SendWaypointIntent.swift index a94e8b7d2..392d232a2 100644 --- a/Meshtastic/AppIntents/SendWaypointIntent.swift +++ b/Meshtastic/AppIntents/SendWaypointIntent.swift @@ -12,7 +12,7 @@ import MeshtasticProtobufs struct SendWaypointIntent: AppIntent { - static var title = LocalizedStringResource("Send a waypoint") + static var title = LocalizedStringResource("Send a Waypoint") @Parameter(title: "Name", default: "Dropped Pin") var nameParameter: String? diff --git a/Meshtastic/AppIntents/ShortcutsProvider.swift b/Meshtastic/AppIntents/ShortcutsProvider.swift new file mode 100644 index 000000000..6a82f3f56 --- /dev/null +++ b/Meshtastic/AppIntents/ShortcutsProvider.swift @@ -0,0 +1,37 @@ +// +// ShortcutsProvider.swift +// Meshtastic +// +// Created by Benjamin Faershtein on 8/24/24. +// + +import Foundation +import AppIntents + +struct ShortcutsProvider: AppShortcutsProvider { + static var appShortcuts: [AppShortcut] { + AppShortcut(intent: ShutDownNodeIntent(), + phrases: ["Shut down node in \(.applicationName)", + "Turn off node in \(.applicationName)", + "Power down node in \(.applicationName)", + "Deactivate node in \(.applicationName)"], + shortTitle: "Shut Down Node", + systemImageName: "power") + + AppShortcut(intent: RestartNodeIntent(), + phrases: ["Restart node in \(.applicationName)", + "Reboot node in \(.applicationName)", + "Reset node in \(.applicationName)", + "Start node again in \(.applicationName)"], + shortTitle: "Restart Node", + systemImageName: "arrow.circlepath") + + AppShortcut(intent: MessageChannelIntent(), + phrases: ["Message channel in \(.applicationName)",], + shortTitle: "Message Channel", + systemImageName: "message") + } +} + + + diff --git a/Meshtastic/AppIntents/ShutDownNodeIntent.swift b/Meshtastic/AppIntents/ShutDownNodeIntent.swift new file mode 100644 index 000000000..ea57548d3 --- /dev/null +++ b/Meshtastic/AppIntents/ShutDownNodeIntent.swift @@ -0,0 +1,41 @@ +// +// ShutDownNodeIntent.swift +// Meshtastic +// +// Created by Benjamin Faershtein on 8/24/24. +// + +import Foundation +import AppIntents + +struct ShutDownNodeIntent: AppIntent { + static var title: LocalizedStringResource = "Shut Down Node" + + static var description: IntentDescription = "Send a shutdown to the node you are connected to" + + + func perform() async throws -> some IntentResult { + try await requestConfirmation(result: .result(dialog: "Shut Down Node?")) + + if !BLEManager.shared.isConnected { + throw AppIntentErrors.AppIntentError.notConnected + } + + // Safely unwrap the connectedNode using if let + if let connectedPeripheralNum = BLEManager.shared.connectedPeripheral?.num, + let connectedNode = getNodeInfo(id: connectedPeripheralNum, context: PersistenceController.shared.container.viewContext), + let fromUser = connectedNode.user, + let toUser = connectedNode.user, + let adminIndex = connectedNode.myInfo?.adminIndex { + + // Attempt to send shutdown, throw an error if it fails + if !BLEManager.shared.sendShutdown(fromUser: fromUser, toUser: toUser, adminIndex: adminIndex) { + throw AppIntentErrors.AppIntentError.message("Failed to shut down") + } + } else { + throw AppIntentErrors.AppIntentError.message("Failed to retrieve connected node or required data") + } + + return .result() + } +}