Merge pull request #195 from meshtastic/1.3.43_Protos

1.3.43 - Generate QR Codes
This commit is contained in:
Garth Vander Houwen 2022-10-11 09:09:12 -07:00 committed by GitHub
commit e7affc48d5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 428 additions and 261 deletions

View file

@ -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;

View file

@ -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

View file

@ -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)

View file

@ -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())
}
}

View file

@ -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"/>

View file

@ -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")
}
}
})
}

View 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)

View file

@ -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
///

View file

@ -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
}
}

View file

@ -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"),
]
}

View file

@ -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")
}
}
}
}
}

View file

@ -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())
}
}