mirror of
https://github.com/meshtastic/Meshtastic-Apple.git
synced 2026-04-20 22:13:56 +00:00
Merge pull request #195 from meshtastic/1.3.43_Protos
1.3.43 - Generate QR Codes
This commit is contained in:
commit
e7affc48d5
12 changed files with 428 additions and 261 deletions
|
|
@ -964,7 +964,7 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.3.41;
|
||||
MARKETING_VERSION = 1.3.43;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
|
|
@ -996,7 +996,7 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.3.41;
|
||||
MARKETING_VERSION = 1.3.43;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
|
|||
|
||||
@Published var connectedPeripheral: Peripheral!
|
||||
@Published var lastConnectionError: String
|
||||
@Published var minimumVersion = "1.3.41"
|
||||
@Published var minimumVersion = "1.3.43"
|
||||
@Published var connectedVersion: String
|
||||
@Published var invalidVersion = false
|
||||
@Published var preferredPeripheral = false
|
||||
|
|
@ -459,7 +459,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
|
|||
MeshLogger.log("ℹ️ Requesting Device Metadata for \(connectedPeripheral!.peripheral.name ?? "Unknown")")
|
||||
|
||||
var adminPacket = AdminMessage()
|
||||
adminPacket.getDeviceMetadataRequest = 0
|
||||
adminPacket.getDeviceMetadataRequest = true
|
||||
|
||||
var meshPacket: MeshPacket = MeshPacket()
|
||||
meshPacket.id = UInt32.random(in: UInt32(UInt8.max)..<UInt32.max)
|
||||
|
|
@ -616,6 +616,12 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
|
|||
}
|
||||
}
|
||||
}
|
||||
// Channels
|
||||
if decodedInfo.channel.isInitialized {
|
||||
nowKnown = true
|
||||
channelPacket(channel: decodedInfo.channel, fromNum: connectedPeripheral.num, meshLogging: meshLoggingEnabled, context: context!)
|
||||
}
|
||||
|
||||
// Config
|
||||
if decodedInfo.config.isInitialized && !invalidVersion {
|
||||
|
||||
|
|
@ -743,27 +749,25 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
|
|||
self.connectedPeripheral.subscribed = true
|
||||
peripherals.removeAll(where: { $0.peripheral.state == CBPeripheralState.disconnected })
|
||||
// Config conplete returns so we don't read the characteristic again
|
||||
|
||||
// Get all the channels
|
||||
var i: UInt32 = 1;
|
||||
let max: Int32 = self.connectedPeripheral.maxChannels
|
||||
|
||||
Timer.scheduledTimer(withTimeInterval: 0.4, repeats: true) { timer in
|
||||
|
||||
if i == (max + 1) {
|
||||
timer.invalidate() // invalidate the timer
|
||||
} else {
|
||||
|
||||
if self.connectedPeripheral != nil {
|
||||
|
||||
_ = self.getChannel(channelIndex: i, nodeNum: self.connectedPeripheral.num, wantResponse: true)
|
||||
i+=1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
// Get all the channels
|
||||
// var i: UInt32 = 1;
|
||||
// let max: Int32 = self.connectedPeripheral.maxChannels
|
||||
//
|
||||
// Timer.scheduledTimer(withTimeInterval: 0.4, repeats: true) { timer in
|
||||
//
|
||||
// if i == (max + 1) {
|
||||
// timer.invalidate() // invalidate the timer
|
||||
// } else {
|
||||
//
|
||||
// if self.connectedPeripheral != nil {
|
||||
//
|
||||
// _ = self.getChannel(channelIndex: i, nodeNum: self.connectedPeripheral.num, wantResponse: true)
|
||||
// i+=1;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
case FROMNUM_UUID :
|
||||
|
||||
|
|
@ -1447,7 +1451,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
|
|||
|
||||
meshPacket.decoded = dataMessage
|
||||
|
||||
let messageDescription = "Saved Canned Message Module Messages for \(toUser.longName ?? "Unknown")"
|
||||
let messageDescription = "💾 Saved Canned Message Module Messages for \(toUser.longName ?? "Unknown")"
|
||||
|
||||
if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) {
|
||||
|
||||
|
|
@ -1457,13 +1461,13 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
|
|||
return 0
|
||||
}
|
||||
|
||||
public func getChannel(channelIndex: UInt32, nodeNum: Int64, wantResponse: Bool) -> Bool {
|
||||
public func getChannel(channelIndex: UInt32, fromUser: UserEntity, toUser: UserEntity, wantResponse: Bool) -> Bool {
|
||||
|
||||
var adminPacket = AdminMessage()
|
||||
adminPacket.getChannelRequest = channelIndex
|
||||
|
||||
var meshPacket: MeshPacket = MeshPacket()
|
||||
meshPacket.to = UInt32(nodeNum)
|
||||
meshPacket.to = UInt32(toUser.num)
|
||||
meshPacket.from = 0 //UInt32(cnodeNum)
|
||||
meshPacket.id = UInt32.random(in: UInt32(UInt8.max)..<UInt32.max)
|
||||
meshPacket.priority = MeshPacket.Priority.reliable
|
||||
|
|
@ -1475,19 +1479,12 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
|
|||
dataMessage.wantResponse = true
|
||||
|
||||
meshPacket.decoded = dataMessage
|
||||
|
||||
var toRadio: ToRadio!
|
||||
toRadio = ToRadio()
|
||||
toRadio.packet = meshPacket
|
||||
|
||||
let binaryData: Data = try! toRadio.serializedData()
|
||||
|
||||
if connectedPeripheral!.peripheral.state == CBPeripheralState.connected {
|
||||
|
||||
if meshLoggingEnabled { MeshLogger.log("🛎️ Send Get Channel \(channelIndex) Request Admin Message for node: \(String(connectedPeripheral.num))") }
|
||||
|
||||
connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse)
|
||||
return true
|
||||
let messageDescription = "🛎️ Sent a Get Channel \(channelIndex) Request Admin Message for node: \(String(connectedPeripheral.num))"
|
||||
|
||||
if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) {
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
|
|
@ -1521,22 +1518,10 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
|
|||
|
||||
if connectedPeripheral!.peripheral.state == CBPeripheralState.connected {
|
||||
|
||||
do {
|
||||
|
||||
try context!.save()
|
||||
|
||||
if meshLoggingEnabled { MeshLogger.log("💾 Saved a Canned Messages Module Get Messages Request Admin Message for node: \(String(destNum))") }
|
||||
if meshLoggingEnabled { MeshLogger.log("✈️ Sent a Canned Messages Module Get Messages Request Admin Message for node: \(String(destNum))") }
|
||||
|
||||
connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse)
|
||||
return true
|
||||
|
||||
} catch {
|
||||
|
||||
context!.rollback()
|
||||
|
||||
let nsError = error as NSError
|
||||
print("💥 Error Inserting New Core Data MessageEntity: \(nsError)")
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
|
|
|
|||
|
|
@ -2,9 +2,6 @@ import Foundation
|
|||
import SwiftUI
|
||||
|
||||
extension Data {
|
||||
var hexDescription: String {
|
||||
return reduce("") {$0 + String(format: "%02x", $1)}
|
||||
}
|
||||
var macAddressString: String {
|
||||
let mac: String = reduce("") {$0 + String(format: "%02x:", $1)}
|
||||
return String(mac.dropLast())
|
||||
|
|
@ -35,34 +32,30 @@ extension Int {
|
|||
}
|
||||
|
||||
extension String {
|
||||
|
||||
/// Create `Data` from hexadecimal string representation
|
||||
///
|
||||
/// This creates a `Data` object from hex string. Note, if the string has any spaces or non-hex characters (e.g. starts with '<' and with a '>'), those are ignored and only hex characters are processed.
|
||||
///
|
||||
/// - returns: Data represented by this hexadecimal string.
|
||||
|
||||
var hexadecimal: Data? {
|
||||
var data = Data(capacity: count / 2)
|
||||
|
||||
let regex = try! NSRegularExpression(pattern: "[0-9a-f]{1,2}", options: .caseInsensitive)
|
||||
regex.enumerateMatches(in: self, range: NSRange(startIndex..., in: self)) { match, _, _ in
|
||||
let byteString = (self as NSString).substring(with: match!.range)
|
||||
let num = UInt8(byteString, radix: 16)!
|
||||
data.append(num)
|
||||
}
|
||||
|
||||
guard data.count > 0 else { return nil }
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
func base64urlToBase64() -> String {
|
||||
var base64 = self
|
||||
.replacingOccurrences(of: "-", with: "+")
|
||||
.replacingOccurrences(of: "_", with: "/")
|
||||
if base64.count % 4 != 0 {
|
||||
base64.append(String(repeating: "==", count: 4 - base64.count % 4))
|
||||
}
|
||||
return base64
|
||||
}
|
||||
|
||||
func base64ToBase64url() -> String {
|
||||
let base64url = self
|
||||
.replacingOccurrences(of: "+", with: "-")
|
||||
.replacingOccurrences(of: "/", with: "_")
|
||||
.replacingOccurrences(of: "=", with: "")
|
||||
return base64url
|
||||
}
|
||||
|
||||
func image(fontSize:CGFloat = 40, bgColor:UIColor = UIColor.clear, imageSize:CGSize? = nil) -> UIImage?
|
||||
{
|
||||
let font = UIFont.systemFont(ofSize: fontSize)
|
||||
let attributes = [NSAttributedString.Key.font: font]
|
||||
let imageSize = imageSize ?? self.size(withAttributes: attributes)
|
||||
|
||||
UIGraphicsBeginImageContextWithOptions(imageSize, false, 0)
|
||||
bgColor.set()
|
||||
let rect = CGRect(origin: .zero, size: imageSize)
|
||||
|
|
|
|||
|
|
@ -194,32 +194,31 @@ func localConfig (config: Config, meshlogging: Bool, context:NSManagedObjectCont
|
|||
let newLoRaConfig = LoRaConfigEntity(context: context)
|
||||
|
||||
newLoRaConfig.regionCode = Int32(config.lora.region.rawValue)
|
||||
newLoRaConfig.modemPreset = Int32(config.lora.modemPreset.rawValue)
|
||||
newLoRaConfig.hopLimit = Int32(config.lora.hopLimit)
|
||||
newLoRaConfig.txPower = Int32(config.lora.txPower)
|
||||
newLoRaConfig.txEnabled = config.lora.txEnabled
|
||||
newLoRaConfig.usePreset = config.lora.usePreset
|
||||
newLoRaConfig.modemPreset = Int32(config.lora.modemPreset.rawValue)
|
||||
newLoRaConfig.bandwidth = Int32(config.lora.bandwidth)
|
||||
newLoRaConfig.spreadFactor = Int32(config.lora.spreadFactor)
|
||||
newLoRaConfig.codingRate = Int32(config.lora.codingRate)
|
||||
newLoRaConfig.spreadFactor = Int32(config.lora.spreadFactor)
|
||||
newLoRaConfig.frequencyOffset = Int32(config.lora.frequencyOffset)
|
||||
|
||||
newLoRaConfig.frequencyOffset = config.lora.frequencyOffset
|
||||
newLoRaConfig.hopLimit = Int32(config.lora.hopLimit)
|
||||
newLoRaConfig.txPower = Int32(config.lora.txPower)
|
||||
newLoRaConfig.txEnabled = config.lora.txEnabled
|
||||
newLoRaConfig.channelNum = Int32(config.lora.channelNum)
|
||||
fetchedNode[0].loRaConfig = newLoRaConfig
|
||||
|
||||
} else {
|
||||
|
||||
fetchedNode[0].loRaConfig?.regionCode = Int32(config.lora.region.rawValue)
|
||||
fetchedNode[0].loRaConfig?.modemPreset = Int32(config.lora.modemPreset.rawValue)
|
||||
fetchedNode[0].loRaConfig?.hopLimit = Int32(config.lora.hopLimit)
|
||||
fetchedNode[0].loRaConfig?.txPower = Int32(config.lora.txPower)
|
||||
fetchedNode[0].loRaConfig?.txEnabled = config.lora.txEnabled
|
||||
fetchedNode[0].loRaConfig?.usePreset = config.lora.usePreset
|
||||
fetchedNode[0].loRaConfig?.modemPreset = Int32(config.lora.modemPreset.rawValue)
|
||||
fetchedNode[0].loRaConfig?.bandwidth = Int32(config.lora.bandwidth)
|
||||
fetchedNode[0].loRaConfig?.spreadFactor = Int32(config.lora.spreadFactor)
|
||||
fetchedNode[0].loRaConfig?.codingRate = Int32(config.lora.codingRate)
|
||||
fetchedNode[0].loRaConfig?.spreadFactor = Int32(config.lora.spreadFactor)
|
||||
fetchedNode[0].loRaConfig?.frequencyOffset = Int32(config.lora.frequencyOffset)
|
||||
fetchedNode[0].loRaConfig?.frequencyOffset = config.lora.frequencyOffset
|
||||
fetchedNode[0].loRaConfig?.hopLimit = Int32(config.lora.hopLimit)
|
||||
fetchedNode[0].loRaConfig?.txPower = Int32(config.lora.txPower)
|
||||
fetchedNode[0].loRaConfig?.txEnabled = config.lora.txEnabled
|
||||
fetchedNode[0].loRaConfig?.channelNum = Int32(config.lora.channelNum)
|
||||
}
|
||||
|
||||
do {
|
||||
|
|
@ -239,7 +238,6 @@ func localConfig (config: Config, meshlogging: Bool, context:NSManagedObjectCont
|
|||
print("💥 No Nodes found in local database matching node number \(nodeNum) unable to save Lora Config")
|
||||
}
|
||||
|
||||
|
||||
} catch {
|
||||
|
||||
let nsError = error as NSError
|
||||
|
|
@ -820,6 +818,61 @@ func myInfoPacket (myInfo: MyNodeInfo, meshLogging: Bool, context: NSManagedObje
|
|||
return nil
|
||||
}
|
||||
|
||||
func channelPacket (channel: Channel, fromNum: Int64, meshLogging: Bool, context: NSManagedObjectContext) -> NodeInfoEntity? {
|
||||
|
||||
if channel.isInitialized && channel.hasSettings {
|
||||
|
||||
let fetchedMyInfoRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "MyInfoEntity")
|
||||
fetchedMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %lld", fromNum)
|
||||
|
||||
do {
|
||||
|
||||
let fetchedMyInfo = try context.fetch(fetchedMyInfoRequest) as! [MyInfoEntity]
|
||||
|
||||
if fetchedMyInfo.count == 1 {
|
||||
|
||||
let newChannel = ChannelEntity(context: context)
|
||||
newChannel.index = Int32(channel.index)
|
||||
newChannel.uplinkEnabled = channel.settings.uplinkEnabled
|
||||
newChannel.downlinkEnabled = channel.settings.downlinkEnabled
|
||||
newChannel.name = channel.settings.name
|
||||
newChannel.role = Int32(channel.role.rawValue)
|
||||
newChannel.psk = channel.settings.psk
|
||||
|
||||
let mutableChannels = fetchedMyInfo[0].channels!.mutableCopy() as! NSMutableOrderedSet
|
||||
|
||||
mutableChannels.add(newChannel)
|
||||
fetchedMyInfo[0].channels = mutableChannels.copy() as? NSOrderedSet
|
||||
|
||||
} else {
|
||||
print("💥 Trying to save a channel to a MyInfo that does not exist: \(fromNum)")
|
||||
}
|
||||
|
||||
try context.save()
|
||||
|
||||
if meshLogging {
|
||||
|
||||
MeshLogger.log("💾 Updated MyInfo channel \(channel.index) from Channel App Packet For: \(fetchedMyInfo[0].myNodeNum)")
|
||||
|
||||
}
|
||||
|
||||
} catch {
|
||||
|
||||
context.rollback()
|
||||
|
||||
let nsError = error as NSError
|
||||
print("💥 Error Saving MyInfo Channel from ADMIN_APP \(nsError)")
|
||||
}
|
||||
}
|
||||
//}
|
||||
|
||||
|
||||
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func nodeInfoPacket (nodeInfo: NodeInfo, meshLogging: Bool, context: NSManagedObjectContext) -> NodeInfoEntity? {
|
||||
|
||||
let fetchNodeInfoRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "NodeInfoEntity")
|
||||
|
|
@ -1012,6 +1065,7 @@ func nodeInfoPacket (nodeInfo: NodeInfo, meshLogging: Bool, context: NSManagedOb
|
|||
return nil
|
||||
}
|
||||
|
||||
|
||||
func nodeInfoAppPacket (packet: MeshPacket, meshLogging: Bool, context: NSManagedObjectContext) {
|
||||
|
||||
let fetchNodeInfoAppRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "NodeInfoEntity")
|
||||
|
|
@ -1076,71 +1130,7 @@ func nodeInfoAppPacket (packet: MeshPacket, meshLogging: Bool, context: NSManage
|
|||
|
||||
func adminAppPacket (packet: MeshPacket, meshLogging: Bool, context: NSManagedObjectContext) {
|
||||
|
||||
if let channelMessage = try? Channel(serializedData: packet.decoded.payload) {
|
||||
|
||||
if channelMessage.hasSettings {
|
||||
|
||||
let fetchedMyInfoRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "MyInfoEntity")
|
||||
fetchedMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %lld", Int64(packet.from))
|
||||
|
||||
do {
|
||||
|
||||
let fetchedMyInfo = try context.fetch(fetchedMyInfoRequest) as! [MyInfoEntity]
|
||||
|
||||
if fetchedMyInfo.count == 1 {
|
||||
|
||||
// Update
|
||||
if fetchedMyInfo[0].channels?.count ?? 0 >= 1 {
|
||||
|
||||
let newChannel = ChannelEntity(context: context)
|
||||
newChannel.index = Int32(channelMessage.settings.channelNum)
|
||||
newChannel.uplinkEnabled = channelMessage.settings.uplinkEnabled
|
||||
newChannel.downlinkEnabled = channelMessage.settings.downlinkEnabled
|
||||
newChannel.name = channelMessage.settings.name
|
||||
newChannel.role = Int32(channelMessage.role.rawValue)
|
||||
|
||||
let mutableChannels = fetchedMyInfo[0].channels!.mutableCopy() as! NSMutableOrderedSet
|
||||
|
||||
mutableChannels.add(newChannel)
|
||||
fetchedMyInfo[0].channels = mutableChannels.copy() as? NSOrderedSet
|
||||
|
||||
} else {
|
||||
|
||||
let newChannel = ChannelEntity(context: context)
|
||||
newChannel.index = Int32(channelMessage.settings.channelNum)
|
||||
newChannel.uplinkEnabled = channelMessage.settings.uplinkEnabled
|
||||
newChannel.downlinkEnabled = channelMessage.settings.downlinkEnabled
|
||||
newChannel.name = channelMessage.settings.name
|
||||
newChannel.role = Int32(channelMessage.role.rawValue)
|
||||
|
||||
var newChannels = [ChannelEntity]()
|
||||
newChannels.append(newChannel)
|
||||
fetchedMyInfo[0].channels! = NSOrderedSet(array: newChannels)
|
||||
}
|
||||
|
||||
} else {
|
||||
print("💥 Trying to save a channel to a MyInfo that does not exist: \(packet.from)")
|
||||
}
|
||||
|
||||
try context.save()
|
||||
|
||||
if meshLogging {
|
||||
|
||||
MeshLogger.log("💾 Updated MyInfo channel \(channelMessage.settings.channelNum + 1) from Channel App Packet For: \(fetchedMyInfo[0].myNodeNum)")
|
||||
}
|
||||
|
||||
} catch {
|
||||
|
||||
context.rollback()
|
||||
|
||||
let nsError = error as NSError
|
||||
print("💥 Error Saving MyInfo Channel from ADMIN_APP \(nsError)")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
print(try! packet.decoded.jsonString())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@
|
|||
<attribute name="id" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="index" attributeType="Integer 32" minValueString="0" maxValueString="13" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="name" optional="YES" attributeType="String"/>
|
||||
<attribute name="psk" optional="YES" attributeType="String"/>
|
||||
<attribute name="psk" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="role" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="uplinkEnabled" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<relationship name="myInfoChannel" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MyInfoEntity" inverseName="channels" inverseEntity="MyInfoEntity"/>
|
||||
|
|
@ -60,7 +60,7 @@
|
|||
<attribute name="bandwidth" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="channelNum" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="codingRate" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="frequencyOffset" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="frequencyOffset" optional="YES" attributeType="Float" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="hopLimit" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="modemPreset" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="regionCode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
|
|
|
|||
|
|
@ -11,8 +11,9 @@ struct MeshtasticAppleApp: App {
|
|||
@ObservedObject private var bleManager: BLEManager = BLEManager.shared
|
||||
@ObservedObject private var userSettings: UserSettings = UserSettings()
|
||||
|
||||
@State var saveQR = false
|
||||
@State var channelUrl: URL?
|
||||
@State var saveChannels = false
|
||||
@State var incomingUrl: URL?
|
||||
@State var channelSettings: String?
|
||||
|
||||
@Environment(\.scenePhase) var scenePhase
|
||||
|
||||
|
|
@ -25,32 +26,41 @@ struct MeshtasticAppleApp: App {
|
|||
|
||||
.onContinueUserActivity(NSUserActivityTypeBrowsingWeb) { userActivity in
|
||||
|
||||
print("QR Code URL received from the Camera \(userActivity)")
|
||||
channelUrl = userActivity.webpageURL
|
||||
if channelUrl!.absoluteString.lowercased().contains("https://meshtastic.org/e/#") {
|
||||
saveQR = true
|
||||
print("URL received \(userActivity)")
|
||||
incomingUrl = userActivity.webpageURL
|
||||
|
||||
if incomingUrl!.absoluteString.lowercased().contains("meshtastic.org/e/#") {
|
||||
|
||||
if let components = incomingUrl?.absoluteString.components(separatedBy: "#") {
|
||||
channelSettings = components.last!
|
||||
}
|
||||
saveChannels = true
|
||||
print("User wants to open a Channel Settings URL: \(incomingUrl?.absoluteString ?? "No QR Code Link")")
|
||||
}
|
||||
if saveChannels {
|
||||
print("User wants to open Channel Settings URL: \(String(describing: incomingUrl!.relativeString))")
|
||||
}
|
||||
|
||||
print("User wants to open URL: \(String(describing: channelUrl?.relativeString))")
|
||||
|
||||
}
|
||||
.sheet(isPresented: $saveQR) {
|
||||
|
||||
SaveChannelQRCode(channelHash: channelUrl?.absoluteString ?? "Empty Channel URL")
|
||||
.sheet(isPresented: $saveChannels) {
|
||||
|
||||
SaveChannelQRCode(channelHash: channelSettings ?? "Empty Channel URL")
|
||||
.presentationDetents([.medium, .large])
|
||||
.presentationDragIndicator(.visible)
|
||||
}
|
||||
.onOpenURL(perform: { (url) in
|
||||
|
||||
print("Some sort of URL was received \(url)")
|
||||
channelUrl = url
|
||||
incomingUrl = url
|
||||
|
||||
|
||||
if url.absoluteString.lowercased().contains("https://meshtastic.org/e/#") {
|
||||
saveQR = true
|
||||
print("User wants to open a Channel Settings URL: \(channelUrl?.absoluteString ?? "No QR Code Link")")
|
||||
if url.absoluteString.lowercased().contains("meshtastic.org/e/#") {
|
||||
if let components = incomingUrl?.absoluteString.components(separatedBy: "#") {
|
||||
channelSettings = components.last!
|
||||
}
|
||||
saveChannels = true
|
||||
print("User wants to open a Channel Settings URL: \(incomingUrl?.absoluteString ?? "No QR Code Link")")
|
||||
} else {
|
||||
print("User wants to import a MBTILES offline map file: \(channelUrl?.absoluteString ?? "No Tiles link")")
|
||||
saveChannels = false
|
||||
print("User wants to import a MBTILES offline map file: \(incomingUrl?.absoluteString ?? "No Tiles link")")
|
||||
}
|
||||
|
||||
//we are expecting a .mbtiles map file that contains raster data
|
||||
|
|
@ -59,26 +69,28 @@ struct MeshtasticAppleApp: App {
|
|||
let documentsDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!
|
||||
let destination = documentsDirectory.appendingPathComponent("offline_map.mbtiles", isDirectory: false)
|
||||
|
||||
//do we need to delete an old one?
|
||||
if (fileManager.fileExists(atPath: destination.path)) {
|
||||
print("ℹ️ Found an old map file. Deleting it")
|
||||
try? fileManager.removeItem(atPath: destination.path)
|
||||
}
|
||||
|
||||
do {
|
||||
try fileManager.copyItem(at: url, to: destination)
|
||||
} catch {
|
||||
print("Copy MB Tile file failed. Error: \(error)")
|
||||
}
|
||||
|
||||
if (fileManager.fileExists(atPath: destination.path)) {
|
||||
print("ℹ️ Saved the map file")
|
||||
if !saveChannels {
|
||||
//do we need to delete an old one?
|
||||
if (fileManager.fileExists(atPath: destination.path)) {
|
||||
print("ℹ️ Found an old map file. Deleting it")
|
||||
try? fileManager.removeItem(atPath: destination.path)
|
||||
}
|
||||
|
||||
//need to tell the map view that it needs to update and try loading the new overlay
|
||||
UserDefaults.standard.set(Date().timeIntervalSince1970, forKey: "lastUpdatedLocalMapFile")
|
||||
do {
|
||||
try fileManager.copyItem(at: url, to: destination)
|
||||
} catch {
|
||||
print("Copy MB Tile file failed. Error: \(error)")
|
||||
}
|
||||
|
||||
} else {
|
||||
print("💥 Didn't save the map file")
|
||||
if (fileManager.fileExists(atPath: destination.path)) {
|
||||
print("ℹ️ Saved the map file")
|
||||
|
||||
//need to tell the map view that it needs to update and try loading the new overlay
|
||||
UserDefaults.standard.set(Date().timeIntervalSince1970, forKey: "lastUpdatedLocalMapFile")
|
||||
|
||||
} else {
|
||||
print("💥 Didn't save the map file")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -114,16 +114,6 @@ struct AdminMessage {
|
|||
set {payloadVariant = .getModuleConfigResponse(newValue)}
|
||||
}
|
||||
|
||||
///
|
||||
/// Send all channels in the response to this message
|
||||
var getAllChannelRequest: Bool {
|
||||
get {
|
||||
if case .getAllChannelRequest(let v)? = payloadVariant {return v}
|
||||
return false
|
||||
}
|
||||
set {payloadVariant = .getAllChannelRequest(newValue)}
|
||||
}
|
||||
|
||||
///
|
||||
/// Get the Canned Message Module messages in the response to this message.
|
||||
var getCannedMessageModuleMessagesRequest: Bool {
|
||||
|
|
@ -146,10 +136,10 @@ struct AdminMessage {
|
|||
|
||||
///
|
||||
/// Request the node to send device metadata (firmware, protobuf version, etc)
|
||||
var getDeviceMetadataRequest: UInt32 {
|
||||
var getDeviceMetadataRequest: Bool {
|
||||
get {
|
||||
if case .getDeviceMetadataRequest(let v)? = payloadVariant {return v}
|
||||
return 0
|
||||
return false
|
||||
}
|
||||
set {payloadVariant = .getDeviceMetadataRequest(newValue)}
|
||||
}
|
||||
|
|
@ -261,6 +251,17 @@ struct AdminMessage {
|
|||
set {payloadVariant = .confirmSetRadio(newValue)}
|
||||
}
|
||||
|
||||
///
|
||||
/// 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.
|
||||
var rebootOtaSeconds: Int32 {
|
||||
get {
|
||||
if case .rebootOtaSeconds(let v)? = payloadVariant {return v}
|
||||
return 0
|
||||
}
|
||||
set {payloadVariant = .rebootOtaSeconds(newValue)}
|
||||
}
|
||||
|
||||
///
|
||||
/// This message is only supported for the simulator porduino build.
|
||||
/// If received the simulator will exit successfully.
|
||||
|
|
@ -343,9 +344,6 @@ struct AdminMessage {
|
|||
/// Send the current Config in the response to this message.
|
||||
case getModuleConfigResponse(ModuleConfig)
|
||||
///
|
||||
/// Send all channels in the response to this message
|
||||
case getAllChannelRequest(Bool)
|
||||
///
|
||||
/// Get the Canned Message Module messages in the response to this message.
|
||||
case getCannedMessageModuleMessagesRequest(Bool)
|
||||
///
|
||||
|
|
@ -353,7 +351,7 @@ struct AdminMessage {
|
|||
case getCannedMessageModuleMessagesResponse(String)
|
||||
///
|
||||
/// Request the node to send device metadata (firmware, protobuf version, etc)
|
||||
case getDeviceMetadataRequest(UInt32)
|
||||
case getDeviceMetadataRequest(Bool)
|
||||
///
|
||||
/// Device metadata response
|
||||
case getDeviceMetadataResponse(DeviceMetadata)
|
||||
|
|
@ -392,6 +390,10 @@ struct AdminMessage {
|
|||
/// TODO: REPLACE
|
||||
case confirmSetRadio(Bool)
|
||||
///
|
||||
/// 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.
|
||||
case rebootOtaSeconds(Int32)
|
||||
///
|
||||
/// This message is only supported for the simulator porduino build.
|
||||
/// If received the simulator will exit successfully.
|
||||
case exitSimulator(Bool)
|
||||
|
|
@ -446,10 +448,6 @@ struct AdminMessage {
|
|||
guard case .getModuleConfigResponse(let l) = lhs, case .getModuleConfigResponse(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.getAllChannelRequest, .getAllChannelRequest): return {
|
||||
guard case .getAllChannelRequest(let l) = lhs, case .getAllChannelRequest(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.getCannedMessageModuleMessagesRequest, .getCannedMessageModuleMessagesRequest): return {
|
||||
guard case .getCannedMessageModuleMessagesRequest(let l) = lhs, case .getCannedMessageModuleMessagesRequest(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
|
|
@ -502,6 +500,10 @@ struct AdminMessage {
|
|||
guard case .confirmSetRadio(let l) = lhs, case .confirmSetRadio(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.rebootOtaSeconds, .rebootOtaSeconds): return {
|
||||
guard case .rebootOtaSeconds(let l) = lhs, case .rebootOtaSeconds(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.exitSimulator, .exitSimulator): return {
|
||||
guard case .exitSimulator(let l) = lhs, case .exitSimulator(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
|
|
@ -713,7 +715,6 @@ extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat
|
|||
6: .standard(proto: "get_config_response"),
|
||||
7: .standard(proto: "get_module_config_request"),
|
||||
8: .standard(proto: "get_module_config_response"),
|
||||
9: .standard(proto: "get_all_channel_request"),
|
||||
10: .standard(proto: "get_canned_message_module_messages_request"),
|
||||
11: .standard(proto: "get_canned_message_module_messages_response"),
|
||||
12: .standard(proto: "get_device_metadata_request"),
|
||||
|
|
@ -727,6 +728,7 @@ extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat
|
|||
65: .standard(proto: "confirm_set_module_config"),
|
||||
66: .standard(proto: "confirm_set_channel"),
|
||||
67: .standard(proto: "confirm_set_radio"),
|
||||
95: .standard(proto: "reboot_ota_seconds"),
|
||||
96: .standard(proto: "exit_simulator"),
|
||||
97: .standard(proto: "reboot_seconds"),
|
||||
98: .standard(proto: "shutdown_seconds"),
|
||||
|
|
@ -824,14 +826,6 @@ extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat
|
|||
self.payloadVariant = .getModuleConfigResponse(v)
|
||||
}
|
||||
}()
|
||||
case 9: try {
|
||||
var v: Bool?
|
||||
try decoder.decodeSingularBoolField(value: &v)
|
||||
if let v = v {
|
||||
if self.payloadVariant != nil {try decoder.handleConflictingOneOf()}
|
||||
self.payloadVariant = .getAllChannelRequest(v)
|
||||
}
|
||||
}()
|
||||
case 10: try {
|
||||
var v: Bool?
|
||||
try decoder.decodeSingularBoolField(value: &v)
|
||||
|
|
@ -849,8 +843,8 @@ extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat
|
|||
}
|
||||
}()
|
||||
case 12: try {
|
||||
var v: UInt32?
|
||||
try decoder.decodeSingularUInt32Field(value: &v)
|
||||
var v: Bool?
|
||||
try decoder.decodeSingularBoolField(value: &v)
|
||||
if let v = v {
|
||||
if self.payloadVariant != nil {try decoder.handleConflictingOneOf()}
|
||||
self.payloadVariant = .getDeviceMetadataRequest(v)
|
||||
|
|
@ -961,6 +955,14 @@ extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat
|
|||
self.payloadVariant = .confirmSetRadio(v)
|
||||
}
|
||||
}()
|
||||
case 95: try {
|
||||
var v: Int32?
|
||||
try decoder.decodeSingularInt32Field(value: &v)
|
||||
if let v = v {
|
||||
if self.payloadVariant != nil {try decoder.handleConflictingOneOf()}
|
||||
self.payloadVariant = .rebootOtaSeconds(v)
|
||||
}
|
||||
}()
|
||||
case 96: try {
|
||||
var v: Bool?
|
||||
try decoder.decodeSingularBoolField(value: &v)
|
||||
|
|
@ -1044,10 +1046,6 @@ extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat
|
|||
guard case .getModuleConfigResponse(let v)? = self.payloadVariant else { preconditionFailure() }
|
||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 8)
|
||||
}()
|
||||
case .getAllChannelRequest?: try {
|
||||
guard case .getAllChannelRequest(let v)? = self.payloadVariant else { preconditionFailure() }
|
||||
try visitor.visitSingularBoolField(value: v, fieldNumber: 9)
|
||||
}()
|
||||
case .getCannedMessageModuleMessagesRequest?: try {
|
||||
guard case .getCannedMessageModuleMessagesRequest(let v)? = self.payloadVariant else { preconditionFailure() }
|
||||
try visitor.visitSingularBoolField(value: v, fieldNumber: 10)
|
||||
|
|
@ -1058,7 +1056,7 @@ extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat
|
|||
}()
|
||||
case .getDeviceMetadataRequest?: try {
|
||||
guard case .getDeviceMetadataRequest(let v)? = self.payloadVariant else { preconditionFailure() }
|
||||
try visitor.visitSingularUInt32Field(value: v, fieldNumber: 12)
|
||||
try visitor.visitSingularBoolField(value: v, fieldNumber: 12)
|
||||
}()
|
||||
case .getDeviceMetadataResponse?: try {
|
||||
guard case .getDeviceMetadataResponse(let v)? = self.payloadVariant else { preconditionFailure() }
|
||||
|
|
@ -1100,6 +1098,10 @@ extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat
|
|||
guard case .confirmSetRadio(let v)? = self.payloadVariant else { preconditionFailure() }
|
||||
try visitor.visitSingularBoolField(value: v, fieldNumber: 67)
|
||||
}()
|
||||
case .rebootOtaSeconds?: try {
|
||||
guard case .rebootOtaSeconds(let v)? = self.payloadVariant else { preconditionFailure() }
|
||||
try visitor.visitSingularInt32Field(value: v, fieldNumber: 95)
|
||||
}()
|
||||
case .exitSimulator?: try {
|
||||
guard case .exitSimulator(let v)? = self.payloadVariant else { preconditionFailure() }
|
||||
try visitor.visitSingularBoolField(value: v, fieldNumber: 96)
|
||||
|
|
|
|||
|
|
@ -675,7 +675,7 @@ struct Config {
|
|||
|
||||
///
|
||||
/// The denominator of the coding rate.
|
||||
/// ie for 4/8, the value is 8. 5/8 the value is 5.
|
||||
/// ie for 4/5, the value is 5. 4/8 the value is 8.
|
||||
var codingRate: UInt32 = 0
|
||||
|
||||
///
|
||||
|
|
|
|||
|
|
@ -1850,6 +1850,16 @@ struct FromRadio {
|
|||
set {_uniqueStorage()._payloadVariant = .moduleConfig(newValue)}
|
||||
}
|
||||
|
||||
///
|
||||
/// One packet is sent for each channel
|
||||
var channel: Channel {
|
||||
get {
|
||||
if case .channel(let v)? = _storage._payloadVariant {return v}
|
||||
return Channel()
|
||||
}
|
||||
set {_uniqueStorage()._payloadVariant = .channel(newValue)}
|
||||
}
|
||||
|
||||
var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
///
|
||||
|
|
@ -1887,6 +1897,9 @@ struct FromRadio {
|
|||
///
|
||||
/// Include module config
|
||||
case moduleConfig(ModuleConfig)
|
||||
///
|
||||
/// One packet is sent for each channel
|
||||
case channel(Channel)
|
||||
|
||||
#if !swift(>=4.1)
|
||||
static func ==(lhs: FromRadio.OneOf_PayloadVariant, rhs: FromRadio.OneOf_PayloadVariant) -> Bool {
|
||||
|
|
@ -1926,6 +1939,10 @@ struct FromRadio {
|
|||
guard case .moduleConfig(let l) = lhs, case .moduleConfig(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.channel, .channel): return {
|
||||
guard case .channel(let l) = lhs, case .channel(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
|
|
@ -3244,6 +3261,7 @@ extension FromRadio: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementation
|
|||
7: .standard(proto: "config_complete_id"),
|
||||
8: .same(proto: "rebooted"),
|
||||
9: .same(proto: "moduleConfig"),
|
||||
10: .same(proto: "channel"),
|
||||
]
|
||||
|
||||
fileprivate class _StorageClass {
|
||||
|
|
@ -3370,6 +3388,19 @@ extension FromRadio: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementation
|
|||
_storage._payloadVariant = .moduleConfig(v)
|
||||
}
|
||||
}()
|
||||
case 10: try {
|
||||
var v: Channel?
|
||||
var hadOneofValue = false
|
||||
if let current = _storage._payloadVariant {
|
||||
hadOneofValue = true
|
||||
if case .channel(let m) = current {v = m}
|
||||
}
|
||||
try decoder.decodeSingularMessageField(value: &v)
|
||||
if let v = v {
|
||||
if hadOneofValue {try decoder.handleConflictingOneOf()}
|
||||
_storage._payloadVariant = .channel(v)
|
||||
}
|
||||
}()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
|
@ -3418,6 +3449,10 @@ extension FromRadio: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementation
|
|||
guard case .moduleConfig(let v)? = _storage._payloadVariant else { preconditionFailure() }
|
||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 9)
|
||||
}()
|
||||
case .channel?: try {
|
||||
guard case .channel(let v)? = _storage._payloadVariant else { preconditionFailure() }
|
||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 10)
|
||||
}()
|
||||
case nil: break
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,6 +60,14 @@ enum TelemetrySensorType: SwiftProtobuf.Enum {
|
|||
///
|
||||
/// High accuracy pressure
|
||||
case lps22 // = 8
|
||||
|
||||
///
|
||||
/// 3-Axis magnetic sensor
|
||||
case qmc6310 // = 9
|
||||
|
||||
///
|
||||
/// 6-Axis inertial measurement sensor
|
||||
case qmi8658 // = 10
|
||||
case UNRECOGNIZED(Int)
|
||||
|
||||
init() {
|
||||
|
|
@ -77,6 +85,8 @@ enum TelemetrySensorType: SwiftProtobuf.Enum {
|
|||
case 6: self = .bmp280
|
||||
case 7: self = .shtc3
|
||||
case 8: self = .lps22
|
||||
case 9: self = .qmc6310
|
||||
case 10: self = .qmi8658
|
||||
default: self = .UNRECOGNIZED(rawValue)
|
||||
}
|
||||
}
|
||||
|
|
@ -92,6 +102,8 @@ enum TelemetrySensorType: SwiftProtobuf.Enum {
|
|||
case .bmp280: return 6
|
||||
case .shtc3: return 7
|
||||
case .lps22: return 8
|
||||
case .qmc6310: return 9
|
||||
case .qmi8658: return 10
|
||||
case .UNRECOGNIZED(let i): return i
|
||||
}
|
||||
}
|
||||
|
|
@ -112,6 +124,8 @@ extension TelemetrySensorType: CaseIterable {
|
|||
.bmp280,
|
||||
.shtc3,
|
||||
.lps22,
|
||||
.qmc6310,
|
||||
.qmi8658,
|
||||
]
|
||||
}
|
||||
|
||||
|
|
@ -272,6 +286,8 @@ extension TelemetrySensorType: SwiftProtobuf._ProtoNameProviding {
|
|||
6: .same(proto: "BMP280"),
|
||||
7: .same(proto: "SHTC3"),
|
||||
8: .same(proto: "LPS22"),
|
||||
9: .same(proto: "QMC6310"),
|
||||
10: .same(proto: "QMI8658"),
|
||||
]
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import SwiftUI
|
|||
struct SaveChannelQRCode: View {
|
||||
|
||||
var channelHash: String
|
||||
|
||||
|
||||
var body: some View {
|
||||
|
||||
VStack {
|
||||
|
|
@ -17,22 +17,37 @@ struct SaveChannelQRCode: View {
|
|||
Text("Save Channel Settings?")
|
||||
.font(.title)
|
||||
|
||||
Text("The settings embedded in this QR code will replace the current settings on your radio.")
|
||||
Text("These settings will replace the current settings on your radio.")
|
||||
.foregroundColor(.gray)
|
||||
.font(.callout)
|
||||
.padding()
|
||||
|
||||
Text(channelHash)
|
||||
.font(.title2)
|
||||
.font(.caption2)
|
||||
.padding()
|
||||
|
||||
Text("This does not work yet.")
|
||||
Text("Error Message")
|
||||
.font(.callout)
|
||||
.foregroundColor(.red)
|
||||
.padding()
|
||||
|
||||
|
||||
Text("Swipe down to dismiss.")
|
||||
.padding()
|
||||
}
|
||||
.onChange(of: channelHash) { newSettings in
|
||||
|
||||
var decodedString = newSettings.base64urlToBase64()
|
||||
|
||||
if let decodedData = Data(base64Encoded: decodedString) {
|
||||
decodedString = String(data: decodedData, encoding: .utf8)!
|
||||
do {
|
||||
var channelSet: ChannelSet = try ChannelSet(serializedData: decodedData)
|
||||
print(channelSet)
|
||||
} catch {
|
||||
print("Invalid Meshtastic QR Code Link")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,24 +29,31 @@ struct QrCodeImage {
|
|||
return qrImage
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct ShareChannels: View {
|
||||
|
||||
@Environment(\.managedObjectContext) var context
|
||||
@EnvironmentObject var bleManager: BLEManager
|
||||
@EnvironmentObject var userSettings: UserSettings
|
||||
@State var initialLoad: Bool = true
|
||||
|
||||
@State var channelSet: ChannelSet = ChannelSet()
|
||||
@State var includeChannel0 = true
|
||||
@State var includeChannel1 = true
|
||||
@State var includeChannel2 = true
|
||||
@State var includeChannel1 = false
|
||||
@State var includeChannel2 = false
|
||||
@State var includeChannel3 = false
|
||||
@State var includeChannel4 = false
|
||||
@State var includeChannel5 = false
|
||||
@State var includeChannel6 = false
|
||||
@State var includeChannel7 = true
|
||||
@State var includeChannel7 = false
|
||||
|
||||
@State var isPresentingHelp = false
|
||||
|
||||
var node: NodeInfoEntity?
|
||||
|
||||
@State private var channelsUrl = "https://meshtastic.org/e/#test"
|
||||
@State private var channelsUrl = "https://www.meshtastic.org/e/#"
|
||||
|
||||
var qrCodeImage = QrCodeImage()
|
||||
|
||||
var body: some View {
|
||||
|
|
@ -69,14 +76,18 @@ struct ShareChannels: View {
|
|||
Text("Include")
|
||||
.font(.caption)
|
||||
.fontWeight(.bold)
|
||||
Text("Name")
|
||||
.padding(.trailing)
|
||||
Text("Channel Name")
|
||||
.font(.caption)
|
||||
.fontWeight(.bold)
|
||||
.padding(.trailing)
|
||||
Text("Encrypted")
|
||||
.font(.caption)
|
||||
.fontWeight(.bold)
|
||||
Spacer()
|
||||
}
|
||||
Divider()
|
||||
|
||||
ForEach(node!.myInfo!.channels?.array.sorted(by: { ($0 as! ChannelEntity).index < ($1 as! ChannelEntity).index }) as! [ChannelEntity], id: \.self) { (channel: ChannelEntity) in
|
||||
ForEach(node!.myInfo!.channels?.array as! [ChannelEntity], id: \.self) { (channel: ChannelEntity) in
|
||||
|
||||
GridRow {
|
||||
Spacer()
|
||||
|
|
@ -84,48 +95,57 @@ struct ShareChannels: View {
|
|||
Toggle("Channel 0 Included", isOn: $includeChannel0)
|
||||
.toggleStyle(.switch)
|
||||
.labelsHidden()
|
||||
.disabled(true)
|
||||
Text("Primary Channel")
|
||||
|
||||
.disabled(channel.role == 1)
|
||||
Text((channel.name!.isEmpty ? "primary" : channel.name) ?? "primary")
|
||||
} else if channel.index == 1 {
|
||||
Toggle("Channel 1 Included", isOn: $includeChannel1)
|
||||
.toggleStyle(.switch)
|
||||
.labelsHidden()
|
||||
Text("Public Channel")
|
||||
.disabled(channel.role == 0)
|
||||
Text((channel.name!.isEmpty ? "channel \(channel.index)" : channel.name) ?? "Channel \(channel.index)")
|
||||
} else if channel.index == 2 {
|
||||
Toggle("Channel 2 Included", isOn: $includeChannel2)
|
||||
.toggleStyle(.switch)
|
||||
.labelsHidden()
|
||||
.disabled(channel.role == 0)
|
||||
Text((channel.name!.isEmpty ? "channel \(channel.index)" : channel.name) ?? "Channel \(channel.index)")
|
||||
} else if channel.index == 3 {
|
||||
Toggle("Channel 3 Included", isOn: $includeChannel3)
|
||||
.toggleStyle(.switch)
|
||||
.labelsHidden()
|
||||
.disabled(channel.role == 0)
|
||||
Text((channel.name!.isEmpty ? "channel \(channel.index)" : channel.name) ?? "Channel \(channel.index)")
|
||||
} else if channel.index == 4 {
|
||||
Toggle("Channel 4 Included", isOn: $includeChannel4)
|
||||
.toggleStyle(.switch)
|
||||
.labelsHidden()
|
||||
.disabled(true)
|
||||
.disabled(channel.role == 0)
|
||||
Text((channel.name!.isEmpty ? "channel \(channel.index)" : channel.name) ?? "Channel \(channel.index)")
|
||||
} else if channel.index == 5 {
|
||||
Toggle("Channel 5 Included", isOn: $includeChannel5)
|
||||
.toggleStyle(.switch)
|
||||
.labelsHidden()
|
||||
.disabled(true)
|
||||
.disabled(channel.role == 0)
|
||||
Text((channel.name!.isEmpty ? "channel \(channel.index)" : channel.name) ?? "Channel \(channel.index)")
|
||||
} else if channel.index == 6 {
|
||||
Toggle("Channel 6 Included", isOn: $includeChannel6)
|
||||
.toggleStyle(.switch)
|
||||
.labelsHidden()
|
||||
.disabled(true)
|
||||
.disabled(channel.role == 0)
|
||||
Text((channel.name!.isEmpty ? "channel \(channel.index)" : channel.name) ?? "Channel \(channel.index)")
|
||||
} else if channel.index == 7 {
|
||||
Toggle("Channel 7 Included", isOn: $includeChannel7)
|
||||
.toggleStyle(.switch)
|
||||
.labelsHidden()
|
||||
Text("Admin Channel")
|
||||
.disabled(channel.role == 0)
|
||||
Text((channel.name!.isEmpty ? "channel \(channel.index)" : channel.name) ?? "Channel \(channel.index)")
|
||||
}
|
||||
if channel.index > 1 && channel.index < 4{
|
||||
Text("Private Chat - \(channel.index)")
|
||||
}
|
||||
if channel.index > 3 && channel.index < 7{
|
||||
Text("Channel - \(channel.index)")
|
||||
if channel.role > 0 {
|
||||
Image(systemName: "lock.fill")
|
||||
.foregroundColor(.green)
|
||||
} else {
|
||||
Image(systemName: "lock.slash")
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
|
|
@ -144,23 +164,62 @@ struct ShareChannels: View {
|
|||
preview: SharePreview("Meshtastic Node \(node?.user?.shortName ?? "????") has shared channels with you",
|
||||
image: Image(uiImage: qrImage))
|
||||
)
|
||||
|
||||
|
||||
Divider()
|
||||
.buttonStyle(.bordered)
|
||||
.buttonBorderShape(.capsule)
|
||||
.controlSize(.large)
|
||||
|
||||
Image(uiImage: qrImage)
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(
|
||||
minWidth: smallest * 0.7,
|
||||
maxWidth: smallest * 0.7,
|
||||
minHeight: smallest * 0.7,
|
||||
maxHeight: smallest * 0.7,
|
||||
minWidth: smallest * 0.65,
|
||||
maxWidth: smallest * 0.65,
|
||||
minHeight: smallest * 0.65,
|
||||
maxHeight: smallest * 0.65,
|
||||
alignment: .top
|
||||
)
|
||||
|
||||
Button {
|
||||
|
||||
isPresentingHelp = true
|
||||
|
||||
} label: {
|
||||
|
||||
Label("Help Me!", systemImage: "lifepreserver")
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
.buttonBorderShape(.capsule)
|
||||
.controlSize(.small)
|
||||
}
|
||||
}
|
||||
.sheet(isPresented: $isPresentingHelp) {
|
||||
|
||||
VStack {
|
||||
Text("Meshtastic Channels").font(.title)
|
||||
Text("A Meshtastic LoRa Mesh network can have up to 8 distinct channels.")
|
||||
.font(.headline)
|
||||
.padding(.bottom)
|
||||
Text("Primary Channel").font(.title2)
|
||||
Text("The first channel is the Primary channel and is where much of the mesh activity takes place. DM's are only available on the primary channel and it can not be disabled.")
|
||||
.font(.callout)
|
||||
.padding([.leading,.trailing,.bottom])
|
||||
Text("Admin Channel").font(.title2)
|
||||
Text("A channel with the name 'admin' is the Admin channel and can be used to remotely administer nodes on your mesh, text messages can not be sent over the admin channel.")
|
||||
.font(.callout)
|
||||
.padding([.leading,.trailing,.bottom])
|
||||
Text("Private Channels").font(.title2)
|
||||
Text("The other six channels can be used for private group converations. Each of these groups has its own encryption key.")
|
||||
.font(.callout)
|
||||
.padding([.leading,.trailing,.bottom])
|
||||
Text("From this view your primary channel and mesh settings are always shared in the generated QR code and you can toggle to include your admin channel and any private groups you want the person you are sharing with to have access to.")
|
||||
.font(.callout)
|
||||
.padding([.leading,.trailing,.bottom])
|
||||
Divider()
|
||||
}
|
||||
.padding()
|
||||
.presentationDetents([.large])
|
||||
.presentationDragIndicator(.automatic)
|
||||
}
|
||||
.navigationTitle("Generate QR Code")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.navigationBarItems(trailing:
|
||||
|
|
@ -171,10 +230,70 @@ struct ShareChannels: View {
|
|||
})
|
||||
.onAppear {
|
||||
|
||||
self.bleManager.context = context
|
||||
if self.initialLoad{
|
||||
|
||||
self.bleManager.context = context
|
||||
|
||||
self.initialLoad = false
|
||||
GenerateChannelSet()
|
||||
}
|
||||
}
|
||||
.onChange(of: includeChannel1) { includeCh1 in
|
||||
GenerateChannelSet()
|
||||
}
|
||||
.onChange(of: includeChannel2) { includeCh2 in
|
||||
GenerateChannelSet()
|
||||
}
|
||||
.onChange(of: includeChannel3) { includeCh3 in
|
||||
GenerateChannelSet()
|
||||
}
|
||||
.onChange(of: includeChannel4) { includeCh4 in
|
||||
GenerateChannelSet()
|
||||
}
|
||||
.onChange(of: includeChannel5) { includeCh5 in
|
||||
GenerateChannelSet()
|
||||
}
|
||||
.onChange(of: includeChannel6) { includeCh6 in
|
||||
GenerateChannelSet()
|
||||
}
|
||||
.onChange(of: includeChannel7) { includeCh7 in
|
||||
GenerateChannelSet()
|
||||
}
|
||||
}
|
||||
.navigationViewStyle(StackNavigationViewStyle())
|
||||
}
|
||||
}
|
||||
func GenerateChannelSet() {
|
||||
channelSet = ChannelSet()
|
||||
var loRaConfig = Config.LoRaConfig()
|
||||
loRaConfig.region = RegionCodes(rawValue: Int(node!.loRaConfig!.regionCode))!.protoEnumValue()
|
||||
loRaConfig.modemPreset = ModemPresets(rawValue: Int(node!.loRaConfig!.modemPreset))!.protoEnumValue()
|
||||
loRaConfig.bandwidth = UInt32(node!.loRaConfig!.bandwidth)
|
||||
loRaConfig.spreadFactor = UInt32(node!.loRaConfig!.spreadFactor)
|
||||
loRaConfig.codingRate = UInt32(node!.loRaConfig!.codingRate)
|
||||
loRaConfig.frequencyOffset = node!.loRaConfig!.frequencyOffset
|
||||
loRaConfig.hopLimit = UInt32(node!.loRaConfig!.hopLimit)
|
||||
loRaConfig.txEnabled = node!.loRaConfig!.txEnabled
|
||||
loRaConfig.txPower = node!.loRaConfig!.txPower
|
||||
loRaConfig.channelNum = UInt32(node!.loRaConfig!.channelNum)
|
||||
channelSet.loraConfig = loRaConfig
|
||||
for ch in node!.myInfo!.channels!.array as! [ChannelEntity] {
|
||||
if ch.role > 0 {
|
||||
|
||||
if ch.index == 0 && includeChannel0 || ch.index == 1 && includeChannel1 || ch.index == 2 && includeChannel2 || ch.index == 3 && includeChannel3 ||
|
||||
ch.index == 4 && includeChannel4 || ch.index == 5 && includeChannel5 || ch.index == 6 && includeChannel6 || ch.index == 7 && includeChannel7 {
|
||||
|
||||
var channelSettings = ChannelSettings()
|
||||
channelSettings.name = ch.name!
|
||||
channelSettings.psk = ch.psk ?? Data()
|
||||
channelSettings.id = UInt32(ch.id)
|
||||
channelSettings.uplinkEnabled = ch.uplinkEnabled
|
||||
channelSettings.downlinkEnabled = ch.downlinkEnabled
|
||||
channelSet.settings.append(channelSettings)
|
||||
}
|
||||
}
|
||||
}
|
||||
let settingsString = try! channelSet.serializedData().base64EncodedString()
|
||||
channelsUrl = ("https://www.meshtastic.org/e/#" + settingsString.base64ToBase64url())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue