Skip to content

Commit

Permalink
Merge pull request #866 from meshtastic/AppIntents
Browse files Browse the repository at this point in the history
App intents
  • Loading branch information
garthvh authored Aug 24, 2024
2 parents 3f190f2 + c36380c commit f352e26
Show file tree
Hide file tree
Showing 10 changed files with 326 additions and 24 deletions.
54 changes: 52 additions & 2 deletions Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -2788,6 +2788,9 @@
},
"Channel Name" : {

},
"Channel number must be between 0 and 7." : {

},
"Channel Role" : {

Expand Down Expand Up @@ -4959,6 +4962,9 @@
}
}
}
},
"Could not find node" : {

},
"Counter Clockwise Rotary Event" : {

Expand Down Expand Up @@ -5202,6 +5208,9 @@
},
"Description" : {

},
"Description must be less than 100 bytes" : {

},
"Detection" : {

Expand Down Expand Up @@ -6826,6 +6835,9 @@
}
}
}
},
"Emoji" : {

},
"Empty" : {

Expand Down Expand Up @@ -6996,6 +7008,9 @@
},
"Erase all device and app data?" : {

},
"Error: %@" : {

},
"ESP 32 OTA update is a work in progress, click the button below to send your device a reboot into ota admin message." : {

Expand Down Expand Up @@ -7197,6 +7212,9 @@
},
"Factory reset your device and app? " : {

},
"Failed to encode message content" : {

},
"Failed to get a valid position to exchange" : {

Expand All @@ -7209,9 +7227,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." : {

},
"Fifteen Minutes" : {

Expand Down Expand Up @@ -7626,6 +7647,9 @@
},
"Get custom waterproof solar and detection sensor router nodes, aluminium desktop nodes and rugged handsets." : {

},
"Get Node Position" : {

},
"Get NRF DFU from the App Store" : {

Expand Down Expand Up @@ -10885,6 +10909,9 @@
},
"Loading Logs. . ." : {

},
"Location" : {

},
"Location: %@" : {

Expand Down Expand Up @@ -14536,9 +14563,11 @@
},
"Message" : {

},
"Message content exceeds 228 bytes." : {
},
"Message Status Options" : {

},
"message.details" : {
"localizations" : {
Expand Down Expand Up @@ -15142,6 +15171,9 @@
}
}
}
},
"Must be a single emoji" : {

},
"Nag timeout" : {

Expand Down Expand Up @@ -15206,6 +15238,9 @@
},
"Name" : {

},
"Name must be less than 30 bytes" : {

},
"Nearby Topics" : {

Expand Down Expand Up @@ -15337,6 +15372,9 @@
},
"Newer firmware is available" : {

},
"No Connected Node" : {

},
"No Device Metrics" : {

Expand Down Expand Up @@ -15418,6 +15456,9 @@
}
}
}
},
"Node does not have positions" : {

},
"Node History" : {

Expand Down Expand Up @@ -19083,6 +19124,15 @@
},
"Send" : {

},
"Send a channel message" : {

},
"Send a message to a certain meshtastic channel" : {

},
"Send a waypoint" : {

},
"Send ASCII bell with alert message. Useful for triggering external notification on bell." : {

Expand Down
24 changes: 24 additions & 0 deletions Meshtastic.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@
6DEDA55C2A9592F900321D2E /* MessageEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DEDA55B2A9592F900321D2E /* MessageEntityExtension.swift */; };
B399E8A42B6F486400E4488E /* RetryButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = B399E8A32B6F486400E4488E /* RetryButton.swift */; };
B3E905B12B71F7F300654D07 /* TextMessageField.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3E905B02B71F7F300654D07 /* TextMessageField.swift */; };
BCB613812C67290800485544 /* SendWaypointIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCB613802C67290800485544 /* SendWaypointIntent.swift */; };
BCB613832C672A2600485544 /* MessageChannelIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCB613822C672A2600485544 /* MessageChannelIntent.swift */; };
BCB613852C68703800485544 /* NodePositionIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCB613842C68703800485544 /* NodePositionIntent.swift */; };
BCB613872C69A0FB00485544 /* AppIntentErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCB613862C69A0FB00485544 /* AppIntentErrors.swift */; };
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 */; };
Expand Down Expand Up @@ -267,6 +271,10 @@
6DEDA55B2A9592F900321D2E /* MessageEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageEntityExtension.swift; sourceTree = "<group>"; };
B399E8A32B6F486400E4488E /* RetryButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RetryButton.swift; sourceTree = "<group>"; };
B3E905B02B71F7F300654D07 /* TextMessageField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextMessageField.swift; sourceTree = "<group>"; };
BCB613802C67290800485544 /* SendWaypointIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendWaypointIntent.swift; sourceTree = "<group>"; };
BCB613822C672A2600485544 /* MessageChannelIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageChannelIntent.swift; sourceTree = "<group>"; };
BCB613842C68703800485544 /* NodePositionIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodePositionIntent.swift; sourceTree = "<group>"; };
BCB613862C69A0FB00485544 /* AppIntentErrors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppIntentErrors.swift; sourceTree = "<group>"; };
D93068D22B8129510066FBC8 /* MessageContextMenuItems.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageContextMenuItems.swift; sourceTree = "<group>"; };
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>"; };
Expand Down Expand Up @@ -558,6 +566,17 @@
path = MeshtasticTests;
sourceTree = "<group>";
};
BCB6137F2C6728E700485544 /* AppIntents */ = {
isa = PBXGroup;
children = (
BCB613802C67290800485544 /* SendWaypointIntent.swift */,
BCB613822C672A2600485544 /* MessageChannelIntent.swift */,
BCB613842C68703800485544 /* NodePositionIntent.swift */,
BCB613862C69A0FB00485544 /* AppIntentErrors.swift */,
);
path = AppIntents;
sourceTree = "<group>";
};
C9483F6B2773016700998F6B /* MapKitMap */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -835,6 +854,7 @@
DDC2E15626CE248E0042C5E4 /* Meshtastic */ = {
isa = PBXGroup;
children = (
BCB6137F2C6728E700485544 /* AppIntents */,
DD1BD0EC2C603C5B008C0C70 /* Measurement */,
25F5D5BC2C3F6D7B008036E3 /* Router */,
DD7709392AA1ABA1007A8BF0 /* Tips */,
Expand Down Expand Up @@ -1364,6 +1384,7 @@
DDAD49ED2AFB39DC00B4425D /* MeshMap.swift in Sources */,
DD8169FB271F1F3A00F4AB02 /* MeshLog.swift in Sources */,
DD3CC24C2C498D6C001BD3A2 /* BatteryCompact.swift in Sources */,
BCB613812C67290800485544 /* SendWaypointIntent.swift in Sources */,
DD1B8F402B35E2F10022AABC /* GPSStatus.swift in Sources */,
DD8ED9C52898D51F00B3B0AB /* NetworkConfig.swift in Sources */,
DDC3B274283F411B00AC321C /* LastHeardText.swift in Sources */,
Expand All @@ -1379,6 +1400,7 @@
DDB75A1E2A0B0CD0006ED576 /* LoRaSignalStrengthIndicator.swift in Sources */,
DDA6B2E928419CF2003E8C16 /* MeshPackets.swift in Sources */,
DDCE4E2C2869F92900BE9F8F /* UserConfig.swift in Sources */,
BCB613852C68703800485544 /* NodePositionIntent.swift in Sources */,
DDB75A212A12B954006ED576 /* LoRaSignalStrength.swift in Sources */,
DD6193752862F6E600E59241 /* ExternalNotificationConfig.swift in Sources */,
DD268D8E2BCC90E2008073AE /* RouteEnums.swift in Sources */,
Expand All @@ -1396,13 +1418,15 @@
DD1933762B0835D500771CD5 /* PositionAltitudeChart.swift in Sources */,
DD415828285859C4009B0E59 /* TelemetryConfig.swift in Sources */,
DDB6CCFB2AAF805100945AF6 /* NodeMapSwiftUI.swift in Sources */,
BCB613872C69A0FB00485544 /* AppIntentErrors.swift in Sources */,
DD73FD1128750779000852D6 /* PositionLog.swift in Sources */,
DD15E4F52B8BFC8E00654F61 /* PaxCounterLog.swift in Sources */,
25F5D5C22C3F6E4B008036E3 /* AppState.swift in Sources */,
DD3CC6C028E7A60700FA9159 /* MessagingEnums.swift in Sources */,
DD6F657B2C6EC2900053C113 /* LockLegend.swift in Sources */,
DD97E96628EFD9820056DDA4 /* MeshtasticLogo.swift in Sources */,
DDAB580D2B0DAA9E00147258 /* Routes.swift in Sources */,
BCB613832C672A2600485544 /* MessageChannelIntent.swift in Sources */,
D93068D52B812B700066FBC8 /* MessageDestination.swift in Sources */,
DDA9515E2BC6F56F00CEA535 /* IndoorAirQuality.swift in Sources */,
DDDB444E29F8AB0E00EE2349 /* Int.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES"
askForAppToLaunch = "Yes"
launchAutomaticallySubstyle = "2">
<BuildableProductRunnable
runnableDebuggingMode = "0">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"originHash" : "af29d93455cb8f728684674f544d815b5becb17e049287cc1df8079a4855d0fc",
"originHash" : "1571e0d09fede5d57a2c415019f30868d90fde5a53a863cc277593881c2dc4a5",
"pins" : [
{
"identity" : "cocoamqtt",
Expand Down
22 changes: 22 additions & 0 deletions Meshtastic/AppIntents/AppIntentErrors.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//
// AppIntentErrors.swift
// Meshtastic
//
// Created by Benjamin Faershtein on 8/11/24.
//

import Foundation

class AppIntentErrors {
enum AppIntentError: Swift.Error, CustomLocalizedStringResourceConvertible {
case notConnected
case message(_ message: String)

var localizedStringResource: LocalizedStringResource {
switch self {
case let .message(message): return "Error: \(message)"
case .notConnected: return "No Connected Node"
}
}
}
}
51 changes: 51 additions & 0 deletions Meshtastic/AppIntents/MessageChannelIntent.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
//
// MessageChannelIntent.swift
// Meshtastic
//
// Created by Benjamin Faershtein on 8/9/24.
//

import Foundation
import AppIntents

struct MessageChannelIntent: AppIntent {
static var title: LocalizedStringResource = "Send a channel message"

static var description: IntentDescription = "Send a message to a certain meshtastic channel"

@Parameter(title: "Message")
var messageContent: String

@Parameter(title: "Channel",controlStyle: .stepper, inclusiveRange: (lowerBound: 0, upperBound: 7))
var channelNumber: Int


static var parameterSummary: some ParameterSummary {
Summary("Send \(\.$messageContent) to \(\.$channelNumber)")
}
func perform() async throws -> some IntentResult {
if (!BLEManager.shared.isConnected){
throw AppIntentErrors.AppIntentError.notConnected
}

// Check if channel number is between 1 and 7
guard (0...7).contains(channelNumber) else {
throw $channelNumber.needsValueError("Channel number must be between 0 and 7.")
}

// Convert messageContent to data and check its length
guard let messageData = messageContent.data(using: .utf8) else {
throw AppIntentErrors.AppIntentError.message("Failed to encode message content")
}

if messageData.count > 228 {
throw $messageContent.needsValueError("Message content exceeds 228 bytes.")
}

if(!BLEManager.shared.sendMessage(message: messageContent, toUserNum: 0, channel: Int32(channelNumber), isEmoji: false, replyID: 0)){
throw AppIntentErrors.AppIntentError.message("Failed to send message")
}

return .result()
}
}
56 changes: 56 additions & 0 deletions Meshtastic/AppIntents/NodePositionIntent.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//
// NodePositionIntent.swift
// Meshtastic
//
// Created by Benjamin Faershtein on 8/10/24.
//

import Foundation
import AppIntents
import CoreLocation
import CoreData

struct NodePositionIntent: AppIntent {

@Parameter(title: "Node Number")
var nodeNum: Int

static var title: LocalizedStringResource = "Get Node Position"
static var description: IntentDescription = "Fetch the latest position of a cetain node"


func perform() async throws -> some IntentResult & ReturnsValue<CLPlacemark> {
if (!BLEManager.shared.isConnected) {
throw AppIntentErrors.AppIntentError.notConnected
}
let fetchNodeInfoRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest(entityName: "NodeInfoEntity")
fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum))
do {
guard let fetchedNode = try PersistenceController.shared.container.viewContext.fetch(fetchNodeInfoRequest) as? [NodeInfoEntity], fetchedNode.count == 1 else {
throw $nodeNum.needsValueError("Could not find node")
}

let nodeInfo = fetchedNode[0]
if let latitude = nodeInfo.latestPosition?.coordinate.latitude,
let longitude = nodeInfo.latestPosition?.coordinate.longitude {
let nodeLocation = CLLocation(latitude: latitude, longitude: longitude)

// Reverse geocode the CLLocation to get a CLPlacemark
let geocoder = CLGeocoder()
let placemarks = try await geocoder.reverseGeocodeLocation(nodeLocation)

if let placemark = placemarks.first {
return .result(value: placemark)
} else {
throw AppIntentErrors.AppIntentError.message("Error Reverse Geocoding Location")
}
} else {
throw AppIntentErrors.AppIntentError.message("Node does not have positions")
}
} catch {
throw AppIntentErrors.AppIntentError.message("Fetch Failure")
}
}

}

Loading

0 comments on commit f352e26

Please sign in to comment.