Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

App intents #866

Merged
merged 8 commits into from
Aug 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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