mirror of
https://github.com/meshtastic/Meshtastic-Apple.git
synced 2026-04-20 22:13:56 +00:00
commit
306aa0b730
8 changed files with 209 additions and 22 deletions
|
|
@ -2779,6 +2779,9 @@
|
|||
},
|
||||
"Channel Name" : {
|
||||
|
||||
},
|
||||
"Channel number must be between 0 and 7." : {
|
||||
|
||||
},
|
||||
"Channel Role" : {
|
||||
|
||||
|
|
@ -5196,6 +5199,9 @@
|
|||
},
|
||||
"Description" : {
|
||||
|
||||
},
|
||||
"Description must be less than 100 bytes" : {
|
||||
|
||||
},
|
||||
"Detection" : {
|
||||
|
||||
|
|
@ -6808,6 +6814,9 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Emoji" : {
|
||||
|
||||
},
|
||||
"Empty" : {
|
||||
|
||||
|
|
@ -7176,6 +7185,9 @@
|
|||
},
|
||||
"Factory reset your device and app? " : {
|
||||
|
||||
},
|
||||
"Failed to encode message content." : {
|
||||
|
||||
},
|
||||
"Failed to get a valid position to exchange" : {
|
||||
|
||||
|
|
@ -10855,6 +10867,9 @@
|
|||
},
|
||||
"Loading Logs. . ." : {
|
||||
|
||||
},
|
||||
"Location" : {
|
||||
|
||||
},
|
||||
"Location: %@" : {
|
||||
|
||||
|
|
@ -14500,6 +14515,12 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Message" : {
|
||||
|
||||
},
|
||||
"Message content exceeds 228 bytes." : {
|
||||
|
||||
},
|
||||
"message.details" : {
|
||||
"localizations" : {
|
||||
|
|
@ -15103,6 +15124,9 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Must be a single emoji" : {
|
||||
|
||||
},
|
||||
"Nag timeout" : {
|
||||
|
||||
|
|
@ -15167,6 +15191,9 @@
|
|||
},
|
||||
"Name" : {
|
||||
|
||||
},
|
||||
"Name must be less than 30 bytes" : {
|
||||
|
||||
},
|
||||
"Nearby Topics" : {
|
||||
|
||||
|
|
@ -18995,6 +19022,12 @@
|
|||
},
|
||||
"Send" : {
|
||||
|
||||
},
|
||||
"Send a channel message" : {
|
||||
|
||||
},
|
||||
"Send a waypoint" : {
|
||||
|
||||
},
|
||||
"Send ASCII bell with alert message. Useful for triggering external notification on bell." : {
|
||||
|
||||
|
|
|
|||
|
|
@ -32,6 +32,8 @@
|
|||
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 */; };
|
||||
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 */; };
|
||||
|
|
@ -262,6 +264,8 @@
|
|||
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>"; };
|
||||
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>"; };
|
||||
|
|
@ -548,6 +552,15 @@
|
|||
path = MeshtasticTests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
BCB6137F2C6728E700485544 /* AppIntents */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
BCB613802C67290800485544 /* SendWaypointIntent.swift */,
|
||||
BCB613822C672A2600485544 /* MessageChannelIntent.swift */,
|
||||
);
|
||||
path = AppIntents;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C9483F6B2773016700998F6B /* MapKitMap */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
|
@ -814,6 +827,7 @@
|
|||
DDC2E15626CE248E0042C5E4 /* Meshtastic */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
BCB6137F2C6728E700485544 /* AppIntents */,
|
||||
DD1BD0EC2C603C5B008C0C70 /* Measurement */,
|
||||
25F5D5BC2C3F6D7B008036E3 /* Router */,
|
||||
DD7709392AA1ABA1007A8BF0 /* Tips */,
|
||||
|
|
@ -1339,6 +1353,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 */,
|
||||
|
|
@ -1375,6 +1390,7 @@
|
|||
DD3CC6C028E7A60700FA9159 /* MessagingEnums.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 */,
|
||||
|
|
|
|||
|
|
@ -89,6 +89,7 @@
|
|||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
askForAppToLaunch = "Yes"
|
||||
launchAutomaticallySubstyle = "2">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"originHash" : "af29d93455cb8f728684674f544d815b5becb17e049287cc1df8079a4855d0fc",
|
||||
"originHash" : "1571e0d09fede5d57a2c415019f30868d90fde5a53a863cc277593881c2dc4a5",
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "cocoamqtt",
|
||||
|
|
|
|||
43
Meshtastic/AppIntents/MessageChannelIntent.swift
Normal file
43
Meshtastic/AppIntents/MessageChannelIntent.swift
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
//
|
||||
// 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"
|
||||
|
||||
@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 {
|
||||
// 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 $messageContent.needsValueError("Failed to encode message content.")
|
||||
}
|
||||
|
||||
if messageData.count > 228 {
|
||||
throw $messageContent.needsValueError("Message content exceeds 228 bytes.")
|
||||
}
|
||||
if (BLEManager.shared.isConnected){
|
||||
BLEManager.shared.sendMessage(message: messageContent, toUserNum: 0, channel: Int32(channelNumber), isEmoji: false, replyID: 0)
|
||||
}
|
||||
return .result()
|
||||
}
|
||||
}
|
||||
85
Meshtastic/AppIntents/SendWaypointIntent.swift
Normal file
85
Meshtastic/AppIntents/SendWaypointIntent.swift
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
//
|
||||
// SendWaypointIntent.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Created by Benjamin Faershtein on 8/9/24.
|
||||
//
|
||||
|
||||
import CoreLocation
|
||||
import Foundation
|
||||
import AppIntents
|
||||
import MeshtasticProtobufs
|
||||
|
||||
struct SendWaypointIntent: AppIntent {
|
||||
|
||||
static var title = LocalizedStringResource("Send a waypoint")
|
||||
|
||||
@Parameter(title: "Name", default: "Dropped Pin")
|
||||
var nameParameter: String?
|
||||
|
||||
@Parameter(title: "Description", default: "")
|
||||
var descriptionParameter: String?
|
||||
|
||||
@Parameter(title: "Emoji", default: "📍")
|
||||
var emojiParameter: String?
|
||||
|
||||
@Parameter(title: "Location")
|
||||
var locationParameter: CLPlacemark
|
||||
|
||||
func perform() async throws -> some IntentResult {
|
||||
|
||||
// Provide default values if parameters are nil
|
||||
let name = nameParameter ?? "Dropped Pin"
|
||||
let description = descriptionParameter ?? ""
|
||||
let emoji = emojiParameter ?? "📍"
|
||||
|
||||
// Validate name length
|
||||
if name.utf8.count > 30 {
|
||||
throw $nameParameter.needsValueError("Name must be less than 30 bytes")
|
||||
}
|
||||
|
||||
// Validate description length
|
||||
if description.utf8.count > 100 {
|
||||
throw $descriptionParameter.needsValueError("Description must be less than 100 bytes")
|
||||
}
|
||||
|
||||
// Validate emoji
|
||||
guard isValidSingleEmoji(emoji) else {
|
||||
throw $emojiParameter.needsValueError("Must be a single emoji")
|
||||
}
|
||||
|
||||
var newWaypoint = Waypoint()
|
||||
|
||||
if let latitude = locationParameter.location?.coordinate.latitude {
|
||||
newWaypoint.latitudeI = Int32(latitude * 10_000_000)
|
||||
}
|
||||
|
||||
if let longitude = locationParameter.location?.coordinate.longitude {
|
||||
newWaypoint.longitudeI = Int32(longitude * 10_000_000)
|
||||
}
|
||||
|
||||
|
||||
newWaypoint.id = UInt32.random(in: UInt32(UInt8.max)..<UInt32.max)
|
||||
// Unicode scalar value for the icon emoji string
|
||||
let unicodeScalers = emoji.unicodeScalars
|
||||
// First element as an UInt32
|
||||
let unicode = unicodeScalers[unicodeScalers.startIndex].value
|
||||
newWaypoint.icon = unicode
|
||||
newWaypoint.name = name
|
||||
newWaypoint.description_p = description
|
||||
if (BLEManager.shared.isConnected){
|
||||
BLEManager.shared.sendWaypoint(waypoint: newWaypoint)
|
||||
}
|
||||
|
||||
return .result()
|
||||
}
|
||||
|
||||
private func isValidSingleEmoji(_ emoji: String) -> Bool {
|
||||
// This regex pattern is for matching a single emoji
|
||||
let emojiPattern = "^([\\p{So}\\p{Cn}])$"
|
||||
let regex = try? NSRegularExpression(pattern: emojiPattern, options: [])
|
||||
let matches = regex?.matches(in: emoji, options: [], range: NSRange(location: 0, length: emoji.utf16.count))
|
||||
|
||||
return matches?.count == 1
|
||||
}
|
||||
}
|
||||
|
|
@ -11,6 +11,7 @@ import OSLog
|
|||
// Meshtastic BLE Device Manager
|
||||
// ---------------------------------------------------------------------------------------
|
||||
class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate, ObservableObject {
|
||||
static var shared: BLEManager! // Singleton instance
|
||||
|
||||
let appState: AppState
|
||||
|
||||
|
|
@ -54,20 +55,30 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
|
||||
// MARK: init
|
||||
|
||||
init(
|
||||
appState: AppState,
|
||||
context: NSManagedObjectContext
|
||||
) {
|
||||
self.appState = appState
|
||||
self.context = context
|
||||
private override init() {
|
||||
// Default initialization should not be used
|
||||
fatalError("Use setup(appState:context:) to initialize the singleton")
|
||||
}
|
||||
|
||||
static func setup(appState: AppState, context: NSManagedObjectContext) {
|
||||
guard shared == nil else {
|
||||
print("BLEManager already initialized")
|
||||
return
|
||||
}
|
||||
shared = BLEManager(appState: appState, context: context)
|
||||
}
|
||||
|
||||
private init(appState: AppState, context: NSManagedObjectContext) {
|
||||
self.appState = appState
|
||||
self.context = context
|
||||
self.lastConnectionError = ""
|
||||
self.connectedVersion = "0.0.0"
|
||||
super.init()
|
||||
centralManager = CBCentralManager(delegate: self, queue: nil)
|
||||
mqttManager.delegate = self
|
||||
}
|
||||
|
||||
|
||||
self.lastConnectionError = ""
|
||||
self.connectedVersion = "0.0.0"
|
||||
super.init()
|
||||
centralManager = CBCentralManager(delegate: self, queue: nil)
|
||||
mqttManager.delegate = self
|
||||
// centralManager = CBCentralManager(delegate: self, queue: nil, options: [CBCentralManagerOptionRestoreIdentifierKey: restoreKey])
|
||||
}
|
||||
|
||||
// MARK: Scanning for BLE Devices
|
||||
// Scan for nearby BLE devices using the Meshtastic BLE service ID
|
||||
|
|
|
|||
|
|
@ -17,8 +17,8 @@ struct MeshtasticAppleApp: App {
|
|||
@ObservedObject
|
||||
var appState: AppState
|
||||
|
||||
@ObservedObject
|
||||
private var bleManager: BLEManager
|
||||
// @ObservedObject
|
||||
// private var bleManager: BLEManager
|
||||
|
||||
private let persistenceController: PersistenceController
|
||||
|
||||
|
|
@ -35,10 +35,8 @@ struct MeshtasticAppleApp: App {
|
|||
)
|
||||
self._appState = ObservedObject(wrappedValue: appState)
|
||||
|
||||
self.bleManager = BLEManager(
|
||||
appState: appState,
|
||||
context: persistenceController.container.viewContext
|
||||
)
|
||||
// Initialize the BLEManager singleton with the necessary dependencies
|
||||
BLEManager.setup(appState: appState, context: persistenceController.container.viewContext)
|
||||
self.persistenceController = persistenceController
|
||||
|
||||
// Wire up router
|
||||
|
|
@ -53,9 +51,9 @@ struct MeshtasticAppleApp: App {
|
|||
)
|
||||
.environment(\.managedObjectContext, persistenceController.container.viewContext)
|
||||
.environmentObject(appState)
|
||||
.environmentObject(bleManager)
|
||||
.environmentObject(BLEManager.shared)
|
||||
.sheet(isPresented: $saveChannels) {
|
||||
SaveChannelQRCode(channelSetLink: channelSettings ?? "Empty Channel URL", addChannels: addChannels, bleManager: bleManager)
|
||||
SaveChannelQRCode(channelSetLink: channelSettings ?? "Empty Channel URL", addChannels: addChannels, bleManager: BLEManager.shared)
|
||||
.presentationDetents([.large])
|
||||
.presentationDragIndicator(.visible)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue