mirror of
https://github.com/meshtastic/Meshtastic-Apple.git
synced 2026-04-20 22:13:56 +00:00
Merge pull request #317 from meshtastic/2.0.15_Working_Changes
2.0.15 working changes
This commit is contained in:
commit
b6ef36f736
28 changed files with 825 additions and 264 deletions
|
|
@ -38,7 +38,6 @@
|
|||
DD4F23CD28779A3C001D37CB /* EnvironmentMetricsLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4F23CC28779A3C001D37CB /* EnvironmentMetricsLog.swift */; };
|
||||
DD5394FC276993AD00AD86B1 /* SwiftProtobuf in Frameworks */ = {isa = PBXBuildFile; productRef = DD5394FB276993AD00AD86B1 /* SwiftProtobuf */; };
|
||||
DD5394FE276BA0EF00AD86B1 /* PositionEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5394FD276BA0EF00AD86B1 /* PositionEntityExtension.swift */; };
|
||||
DD539502276DAA6A00AD86B1 /* MapLocation.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD539501276DAA6A00AD86B1 /* MapLocation.swift */; };
|
||||
DD58C5F22919AD3C00D5BEFB /* ChannelEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD58C5F12919AD3C00D5BEFB /* ChannelEntityExtension.swift */; };
|
||||
DD5D0A9C2931B9F200F7EA61 /* EthernetModes.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5D0A9B2931B9F200F7EA61 /* EthernetModes.swift */; };
|
||||
DD5E5202298EE33B00D21B61 /* admin.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5E51F0298EE33B00D21B61 /* admin.pb.swift */; };
|
||||
|
|
@ -50,7 +49,6 @@
|
|||
DD5E5208298EE33B00D21B61 /* rtttl.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5E51F6298EE33B00D21B61 /* rtttl.pb.swift */; };
|
||||
DD5E5209298EE33B00D21B61 /* module_config.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5E51F7298EE33B00D21B61 /* module_config.pb.swift */; };
|
||||
DD5E520A298EE33B00D21B61 /* channel.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5E51F8298EE33B00D21B61 /* channel.pb.swift */; };
|
||||
DD5E520B298EE33B00D21B61 /* device_metadata.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5E51F9298EE33B00D21B61 /* device_metadata.pb.swift */; };
|
||||
DD5E520C298EE33B00D21B61 /* portnums.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5E51FA298EE33B00D21B61 /* portnums.pb.swift */; };
|
||||
DD5E520D298EE33B00D21B61 /* storeforward.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5E51FB298EE33B00D21B61 /* storeforward.pb.swift */; };
|
||||
DD5E520E298EE33B00D21B61 /* mqtt.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5E51FC298EE33B00D21B61 /* mqtt.pb.swift */; };
|
||||
|
|
@ -60,7 +58,6 @@
|
|||
DD5E5212298EE33B00D21B61 /* apponly.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5E5200298EE33B00D21B61 /* apponly.pb.swift */; };
|
||||
DD5E5213298EE33B00D21B61 /* deviceonly.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5E5201298EE33B00D21B61 /* deviceonly.pb.swift */; };
|
||||
DD5E523A298EFA5300D21B61 /* TelemetryWeather.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5E5239298EFA5300D21B61 /* TelemetryWeather.swift */; };
|
||||
DD5E523C298F02D400D21B61 /* LicensedUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5E523B298F02D400D21B61 /* LicensedUser.swift */; };
|
||||
DD5E523F298F5A9E00D21B61 /* AirQualityIndexCompact.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5E523E298F5A9E00D21B61 /* AirQualityIndexCompact.swift */; };
|
||||
DD6193752862F6E600E59241 /* ExternalNotificationConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6193742862F6E600E59241 /* ExternalNotificationConfig.swift */; };
|
||||
DD6193772862F90F00E59241 /* CannedMessagesConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6193762862F90F00E59241 /* CannedMessagesConfig.swift */; };
|
||||
|
|
@ -170,7 +167,6 @@
|
|||
DD4A911D2708C65400501B7E /* AppSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettings.swift; sourceTree = "<group>"; };
|
||||
DD4F23CC28779A3C001D37CB /* EnvironmentMetricsLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnvironmentMetricsLog.swift; sourceTree = "<group>"; };
|
||||
DD5394FD276BA0EF00AD86B1 /* PositionEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PositionEntityExtension.swift; sourceTree = "<group>"; };
|
||||
DD539501276DAA6A00AD86B1 /* MapLocation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapLocation.swift; sourceTree = "<group>"; };
|
||||
DD58C5F12919AD3C00D5BEFB /* ChannelEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelEntityExtension.swift; sourceTree = "<group>"; };
|
||||
DD5D0A9A2931AD6B00F7EA61 /* MeshtasticDataModelV2.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV2.xcdatamodel; sourceTree = "<group>"; };
|
||||
DD5D0A9B2931B9F200F7EA61 /* EthernetModes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EthernetModes.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -184,7 +180,6 @@
|
|||
DD5E51F6298EE33B00D21B61 /* rtttl.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = rtttl.pb.swift; sourceTree = "<group>"; };
|
||||
DD5E51F7298EE33B00D21B61 /* module_config.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = module_config.pb.swift; sourceTree = "<group>"; };
|
||||
DD5E51F8298EE33B00D21B61 /* channel.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = channel.pb.swift; sourceTree = "<group>"; };
|
||||
DD5E51F9298EE33B00D21B61 /* device_metadata.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = device_metadata.pb.swift; sourceTree = "<group>"; };
|
||||
DD5E51FA298EE33B00D21B61 /* portnums.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = portnums.pb.swift; sourceTree = "<group>"; };
|
||||
DD5E51FB298EE33B00D21B61 /* storeforward.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = storeforward.pb.swift; sourceTree = "<group>"; };
|
||||
DD5E51FC298EE33B00D21B61 /* mqtt.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = mqtt.pb.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -194,7 +189,6 @@
|
|||
DD5E5200298EE33B00D21B61 /* apponly.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = apponly.pb.swift; sourceTree = "<group>"; };
|
||||
DD5E5201298EE33B00D21B61 /* deviceonly.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = deviceonly.pb.swift; sourceTree = "<group>"; };
|
||||
DD5E5239298EFA5300D21B61 /* TelemetryWeather.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TelemetryWeather.swift; sourceTree = "<group>"; };
|
||||
DD5E523B298F02D400D21B61 /* LicensedUser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LicensedUser.swift; sourceTree = "<group>"; };
|
||||
DD5E523E298F5A9E00D21B61 /* AirQualityIndexCompact.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirQualityIndexCompact.swift; sourceTree = "<group>"; };
|
||||
DD6193742862F6E600E59241 /* ExternalNotificationConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExternalNotificationConfig.swift; sourceTree = "<group>"; };
|
||||
DD6193762862F90F00E59241 /* CannedMessagesConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CannedMessagesConfig.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -239,6 +233,7 @@
|
|||
DDB6ABE128B13FB500384BA1 /* PositionConfigEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PositionConfigEnums.swift; sourceTree = "<group>"; };
|
||||
DDB6ABE328B13FFF00384BA1 /* DisplayEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplayEnums.swift; sourceTree = "<group>"; };
|
||||
DDB6ABE528B1406100384BA1 /* LoraConfigEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoraConfigEnums.swift; sourceTree = "<group>"; };
|
||||
DDBA45EC299ED78100DEEDDC /* MeshtasticDataModelV8.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV8.xcdatamodel; sourceTree = "<group>"; };
|
||||
DDC2E15426CE248E0042C5E4 /* Meshtastic.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Meshtastic.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
DDC2E15726CE248E0042C5E4 /* MeshtasticApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshtasticApp.swift; sourceTree = "<group>"; };
|
||||
DDC2E15B26CE248F0042C5E4 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = ../Assets.xcassets; sourceTree = "<group>"; };
|
||||
|
|
@ -343,7 +338,6 @@
|
|||
DD0F791A28713C8A00A6FDAD /* AdminMessageList.swift */,
|
||||
DD4A911D2708C65400501B7E /* AppSettings.swift */,
|
||||
DDA0B6B1294CDC55001356EC /* Channels.swift */,
|
||||
DD5E523B298F02D400D21B61 /* LicensedUser.swift */,
|
||||
DD8169FA271F1F3A00F4AB02 /* MeshLog.swift */,
|
||||
DD86D40B287F401000BAEB7A /* SaveChannelQRCode.swift */,
|
||||
DD3501882852FC3B000FC853 /* Settings.swift */,
|
||||
|
|
@ -366,7 +360,6 @@
|
|||
DD5E51F6298EE33B00D21B61 /* rtttl.pb.swift */,
|
||||
DD5E51F7298EE33B00D21B61 /* module_config.pb.swift */,
|
||||
DD5E51F8298EE33B00D21B61 /* channel.pb.swift */,
|
||||
DD5E51F9298EE33B00D21B61 /* device_metadata.pb.swift */,
|
||||
DD5E51FA298EE33B00D21B61 /* portnums.pb.swift */,
|
||||
DD5E51FB298EE33B00D21B61 /* storeforward.pb.swift */,
|
||||
DD5E51FC298EE33B00D21B61 /* mqtt.pb.swift */,
|
||||
|
|
@ -548,7 +541,6 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
DD23A50E26FD1B4400D9B90C /* PeripheralModel.swift */,
|
||||
DD539501276DAA6A00AD86B1 /* MapLocation.swift */,
|
||||
DD35018A2852FC79000FC853 /* UserSettings.swift */,
|
||||
);
|
||||
path = Model;
|
||||
|
|
@ -850,7 +842,6 @@
|
|||
DD8169FB271F1F3A00F4AB02 /* MeshLog.swift in Sources */,
|
||||
DD8ED9C52898D51F00B3B0AB /* NetworkConfig.swift in Sources */,
|
||||
DDC3B274283F411B00AC321C /* LastHeardText.swift in Sources */,
|
||||
DD5E520B298EE33B00D21B61 /* device_metadata.pb.swift in Sources */,
|
||||
DD2E65262767A01F00E45FC5 /* NodeDetail.swift in Sources */,
|
||||
DD1925B928CDA93900720036 /* SerialConfigEnums.swift in Sources */,
|
||||
DD86D4112881D16900BAEB7A /* WriteCsvFile.swift in Sources */,
|
||||
|
|
@ -858,7 +849,6 @@
|
|||
DDA6B2E928419CF2003E8C16 /* MeshPackets.swift in Sources */,
|
||||
DDCE4E2C2869F92900BE9F8F /* UserConfig.swift in Sources */,
|
||||
DD6193752862F6E600E59241 /* ExternalNotificationConfig.swift in Sources */,
|
||||
DD5E523C298F02D400D21B61 /* LicensedUser.swift in Sources */,
|
||||
DDB6ABE428B13FFF00384BA1 /* DisplayEnums.swift in Sources */,
|
||||
DD86D40A287F04F100BAEB7A /* InvalidVersion.swift in Sources */,
|
||||
DDD94A502845C8F5004A87A0 /* DateTimeText.swift in Sources */,
|
||||
|
|
@ -879,7 +869,6 @@
|
|||
DD5E5210298EE33B00D21B61 /* telemetry.pb.swift in Sources */,
|
||||
DD5E5205298EE33B00D21B61 /* mesh.pb.swift in Sources */,
|
||||
DD8169F9271F1A6100F4AB02 /* MeshLogger.swift in Sources */,
|
||||
DD539502276DAA6A00AD86B1 /* MapLocation.swift in Sources */,
|
||||
DD41582A28585C32009B0E59 /* RangeTestConfig.swift in Sources */,
|
||||
DD1925B728CDA5A400720036 /* CannedMessagesConfigEnums.swift in Sources */,
|
||||
DD5E5211298EE33B00D21B61 /* remote_hardware.pb.swift in Sources */,
|
||||
|
|
@ -1075,7 +1064,7 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.0.14;
|
||||
MARKETING_VERSION = 2.0.15;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
|
|
@ -1108,7 +1097,7 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.0.14;
|
||||
MARKETING_VERSION = 2.0.15;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
|
|
@ -1284,6 +1273,7 @@
|
|||
DD3CC6BA28E366DF00FA9159 /* Meshtastic.xcdatamodeld */ = {
|
||||
isa = XCVersionGroup;
|
||||
children = (
|
||||
DDBA45EC299ED78100DEEDDC /* MeshtasticDataModelV8.xcdatamodel */,
|
||||
DD5E51CC2986643400D21B61 /* MeshtasticDataModelV7.xcdatamodel */,
|
||||
DD964FC029724F6D007C176F /* MeshtasticDataModelV6.xcdatamodel */,
|
||||
DD457BC4295D5E35004BCE4D /* MeshtasticDataModelV5.xcdatamodel */,
|
||||
|
|
@ -1292,7 +1282,7 @@
|
|||
DD5D0A9A2931AD6B00F7EA61 /* MeshtasticDataModelV2.xcdatamodel */,
|
||||
DD3CC6BB28E366DF00FA9159 /* MeshtasticDataModel.xcdatamodel */,
|
||||
);
|
||||
currentVersion = DD5E51CC2986643400D21B61 /* MeshtasticDataModelV7.xcdatamodel */;
|
||||
currentVersion = DDBA45EC299ED78100DEEDDC /* MeshtasticDataModelV8.xcdatamodel */;
|
||||
name = Meshtastic.xcdatamodeld;
|
||||
path = Meshtastic/Meshtastic.xcdatamodeld;
|
||||
sourceTree = "<group>";
|
||||
|
|
|
|||
|
|
@ -35,6 +35,27 @@ enum KeyboardType: Int, CaseIterable, Identifiable {
|
|||
}
|
||||
}
|
||||
|
||||
enum CenteringMode: Int, CaseIterable, Identifiable {
|
||||
|
||||
case allAnnotations = 0
|
||||
case allPositions = 1
|
||||
case clientGps = 2
|
||||
|
||||
var id: Int { self.rawValue }
|
||||
var description: String {
|
||||
get {
|
||||
switch self {
|
||||
case .allAnnotations:
|
||||
return "All Annotations"// NSLocalizedString("default", comment: "Default Keyboard")
|
||||
case .allPositions:
|
||||
return "All Node Postions"// NSLocalizedString("ascii.capable", comment: "ASCII Capable Keyboard")
|
||||
case .clientGps:
|
||||
return "Client GPS"//NSLocalizedString("email.address", comment: "Email Address Keyboard")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum MeshMapType: String, CaseIterable, Identifiable {
|
||||
|
||||
case standard = "standard"
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import MapKit
|
|||
// Meshtastic BLE Device Manager
|
||||
// ---------------------------------------------------------------------------------------
|
||||
class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
||||
|
||||
|
||||
private static var documentsFolder: URL {
|
||||
do {
|
||||
return try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
|
||||
|
|
@ -16,11 +16,12 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
fatalError("Can't find documents directory.")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var context: NSManagedObjectContext?
|
||||
var userSettings: UserSettings?
|
||||
private var centralManager: CBCentralManager!
|
||||
|
||||
private let restoreKey = "Meshtastic.BLE.Manager"
|
||||
|
||||
@Published var peripherals: [Peripheral] = []
|
||||
@Published var connectedPeripheral: Peripheral!
|
||||
@Published var lastConnectionError: String
|
||||
|
|
@ -35,35 +36,36 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
public var isConnected: Bool = false
|
||||
public var isSubscribed: Bool = false
|
||||
private var configNonce: UInt32 = 1
|
||||
|
||||
|
||||
var timeoutTimer: Timer?
|
||||
var timeoutTimerCount = 0
|
||||
var timeoutTimerRuns = 0
|
||||
var positionTimer: Timer?
|
||||
let emptyNodeNum: UInt32 = 4294967295
|
||||
|
||||
|
||||
/* Meshtastic Service Details */
|
||||
var TORADIO_characteristic: CBCharacteristic!
|
||||
var FROMRADIO_characteristic: CBCharacteristic!
|
||||
var FROMNUM_characteristic: CBCharacteristic!
|
||||
|
||||
|
||||
let meshtasticServiceCBUUID = CBUUID(string: "0x6BA1B218-15A8-461F-9FA8-5DCAE273EAFD")
|
||||
let TORADIO_UUID = CBUUID(string: "0xF75C76D2-129E-4DAD-A1DD-7866124401E7")
|
||||
let FROMRADIO_UUID = CBUUID(string: "0x2C55E69E-4993-11ED-B878-0242AC120002")
|
||||
let EOL_FROMRADIO_UUID = CBUUID(string: "0x8BA2BCC2-EE02-4A55-A531-C525C5E454D5")
|
||||
let FROMNUM_UUID = CBUUID(string: "0xED9DA18C-A800-4F66-A670-AA7547E34453")
|
||||
|
||||
|
||||
//private var meshLoggingEnabled: Bool = true
|
||||
let meshLog = documentsFolder.appendingPathComponent("meshlog.txt")
|
||||
|
||||
|
||||
// MARK: init BLEManager
|
||||
override init() {
|
||||
self.lastConnectionError = ""
|
||||
self.connectedVersion = "0.0.0"
|
||||
super.init()
|
||||
centralManager = CBCentralManager(delegate: self, queue: nil)
|
||||
//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
|
||||
func startScanning() {
|
||||
|
|
@ -130,6 +132,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
print("ℹ️ BLE Disconnecting from: \(connectedPeripheral.name) to connect to \(peripheral.name ?? "Unknown")")
|
||||
disconnectPeripheral()
|
||||
}
|
||||
|
||||
centralManager?.connect(peripheral)
|
||||
// Invalidate any existing timer
|
||||
if timeoutTimer != nil {
|
||||
|
|
@ -495,6 +498,12 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
}
|
||||
}
|
||||
}
|
||||
// Device Metadata
|
||||
if decodedInfo.metadata.firmwareVersion.count > 0 && !invalidVersion {
|
||||
nowKnown = true
|
||||
deviceMetadataPacket(metadata: decodedInfo.metadata, fromNum: connectedPeripheral.num, context: context!)
|
||||
}
|
||||
|
||||
// Log any other unknownApp calls
|
||||
if !nowKnown { MeshLogger.log("🕸️ MESH PACKET received for Unknown App UNHANDLED \(try! decodedInfo.packet.jsonString())") }
|
||||
|
||||
|
|
@ -674,7 +683,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
newMessage.messagePayloadMarkdown = generateMessageMarkdown(message: message)
|
||||
|
||||
let dataType = PortNum.textMessageApp
|
||||
let payloadData: Data = message.data(using: String.Encoding.utf8)!
|
||||
var messageQuotesReplaced = message.replacingOccurrences(of: "’", with: "'")
|
||||
messageQuotesReplaced = message.replacingOccurrences(of: "”", with: "\"")
|
||||
let payloadData: Data = messageQuotesReplaced.data(using: String.Encoding.utf8)!
|
||||
|
||||
var dataMessage = DataMessage()
|
||||
dataMessage.payload = payloadData
|
||||
|
|
@ -1999,4 +2010,39 @@ extension BLEManager: CBCentralManagerDelegate {
|
|||
let visibleDuration = Calendar.current.date(byAdding: .second, value: -5, to: today)!
|
||||
self.peripherals.removeAll(where: { $0.lastUpdate < visibleDuration})
|
||||
}
|
||||
|
||||
// func centralManager(_ central: CBCentralManager, willRestoreState dict: [String : Any]) {
|
||||
//
|
||||
// guard let peripherals = dict[CBCentralManagerRestoredStatePeripheralsKey] as? [CBPeripheral] else {
|
||||
// return
|
||||
// }
|
||||
// print(peripherals)
|
||||
// if peripherals.count > 0 {
|
||||
// //connectedPeripheral.peripheral = peripherals[0]
|
||||
// // 5
|
||||
// //connectedPeripheral.peripheral.delegate = self
|
||||
//
|
||||
// for peripheral in peripherals {
|
||||
//
|
||||
// switch peripheral.state {
|
||||
// case .connecting: // I've only seen this happen when
|
||||
// // re-launching attached to Xcode.
|
||||
// print("Xcode Restore")
|
||||
//
|
||||
// case .connected: // Store for connection / requesting
|
||||
// // notifications when BT starts.
|
||||
// print("Actual restore")
|
||||
// //centralManager.connect(peripheral)
|
||||
// default: break
|
||||
// }
|
||||
//
|
||||
//
|
||||
//
|
||||
// // connectedPeripheral.peripheral
|
||||
// //connectedPeripheral.peripheral = peripheral
|
||||
// //connectedPeripheral.peripheral.delegate = self
|
||||
// }
|
||||
// }
|
||||
// print("willRestoreState Hit!")
|
||||
// }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,6 +72,23 @@ extension Int {
|
|||
}
|
||||
}
|
||||
|
||||
extension UIImage {
|
||||
func rotate(radians: Float) -> UIImage? {
|
||||
var newSize = CGRect(origin: CGPoint.zero, size: self.size).applying(CGAffineTransform(rotationAngle: CGFloat(radians))).size
|
||||
newSize.width = floor(newSize.width)
|
||||
newSize.height = floor(newSize.height)
|
||||
UIGraphicsBeginImageContextWithOptions(newSize, false, self.scale)
|
||||
let context = UIGraphicsGetCurrentContext()!
|
||||
context.translateBy(x: newSize.width/2, y: newSize.height/2)
|
||||
context.rotate(by: CGFloat(radians))
|
||||
self.draw(in: CGRect(x: -self.size.width/2, y: -self.size.height/2, width: self.size.width, height: self.size.height))
|
||||
let newImage = UIGraphicsGetImageFromCurrentImageContext()
|
||||
UIGraphicsEndImageContext()
|
||||
|
||||
return newImage
|
||||
}
|
||||
}
|
||||
|
||||
extension String {
|
||||
|
||||
func base64urlToBase64() -> String {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,6 @@
|
|||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>_XCCurrentVersionName</key>
|
||||
<string>MeshtasticDataModelV7.xcdatamodel</string>
|
||||
<string>MeshtasticDataModelV8.xcdatamodel</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="21513" systemVersion="22D49" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="21513" systemVersion="22D68" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
||||
<entity name="BluetoothConfigEntity" representedClassName="BluetoothConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="fixedPin" optional="YES" attributeType="Integer 32" defaultValueString="123456" usesScalarValueType="YES"/>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,301 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="21513" systemVersion="22D68" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
||||
<entity name="BluetoothConfigEntity" representedClassName="BluetoothConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="fixedPin" optional="YES" attributeType="Integer 32" defaultValueString="123456" usesScalarValueType="YES"/>
|
||||
<attribute name="mode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="bluetoothConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="bluetoothConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="CannedMessageConfigEntity" representedClassName="CannedMessageConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerEventCcw" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerEventCw" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerEventPress" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerPinA" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerPinB" optional="YES" attributeType="Integer 32" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerPinPress" optional="YES" attributeType="Integer 32" usesScalarValueType="YES"/>
|
||||
<attribute name="messages" optional="YES" attributeType="String" minValueString="0" maxValueString="198"/>
|
||||
<attribute name="rotary1Enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="sendBell" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="updown1Enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<relationship name="cannedMessagesConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="cannedMessageConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="ChannelEntity" representedClassName="ChannelEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="downlinkEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<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="mute" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="name" 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"/>
|
||||
<fetchedProperty name="allPrivateMessages" optional="YES">
|
||||
<fetchRequest name="fetchedPropertyFetchRequest" entity="MessageEntity" predicateString="channel == $FETCH_SOURCE.index && toUser == nil AND isEmoji == false"/>
|
||||
</fetchedProperty>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="index"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="DeviceConfigEntity" representedClassName="DeviceConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="buttonGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="buzzerGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="debugLogEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="role" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="serialEnabled" optional="YES" attributeType="Boolean" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="deviceConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="deviceConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="DeviceMetadataEntity" representedClassName="DeviceMetadataEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="canShutdown" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="deviceStateVersion" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="firmwareVersion" optional="YES" attributeType="String"/>
|
||||
<attribute name="hasBluetooth" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="hasEthernet" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="hasWifi" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="hwModel" optional="YES" attributeType="String"/>
|
||||
<attribute name="positionFlags" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="role" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="metadataNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="metadata" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="DisplayConfigEntity" representedClassName="DisplayConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="compassNorthTop" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="displayMode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="flipScreen" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="gpsFormat" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="oledType" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="screenCarouselInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="screenOnSeconds" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="displayConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="displayConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="ExternalNotificationConfigEntity" representedClassName="ExternalNotificationConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="active" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="alertBell" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="alertBellBuzzer" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="alertBellVibra" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="alertMessage" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="alertMessageBuzzer" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="alertMessageVibra" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="nagTimeout" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="output" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="outputBuzzer" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="outputMilliseconds" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="outputVibra" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="usePWM" optional="YES" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<relationship name="externalNotificationConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="externalNotificationConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="LoRaConfigEntity" representedClassName="LoRaConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<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="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="overrideDutyCycle" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="overrideFrequency" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="regionCode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="spreadFactor" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="txEnabled" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<attribute name="txPower" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="usePreset" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<relationship name="loRaConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="loRaConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="MessageEntity" representedClassName="MessageEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="ackError" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="ackSNR" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="ackTimestamp" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="admin" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="adminDescription" optional="YES" attributeType="String"/>
|
||||
<attribute name="channel" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="isEmoji" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="messageId" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="messagePayload" optional="YES" attributeType="String" defaultValueString=""/>
|
||||
<attribute name="messagePayloadMarkdown" optional="YES" attributeType="String"/>
|
||||
<attribute name="messageTimestamp" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="realACK" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="receivedACK" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="replyID" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<relationship name="fromUser" optional="YES" maxCount="1" deletionRule="Nullify" ordered="YES" destinationEntity="UserEntity" inverseName="sentMessages" inverseEntity="UserEntity"/>
|
||||
<relationship name="toUser" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="UserEntity" inverseName="receivedMessages" inverseEntity="UserEntity"/>
|
||||
<fetchedProperty name="tapbacks" optional="YES">
|
||||
<fetchRequest name="fetchedPropertyFetchRequest" entity="MessageEntity" predicateString="replyID == $FETCH_SOURCE.messageId AND isEmoji == true"/>
|
||||
</fetchedProperty>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="messageId"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="MQTTConfigEntity" representedClassName="MQTTConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="address" optional="YES" attributeType="String" maxValueString="30"/>
|
||||
<attribute name="enabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="encryptionEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="jsonEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="password" optional="YES" attributeType="String" maxValueString="30"/>
|
||||
<attribute name="username" optional="YES" attributeType="String" maxValueString="30"/>
|
||||
<relationship name="mqttConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="mqttConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="MyInfoEntity" representedClassName="MyInfoEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="adminIndex" optional="YES" attributeType="Integer 32" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="bitrate" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="bleName" optional="YES" attributeType="String"/>
|
||||
<attribute name="errorCount" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="firmwareVersion" attributeType="String"/>
|
||||
<attribute name="hasGps" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="hasWifi" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="maxChannels" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="messageTimeoutMsec" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="minAppVersion" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="myNodeNum" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="peripheralId" optional="YES" attributeType="String"/>
|
||||
<attribute name="rebootCount" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="channels" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="ChannelEntity" inverseName="myInfoChannel" inverseEntity="ChannelEntity"/>
|
||||
<relationship name="myInfoNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="myInfo" inverseEntity="NodeInfoEntity"/>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="myNodeNum"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="NetworkConfigEntity" representedClassName="NetworkConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="dns" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="ethEnabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="gateway" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="ip" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="ntpServer" optional="YES" attributeType="String"/>
|
||||
<attribute name="subnet" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="wifiEnabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="wifiMode" optional="YES" attributeType="Integer 32" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="wifiPsk" optional="YES" attributeType="String" minValueString="0" maxValueString="60"/>
|
||||
<attribute name="wifiSsid" optional="YES" attributeType="String" minValueString="0" maxValueString="30"/>
|
||||
<relationship name="networkConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="networkConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="NodeInfoEntity" representedClassName="NodeInfoEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="bleName" optional="YES" attributeType="String"/>
|
||||
<attribute name="channel" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="id" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="lastHeard" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="num" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="peripheralId" optional="YES" attributeType="String"/>
|
||||
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<relationship name="bluetoothConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="BluetoothConfigEntity" inverseName="bluetoothConfigNode" inverseEntity="BluetoothConfigEntity"/>
|
||||
<relationship name="cannedMessageConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="CannedMessageConfigEntity" inverseName="cannedMessagesConfigNode" inverseEntity="CannedMessageConfigEntity"/>
|
||||
<relationship name="deviceConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="DeviceConfigEntity" inverseName="deviceConfigNode" inverseEntity="DeviceConfigEntity"/>
|
||||
<relationship name="displayConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="DisplayConfigEntity" inverseName="displayConfigNode" inverseEntity="DisplayConfigEntity"/>
|
||||
<relationship name="externalNotificationConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="ExternalNotificationConfigEntity" inverseName="externalNotificationConfigNode" inverseEntity="ExternalNotificationConfigEntity"/>
|
||||
<relationship name="loRaConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="LoRaConfigEntity" inverseName="loRaConfigNode" inverseEntity="LoRaConfigEntity"/>
|
||||
<relationship name="metadata" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="DeviceMetadataEntity" inverseName="metadataNode" inverseEntity="DeviceMetadataEntity"/>
|
||||
<relationship name="mqttConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MQTTConfigEntity" inverseName="mqttConfigNode" inverseEntity="MQTTConfigEntity"/>
|
||||
<relationship name="myInfo" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MyInfoEntity" inverseName="myInfoNode" inverseEntity="MyInfoEntity"/>
|
||||
<relationship name="networkConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NetworkConfigEntity" inverseName="networkConfigNode" inverseEntity="NetworkConfigEntity"/>
|
||||
<relationship name="positionConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="PositionConfigEntity" inverseName="positionConfigNode" inverseEntity="PositionConfigEntity"/>
|
||||
<relationship name="positions" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="PositionEntity" inverseName="nodePosition" inverseEntity="PositionEntity"/>
|
||||
<relationship name="rangeTestConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="RangeTestConfigEntity" inverseName="rangeTestConfigNode" inverseEntity="RangeTestConfigEntity"/>
|
||||
<relationship name="serialConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="SerialConfigEntity" inverseName="serialConfigNode" inverseEntity="SerialConfigEntity"/>
|
||||
<relationship name="telemetries" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="TelemetryEntity" inverseName="nodeTelemetry" inverseEntity="TelemetryEntity"/>
|
||||
<relationship name="telemetryConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="TelemetryConfigEntity" inverseName="telemetryConfigNode" inverseEntity="TelemetryConfigEntity"/>
|
||||
<relationship name="user" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="UserEntity" inverseName="userNode" inverseEntity="UserEntity"/>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="num"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="PositionConfigEntity" representedClassName="PositionConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="deviceGpsEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="fixedPosition" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="gpsAttemptTime" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="gpsUpdateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="positionBroadcastSeconds" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="positionFlags" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="rxGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="smartPositionEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="txGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="positionConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="positionConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="PositionEntity" representedClassName="PositionEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="altitude" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="heading" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="latest" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="latitudeI" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="longitudeI" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="satsInView" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="seqNo" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="speed" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="time" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<relationship name="nodePosition" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="positions" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="RangeTestConfigEntity" representedClassName="RangeTestConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="save" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="sender" optional="YES" attributeType="Integer 32" usesScalarValueType="YES"/>
|
||||
<relationship name="rangeTestConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="rangeTestConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="SerialConfigEntity" representedClassName="SerialConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="baudRate" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="echo" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="mode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="rxd" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="timeout" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="txd" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="serialConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="serialConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="TelemetryConfigEntity" representedClassName="TelemetryConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="deviceUpdateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="environmentDisplayFahrenheit" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="environmentMeasurementEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="environmentScreenEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="environmentUpdateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="telemetryConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="telemetryConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="TelemetryEntity" representedClassName="TelemetryEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="airUtilTx" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="barometricPressure" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="batteryLevel" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="channelUtilization" optional="YES" attributeType="Float" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="current" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="gasResistance" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="metricsType" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="relativeHumidity" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="temperature" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="time" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="voltage" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<relationship name="nodeTelemetry" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="telemetries" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="UserEntity" representedClassName="UserEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="hwModel" attributeType="String"/>
|
||||
<attribute name="isLicensed" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="longName" attributeType="String"/>
|
||||
<attribute name="macaddr" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="mute" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="num" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="shortName" attributeType="String"/>
|
||||
<attribute name="userId" attributeType="String"/>
|
||||
<relationship name="receivedMessages" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="MessageEntity" inverseName="toUser" inverseEntity="MessageEntity"/>
|
||||
<relationship name="sentMessages" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="MessageEntity" inverseName="fromUser" inverseEntity="MessageEntity"/>
|
||||
<relationship name="userNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="user" inverseEntity="NodeInfoEntity"/>
|
||||
<fetchedProperty name="adminMessages" optional="YES">
|
||||
<fetchRequest name="fetchedPropertyFetchRequest" entity="MessageEntity" predicateString="(fromUser.num == $FETCH_SOURCE.num) AND isEmoji == false AND admin = true"/>
|
||||
</fetchedProperty>
|
||||
<fetchedProperty name="allMessages" optional="YES">
|
||||
<fetchRequest name="fetchedPropertyFetchRequest" entity="MessageEntity" predicateString="((toUser.num == $FETCH_SOURCE.num) OR (fromUser.num == $FETCH_SOURCE.num)) AND toUser != nil AND fromUser != nil AND isEmoji == false AND admin = false"/>
|
||||
</fetchedProperty>
|
||||
</entity>
|
||||
<entity name="WaypointEntity" representedClassName="WaypointEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="created" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="expire" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="icon" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="id" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="lastUpdated" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="latitudeI" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="locked" attributeType="Integer 64" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="longDescription" optional="YES" attributeType="String" maxValueString="100"/>
|
||||
<attribute name="longitudeI" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="name" attributeType="String" minValueString="1" maxValueString="30"/>
|
||||
</entity>
|
||||
</model>
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
//
|
||||
// MapLocation.swift
|
||||
// MeshtasticApple
|
||||
//
|
||||
// Created by Garth Vander Houwen on 12/17/21.
|
||||
//
|
||||
import Foundation
|
||||
import MapKit
|
||||
|
||||
struct MapLocation: Identifiable {
|
||||
|
||||
let id = UUID()
|
||||
let name: String
|
||||
let coordinate: CLLocationCoordinate2D
|
||||
}
|
||||
|
|
@ -45,6 +45,18 @@ class UserSettings: ObservableObject {
|
|||
UserDefaults.standard.set(meshMapType, forKey: "meshMapType")
|
||||
}
|
||||
}
|
||||
@Published var meshMapCenteringMode: Int {
|
||||
didSet {
|
||||
UserDefaults.standard.set(meshMapCenteringMode, forKey: "meshMapCenteringMode")
|
||||
UserDefaults.standard.synchronize()
|
||||
}
|
||||
}
|
||||
@Published var meshMapRecentering: Bool {
|
||||
didSet {
|
||||
UserDefaults.standard.set(meshMapCenteringMode, forKey: "meshMapRecentering")
|
||||
UserDefaults.standard.synchronize()
|
||||
}
|
||||
}
|
||||
@Published var meshMapCustomTileServer: String {
|
||||
didSet {
|
||||
UserDefaults.standard.set(meshMapCustomTileServer, forKey: "meshMapCustomTileServer")
|
||||
|
|
@ -59,7 +71,9 @@ class UserSettings: ObservableObject {
|
|||
self.provideLocation = UserDefaults.standard.object(forKey: "provideLocation") as? Bool ?? false
|
||||
self.provideLocationInterval = UserDefaults.standard.object(forKey: "provideLocationInterval") as? Int ?? 900
|
||||
self.keyboardType = UserDefaults.standard.object(forKey: "keyboardType") as? Int ?? 0
|
||||
self.meshMapType = UserDefaults.standard.string(forKey: "meshMapType") ?? "hybrid"
|
||||
self.meshMapType = UserDefaults.standard.string(forKey: "meshMapType") ?? "standard"
|
||||
self.meshMapCenteringMode = UserDefaults.standard.object(forKey: "meshMapCenteringMode") as? Int ?? 0
|
||||
self.meshMapRecentering = UserDefaults.standard.object(forKey: "meshMapRecentering") as? Bool ?? true
|
||||
self.meshMapCustomTileServer = UserDefaults.standard.string(forKey: "meshMapCustomTileServer") ?? ""
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,6 +52,6 @@ extension PositionEntity {
|
|||
|
||||
extension PositionEntity: MKAnnotation {
|
||||
public var coordinate: CLLocationCoordinate2D { nodeCoordinate ?? LocationHelper.DefaultLocation }
|
||||
public var title: String? { nodePosition?.user?.longName ?? NSLocalizedString("unknown", comment: "Unknown") }
|
||||
public var title: String? { nodePosition?.user?.shortName ?? NSLocalizedString("unknown", comment: "Unknown") }
|
||||
public var subtitle: String? { time?.formatted() }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -119,7 +119,18 @@ func upsertPositionPacket (packet: MeshPacket, context: NSManagedObjectContext)
|
|||
let fetchedNode = try context.fetch(fetchNodePositionRequest) as! [NodeInfoEntity]
|
||||
if fetchedNode.count == 1 {
|
||||
|
||||
// Unset the current latest position for this node
|
||||
let fetchCurrentLatestPositionsRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "PositionEntity")
|
||||
fetchCurrentLatestPositionsRequest.predicate = NSPredicate(format: "nodePosition.num == %lld && latest = true", Int64(packet.from))
|
||||
let fetchedPositions = try context.fetch(fetchCurrentLatestPositionsRequest) as! [PositionEntity]
|
||||
if fetchedPositions.count > 0 {
|
||||
for position in fetchedPositions {
|
||||
position.latest = false
|
||||
}
|
||||
}
|
||||
|
||||
let position = PositionEntity(context: context)
|
||||
position.latest = true
|
||||
position.snr = packet.rxSnr
|
||||
position.seqNo = Int32(positionMessage.seqNumber)
|
||||
position.latitudeI = positionMessage.latitudeI
|
||||
|
|
@ -134,12 +145,14 @@ func upsertPositionPacket (packet: MeshPacket, context: NSManagedObjectContext)
|
|||
position.time = Date(timeIntervalSince1970: TimeInterval(Int64(positionMessage.time)))
|
||||
}
|
||||
let mutablePositions = fetchedNode[0].positions!.mutableCopy() as! NSMutableOrderedSet
|
||||
|
||||
mutablePositions.add(position)
|
||||
fetchedNode[0].id = Int64(packet.from)
|
||||
fetchedNode[0].num = Int64(packet.from)
|
||||
fetchedNode[0].lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(positionMessage.time)))
|
||||
fetchedNode[0].snr = packet.rxSnr
|
||||
fetchedNode[0].positions = mutablePositions.copy() as? NSOrderedSet
|
||||
|
||||
do {
|
||||
try context.save()
|
||||
print("💾 Updated Node Position Coordinates, SNR and Time from Position App Packet For: \(fetchedNode[0].num)")
|
||||
|
|
|
|||
|
|
@ -794,6 +794,10 @@ struct HamParameters {
|
|||
/// Ensure your radio is capable of operating of the selected frequency before setting this.
|
||||
var frequency: Float = 0
|
||||
|
||||
///
|
||||
/// Optional short name of user
|
||||
var shortName: String = String()
|
||||
|
||||
var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
init() {}
|
||||
|
|
@ -1335,6 +1339,7 @@ extension HamParameters: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementa
|
|||
1: .standard(proto: "call_sign"),
|
||||
2: .standard(proto: "tx_power"),
|
||||
3: .same(proto: "frequency"),
|
||||
4: .standard(proto: "short_name"),
|
||||
]
|
||||
|
||||
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||
|
|
@ -1346,6 +1351,7 @@ extension HamParameters: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementa
|
|||
case 1: try { try decoder.decodeSingularStringField(value: &self.callSign) }()
|
||||
case 2: try { try decoder.decodeSingularInt32Field(value: &self.txPower) }()
|
||||
case 3: try { try decoder.decodeSingularFloatField(value: &self.frequency) }()
|
||||
case 4: try { try decoder.decodeSingularStringField(value: &self.shortName) }()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
|
@ -1361,6 +1367,9 @@ extension HamParameters: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementa
|
|||
if self.frequency != 0 {
|
||||
try visitor.visitSingularFloatField(value: self.frequency, fieldNumber: 3)
|
||||
}
|
||||
if !self.shortName.isEmpty {
|
||||
try visitor.visitSingularStringField(value: self.shortName, fieldNumber: 4)
|
||||
}
|
||||
try unknownFields.traverse(visitor: &visitor)
|
||||
}
|
||||
|
||||
|
|
@ -1368,6 +1377,7 @@ extension HamParameters: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementa
|
|||
if lhs.callSign != rhs.callSign {return false}
|
||||
if lhs.txPower != rhs.txPower {return false}
|
||||
if lhs.frequency != rhs.frequency {return false}
|
||||
if lhs.shortName != rhs.shortName {return false}
|
||||
if lhs.unknownFields != rhs.unknownFields {return false}
|
||||
return true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -172,6 +172,11 @@ struct Config {
|
|||
/// Sets the role of node
|
||||
var rebroadcastMode: Config.DeviceConfig.RebroadcastMode = .all
|
||||
|
||||
///
|
||||
/// Send our nodeinfo this often
|
||||
/// Defaults to 900 Seconds (15 minutes)
|
||||
var nodeInfoBroadcastSecs: UInt32 = 0
|
||||
|
||||
var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
///
|
||||
|
|
@ -1561,6 +1566,7 @@ extension Config.DeviceConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImpl
|
|||
4: .standard(proto: "button_gpio"),
|
||||
5: .standard(proto: "buzzer_gpio"),
|
||||
6: .standard(proto: "rebroadcast_mode"),
|
||||
7: .standard(proto: "node_info_broadcast_secs"),
|
||||
]
|
||||
|
||||
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||
|
|
@ -1575,6 +1581,7 @@ extension Config.DeviceConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImpl
|
|||
case 4: try { try decoder.decodeSingularUInt32Field(value: &self.buttonGpio) }()
|
||||
case 5: try { try decoder.decodeSingularUInt32Field(value: &self.buzzerGpio) }()
|
||||
case 6: try { try decoder.decodeSingularEnumField(value: &self.rebroadcastMode) }()
|
||||
case 7: try { try decoder.decodeSingularUInt32Field(value: &self.nodeInfoBroadcastSecs) }()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
|
@ -1599,6 +1606,9 @@ extension Config.DeviceConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImpl
|
|||
if self.rebroadcastMode != .all {
|
||||
try visitor.visitSingularEnumField(value: self.rebroadcastMode, fieldNumber: 6)
|
||||
}
|
||||
if self.nodeInfoBroadcastSecs != 0 {
|
||||
try visitor.visitSingularUInt32Field(value: self.nodeInfoBroadcastSecs, fieldNumber: 7)
|
||||
}
|
||||
try unknownFields.traverse(visitor: &visitor)
|
||||
}
|
||||
|
||||
|
|
@ -1609,6 +1619,7 @@ extension Config.DeviceConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImpl
|
|||
if lhs.buttonGpio != rhs.buttonGpio {return false}
|
||||
if lhs.buzzerGpio != rhs.buzzerGpio {return false}
|
||||
if lhs.rebroadcastMode != rhs.rebroadcastMode {return false}
|
||||
if lhs.nodeInfoBroadcastSecs != rhs.nodeInfoBroadcastSecs {return false}
|
||||
if lhs.unknownFields != rhs.unknownFields {return false}
|
||||
return true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,157 +0,0 @@
|
|||
// DO NOT EDIT.
|
||||
// swift-format-ignore-file
|
||||
//
|
||||
// Generated by the Swift generator plugin for the protocol buffer compiler.
|
||||
// Source: meshtastic/device_metadata.proto
|
||||
//
|
||||
// For information on using the generated types, please see the documentation:
|
||||
// https://github.com/apple/swift-protobuf/
|
||||
|
||||
import Foundation
|
||||
import SwiftProtobuf
|
||||
|
||||
// If the compiler emits an error on this type, it is because this file
|
||||
// was generated by a version of the `protoc` Swift plug-in that is
|
||||
// incompatible with the version of SwiftProtobuf to which you are linking.
|
||||
// Please ensure that you are building against the same version of the API
|
||||
// that was used to generate this file.
|
||||
fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck {
|
||||
struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {}
|
||||
typealias Version = _2
|
||||
}
|
||||
|
||||
///
|
||||
/// Device metadata response
|
||||
struct DeviceMetadata {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
// methods supported on all messages.
|
||||
|
||||
///
|
||||
/// Device firmware version string
|
||||
var firmwareVersion: String = String()
|
||||
|
||||
///
|
||||
/// Device state version
|
||||
var deviceStateVersion: UInt32 = 0
|
||||
|
||||
///
|
||||
/// Indicates whether the device can shutdown CPU natively or via power management chip
|
||||
var canShutdown: Bool = false
|
||||
|
||||
///
|
||||
/// Indicates that the device has native wifi capability
|
||||
var hasWifi_p: Bool = false
|
||||
|
||||
///
|
||||
/// Indicates that the device has native bluetooth capability
|
||||
var hasBluetooth_p: Bool = false
|
||||
|
||||
///
|
||||
/// Indicates that the device has an ethernet peripheral
|
||||
var hasEthernet_p: Bool = false
|
||||
|
||||
///
|
||||
/// Indicates that the device's role in the mesh
|
||||
var role: Config.DeviceConfig.Role = .client
|
||||
|
||||
///
|
||||
/// Indicates the device's current enabled position flags
|
||||
var positionFlags: UInt32 = 0
|
||||
|
||||
///
|
||||
/// Device hardware model
|
||||
var hwModel: HardwareModel = .unset
|
||||
|
||||
var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
init() {}
|
||||
}
|
||||
|
||||
#if swift(>=5.5) && canImport(_Concurrency)
|
||||
extension DeviceMetadata: @unchecked Sendable {}
|
||||
#endif // swift(>=5.5) && canImport(_Concurrency)
|
||||
|
||||
// MARK: - Code below here is support for the SwiftProtobuf runtime.
|
||||
|
||||
fileprivate let _protobuf_package = "meshtastic"
|
||||
|
||||
extension DeviceMetadata: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
||||
static let protoMessageName: String = _protobuf_package + ".DeviceMetadata"
|
||||
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
||||
1: .standard(proto: "firmware_version"),
|
||||
2: .standard(proto: "device_state_version"),
|
||||
3: .same(proto: "canShutdown"),
|
||||
4: .same(proto: "hasWifi"),
|
||||
5: .same(proto: "hasBluetooth"),
|
||||
6: .same(proto: "hasEthernet"),
|
||||
7: .same(proto: "role"),
|
||||
8: .standard(proto: "position_flags"),
|
||||
9: .standard(proto: "hw_model"),
|
||||
]
|
||||
|
||||
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||
while let fieldNumber = try decoder.nextFieldNumber() {
|
||||
// The use of inline closures is to circumvent an issue where the compiler
|
||||
// allocates stack space for every case branch when no optimizations are
|
||||
// enabled. https://github.com/apple/swift-protobuf/issues/1034
|
||||
switch fieldNumber {
|
||||
case 1: try { try decoder.decodeSingularStringField(value: &self.firmwareVersion) }()
|
||||
case 2: try { try decoder.decodeSingularUInt32Field(value: &self.deviceStateVersion) }()
|
||||
case 3: try { try decoder.decodeSingularBoolField(value: &self.canShutdown) }()
|
||||
case 4: try { try decoder.decodeSingularBoolField(value: &self.hasWifi_p) }()
|
||||
case 5: try { try decoder.decodeSingularBoolField(value: &self.hasBluetooth_p) }()
|
||||
case 6: try { try decoder.decodeSingularBoolField(value: &self.hasEthernet_p) }()
|
||||
case 7: try { try decoder.decodeSingularEnumField(value: &self.role) }()
|
||||
case 8: try { try decoder.decodeSingularUInt32Field(value: &self.positionFlags) }()
|
||||
case 9: try { try decoder.decodeSingularEnumField(value: &self.hwModel) }()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
|
||||
if !self.firmwareVersion.isEmpty {
|
||||
try visitor.visitSingularStringField(value: self.firmwareVersion, fieldNumber: 1)
|
||||
}
|
||||
if self.deviceStateVersion != 0 {
|
||||
try visitor.visitSingularUInt32Field(value: self.deviceStateVersion, fieldNumber: 2)
|
||||
}
|
||||
if self.canShutdown != false {
|
||||
try visitor.visitSingularBoolField(value: self.canShutdown, fieldNumber: 3)
|
||||
}
|
||||
if self.hasWifi_p != false {
|
||||
try visitor.visitSingularBoolField(value: self.hasWifi_p, fieldNumber: 4)
|
||||
}
|
||||
if self.hasBluetooth_p != false {
|
||||
try visitor.visitSingularBoolField(value: self.hasBluetooth_p, fieldNumber: 5)
|
||||
}
|
||||
if self.hasEthernet_p != false {
|
||||
try visitor.visitSingularBoolField(value: self.hasEthernet_p, fieldNumber: 6)
|
||||
}
|
||||
if self.role != .client {
|
||||
try visitor.visitSingularEnumField(value: self.role, fieldNumber: 7)
|
||||
}
|
||||
if self.positionFlags != 0 {
|
||||
try visitor.visitSingularUInt32Field(value: self.positionFlags, fieldNumber: 8)
|
||||
}
|
||||
if self.hwModel != .unset {
|
||||
try visitor.visitSingularEnumField(value: self.hwModel, fieldNumber: 9)
|
||||
}
|
||||
try unknownFields.traverse(visitor: &visitor)
|
||||
}
|
||||
|
||||
static func ==(lhs: DeviceMetadata, rhs: DeviceMetadata) -> Bool {
|
||||
if lhs.firmwareVersion != rhs.firmwareVersion {return false}
|
||||
if lhs.deviceStateVersion != rhs.deviceStateVersion {return false}
|
||||
if lhs.canShutdown != rhs.canShutdown {return false}
|
||||
if lhs.hasWifi_p != rhs.hasWifi_p {return false}
|
||||
if lhs.hasBluetooth_p != rhs.hasBluetooth_p {return false}
|
||||
if lhs.hasEthernet_p != rhs.hasEthernet_p {return false}
|
||||
if lhs.role != rhs.role {return false}
|
||||
if lhs.positionFlags != rhs.positionFlags {return false}
|
||||
if lhs.hwModel != rhs.hwModel {return false}
|
||||
if lhs.unknownFields != rhs.unknownFields {return false}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
@ -158,6 +158,10 @@ enum HardwareModel: SwiftProtobuf.Enum {
|
|||
/// New BETAFPV ELRS Micro TX Module 2.4G with ESP32 CPU
|
||||
case betafpv2400Tx // = 45
|
||||
|
||||
///
|
||||
/// BetaFPV ExpressLRS "Nano" TX Module 900MHz with ESP32 CPU
|
||||
case betafpv900NanoTx // = 46
|
||||
|
||||
///
|
||||
/// Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits.
|
||||
case privateHw // = 255
|
||||
|
|
@ -201,6 +205,7 @@ enum HardwareModel: SwiftProtobuf.Enum {
|
|||
case 43: self = .heltecV3
|
||||
case 44: self = .heltecWslV3
|
||||
case 45: self = .betafpv2400Tx
|
||||
case 46: self = .betafpv900NanoTx
|
||||
case 255: self = .privateHw
|
||||
default: self = .UNRECOGNIZED(rawValue)
|
||||
}
|
||||
|
|
@ -240,6 +245,7 @@ enum HardwareModel: SwiftProtobuf.Enum {
|
|||
case .heltecV3: return 43
|
||||
case .heltecWslV3: return 44
|
||||
case .betafpv2400Tx: return 45
|
||||
case .betafpv900NanoTx: return 46
|
||||
case .privateHw: return 255
|
||||
case .UNRECOGNIZED(let i): return i
|
||||
}
|
||||
|
|
@ -284,6 +290,7 @@ extension HardwareModel: CaseIterable {
|
|||
.heltecV3,
|
||||
.heltecWslV3,
|
||||
.betafpv2400Tx,
|
||||
.betafpv900NanoTx,
|
||||
.privateHw,
|
||||
]
|
||||
}
|
||||
|
|
@ -1949,6 +1956,16 @@ struct FromRadio {
|
|||
set {_uniqueStorage()._payloadVariant = .xmodemPacket(newValue)}
|
||||
}
|
||||
|
||||
///
|
||||
/// Device metadata message
|
||||
var metadata: DeviceMetadata {
|
||||
get {
|
||||
if case .metadata(let v)? = _storage._payloadVariant {return v}
|
||||
return DeviceMetadata()
|
||||
}
|
||||
set {_uniqueStorage()._payloadVariant = .metadata(newValue)}
|
||||
}
|
||||
|
||||
var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
///
|
||||
|
|
@ -1995,6 +2012,9 @@ struct FromRadio {
|
|||
///
|
||||
/// File Transfer Chunk
|
||||
case xmodemPacket(XModem)
|
||||
///
|
||||
/// Device metadata message
|
||||
case metadata(DeviceMetadata)
|
||||
|
||||
#if !swift(>=4.1)
|
||||
static func ==(lhs: FromRadio.OneOf_PayloadVariant, rhs: FromRadio.OneOf_PayloadVariant) -> Bool {
|
||||
|
|
@ -2046,6 +2066,10 @@ struct FromRadio {
|
|||
guard case .xmodemPacket(let l) = lhs, case .xmodemPacket(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.metadata, .metadata): return {
|
||||
guard case .metadata(let l) = lhs, case .metadata(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
|
|
@ -2192,6 +2216,54 @@ struct Compressed {
|
|||
init() {}
|
||||
}
|
||||
|
||||
///
|
||||
/// Device metadata response
|
||||
struct DeviceMetadata {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
// methods supported on all messages.
|
||||
|
||||
///
|
||||
/// Device firmware version string
|
||||
var firmwareVersion: String = String()
|
||||
|
||||
///
|
||||
/// Device state version
|
||||
var deviceStateVersion: UInt32 = 0
|
||||
|
||||
///
|
||||
/// Indicates whether the device can shutdown CPU natively or via power management chip
|
||||
var canShutdown: Bool = false
|
||||
|
||||
///
|
||||
/// Indicates that the device has native wifi capability
|
||||
var hasWifi_p: Bool = false
|
||||
|
||||
///
|
||||
/// Indicates that the device has native bluetooth capability
|
||||
var hasBluetooth_p: Bool = false
|
||||
|
||||
///
|
||||
/// Indicates that the device has an ethernet peripheral
|
||||
var hasEthernet_p: Bool = false
|
||||
|
||||
///
|
||||
/// Indicates that the device's role in the mesh
|
||||
var role: Config.DeviceConfig.Role = .client
|
||||
|
||||
///
|
||||
/// Indicates the device's current enabled position flags
|
||||
var positionFlags: UInt32 = 0
|
||||
|
||||
///
|
||||
/// Device hardware model
|
||||
var hwModel: HardwareModel = .unset
|
||||
|
||||
var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
init() {}
|
||||
}
|
||||
|
||||
#if swift(>=5.5) && canImport(_Concurrency)
|
||||
extension HardwareModel: @unchecked Sendable {}
|
||||
extension Constants: @unchecked Sendable {}
|
||||
|
|
@ -2220,6 +2292,7 @@ extension FromRadio.OneOf_PayloadVariant: @unchecked Sendable {}
|
|||
extension ToRadio: @unchecked Sendable {}
|
||||
extension ToRadio.OneOf_PayloadVariant: @unchecked Sendable {}
|
||||
extension Compressed: @unchecked Sendable {}
|
||||
extension DeviceMetadata: @unchecked Sendable {}
|
||||
#endif // swift(>=5.5) && canImport(_Concurrency)
|
||||
|
||||
// MARK: - Code below here is support for the SwiftProtobuf runtime.
|
||||
|
|
@ -2260,6 +2333,7 @@ extension HardwareModel: SwiftProtobuf._ProtoNameProviding {
|
|||
43: .same(proto: "HELTEC_V3"),
|
||||
44: .same(proto: "HELTEC_WSL_V3"),
|
||||
45: .same(proto: "BETAFPV_2400_TX"),
|
||||
46: .same(proto: "BETAFPV_900_NANO_TX"),
|
||||
255: .same(proto: "PRIVATE_HW"),
|
||||
]
|
||||
}
|
||||
|
|
@ -3401,6 +3475,7 @@ extension FromRadio: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementation
|
|||
10: .same(proto: "channel"),
|
||||
11: .same(proto: "queueStatus"),
|
||||
12: .same(proto: "xmodemPacket"),
|
||||
13: .same(proto: "metadata"),
|
||||
]
|
||||
|
||||
fileprivate class _StorageClass {
|
||||
|
|
@ -3566,6 +3641,19 @@ extension FromRadio: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementation
|
|||
_storage._payloadVariant = .xmodemPacket(v)
|
||||
}
|
||||
}()
|
||||
case 13: try {
|
||||
var v: DeviceMetadata?
|
||||
var hadOneofValue = false
|
||||
if let current = _storage._payloadVariant {
|
||||
hadOneofValue = true
|
||||
if case .metadata(let m) = current {v = m}
|
||||
}
|
||||
try decoder.decodeSingularMessageField(value: &v)
|
||||
if let v = v {
|
||||
if hadOneofValue {try decoder.handleConflictingOneOf()}
|
||||
_storage._payloadVariant = .metadata(v)
|
||||
}
|
||||
}()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
|
@ -3626,6 +3714,10 @@ extension FromRadio: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementation
|
|||
guard case .xmodemPacket(let v)? = _storage._payloadVariant else { preconditionFailure() }
|
||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 12)
|
||||
}()
|
||||
case .metadata?: try {
|
||||
guard case .metadata(let v)? = _storage._payloadVariant else { preconditionFailure() }
|
||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 13)
|
||||
}()
|
||||
case nil: break
|
||||
}
|
||||
}
|
||||
|
|
@ -3781,3 +3873,83 @@ extension Compressed: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio
|
|||
return true
|
||||
}
|
||||
}
|
||||
|
||||
extension DeviceMetadata: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
||||
static let protoMessageName: String = _protobuf_package + ".DeviceMetadata"
|
||||
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
||||
1: .standard(proto: "firmware_version"),
|
||||
2: .standard(proto: "device_state_version"),
|
||||
3: .same(proto: "canShutdown"),
|
||||
4: .same(proto: "hasWifi"),
|
||||
5: .same(proto: "hasBluetooth"),
|
||||
6: .same(proto: "hasEthernet"),
|
||||
7: .same(proto: "role"),
|
||||
8: .standard(proto: "position_flags"),
|
||||
9: .standard(proto: "hw_model"),
|
||||
]
|
||||
|
||||
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||
while let fieldNumber = try decoder.nextFieldNumber() {
|
||||
// The use of inline closures is to circumvent an issue where the compiler
|
||||
// allocates stack space for every case branch when no optimizations are
|
||||
// enabled. https://github.com/apple/swift-protobuf/issues/1034
|
||||
switch fieldNumber {
|
||||
case 1: try { try decoder.decodeSingularStringField(value: &self.firmwareVersion) }()
|
||||
case 2: try { try decoder.decodeSingularUInt32Field(value: &self.deviceStateVersion) }()
|
||||
case 3: try { try decoder.decodeSingularBoolField(value: &self.canShutdown) }()
|
||||
case 4: try { try decoder.decodeSingularBoolField(value: &self.hasWifi_p) }()
|
||||
case 5: try { try decoder.decodeSingularBoolField(value: &self.hasBluetooth_p) }()
|
||||
case 6: try { try decoder.decodeSingularBoolField(value: &self.hasEthernet_p) }()
|
||||
case 7: try { try decoder.decodeSingularEnumField(value: &self.role) }()
|
||||
case 8: try { try decoder.decodeSingularUInt32Field(value: &self.positionFlags) }()
|
||||
case 9: try { try decoder.decodeSingularEnumField(value: &self.hwModel) }()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
|
||||
if !self.firmwareVersion.isEmpty {
|
||||
try visitor.visitSingularStringField(value: self.firmwareVersion, fieldNumber: 1)
|
||||
}
|
||||
if self.deviceStateVersion != 0 {
|
||||
try visitor.visitSingularUInt32Field(value: self.deviceStateVersion, fieldNumber: 2)
|
||||
}
|
||||
if self.canShutdown != false {
|
||||
try visitor.visitSingularBoolField(value: self.canShutdown, fieldNumber: 3)
|
||||
}
|
||||
if self.hasWifi_p != false {
|
||||
try visitor.visitSingularBoolField(value: self.hasWifi_p, fieldNumber: 4)
|
||||
}
|
||||
if self.hasBluetooth_p != false {
|
||||
try visitor.visitSingularBoolField(value: self.hasBluetooth_p, fieldNumber: 5)
|
||||
}
|
||||
if self.hasEthernet_p != false {
|
||||
try visitor.visitSingularBoolField(value: self.hasEthernet_p, fieldNumber: 6)
|
||||
}
|
||||
if self.role != .client {
|
||||
try visitor.visitSingularEnumField(value: self.role, fieldNumber: 7)
|
||||
}
|
||||
if self.positionFlags != 0 {
|
||||
try visitor.visitSingularUInt32Field(value: self.positionFlags, fieldNumber: 8)
|
||||
}
|
||||
if self.hwModel != .unset {
|
||||
try visitor.visitSingularEnumField(value: self.hwModel, fieldNumber: 9)
|
||||
}
|
||||
try unknownFields.traverse(visitor: &visitor)
|
||||
}
|
||||
|
||||
static func ==(lhs: DeviceMetadata, rhs: DeviceMetadata) -> Bool {
|
||||
if lhs.firmwareVersion != rhs.firmwareVersion {return false}
|
||||
if lhs.deviceStateVersion != rhs.deviceStateVersion {return false}
|
||||
if lhs.canShutdown != rhs.canShutdown {return false}
|
||||
if lhs.hasWifi_p != rhs.hasWifi_p {return false}
|
||||
if lhs.hasBluetooth_p != rhs.hasBluetooth_p {return false}
|
||||
if lhs.hasEthernet_p != rhs.hasEthernet_p {return false}
|
||||
if lhs.role != rhs.role {return false}
|
||||
if lhs.positionFlags != rhs.positionFlags {return false}
|
||||
if lhs.hwModel != rhs.hwModel {return false}
|
||||
if lhs.unknownFields != rhs.unknownFields {return false}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,10 +22,10 @@ struct Connect: View {
|
|||
@State var isUnsetRegion = false
|
||||
@State var invalidFirmwareVersion = false
|
||||
|
||||
var body: some View {
|
||||
var body: some View {
|
||||
|
||||
NavigationStack {
|
||||
VStack {
|
||||
VStack {
|
||||
List {
|
||||
if bleManager.isSwitchedOn {
|
||||
Section(header: Text("connected.radio").font(.title)) {
|
||||
|
|
@ -202,7 +202,7 @@ struct Connect: View {
|
|||
|
||||
HStack(alignment: .center) {
|
||||
Spacer()
|
||||
|
||||
|
||||
#if targetEnvironment(macCatalyst)
|
||||
|
||||
if bleManager.connectedPeripheral != nil {
|
||||
|
|
@ -226,15 +226,15 @@ struct Connect: View {
|
|||
}
|
||||
#endif
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
.padding(.bottom, 10)
|
||||
}
|
||||
.navigationTitle("bluetooth")
|
||||
}
|
||||
.navigationTitle("bluetooth")
|
||||
.navigationBarItems(leading: MeshtasticLogo(), trailing:
|
||||
ZStack {
|
||||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????")
|
||||
})
|
||||
}
|
||||
}
|
||||
.sheet(isPresented: $invalidFirmwareVersion, onDismiss: didDismissSheet) {
|
||||
InvalidVersion(minimumVersion: self.bleManager.minimumVersion, version: self.bleManager.connectedVersion)
|
||||
.presentationDetents([.large])
|
||||
|
|
@ -267,7 +267,7 @@ struct Connect: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
.onAppear(perform: {
|
||||
.onAppear(perform: {
|
||||
self.bleManager.context = context
|
||||
self.bleManager.userSettings = userSettings
|
||||
|
||||
|
|
@ -285,7 +285,7 @@ struct Connect: View {
|
|||
isPreferredRadio = false
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
func didDismissSheet() {
|
||||
bleManager.disconnectPeripheral(reconnect: false)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ struct ContentView: View {
|
|||
|
||||
TabView(selection: $selection) {
|
||||
|
||||
if userSettings.preferredNodeNum > 0 {
|
||||
if userSettings.preferredPeripheralId.count > 0 {
|
||||
|
||||
Contacts()
|
||||
.tabItem {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,10 @@
|
|||
import SwiftUI
|
||||
import MapKit
|
||||
|
||||
func degreesToRadians(_ number: Double) -> Double {
|
||||
return number * .pi / 180
|
||||
}
|
||||
|
||||
struct MapViewSwiftUI: UIViewRepresentable {
|
||||
|
||||
var onLongPress: (_ waypointCoordinate: CLLocationCoordinate2D) -> Void
|
||||
|
|
@ -15,7 +19,10 @@ struct MapViewSwiftUI: UIViewRepresentable {
|
|||
let positions: [PositionEntity]
|
||||
let waypoints: [WaypointEntity]
|
||||
let mapViewType: MKMapType
|
||||
let centeringMode: CenteringMode
|
||||
|
||||
let centerOnPositionsOnly: Bool
|
||||
@AppStorage("meshMapRecentering") private var recenter = false
|
||||
|
||||
// Offline Maps
|
||||
//make this view dependent on the UserDefault that is updated when importing a new map file
|
||||
|
|
@ -28,28 +35,54 @@ struct MapViewSwiftUI: UIViewRepresentable {
|
|||
|
||||
func makeUIView(context: Context) -> MKMapView {
|
||||
// Parameters
|
||||
mapView.mapType = mapViewType
|
||||
mapView.addAnnotations(waypoints)
|
||||
if centerOnPositionsOnly {
|
||||
mapView.fit(annotations: positions, andShow: true)
|
||||
} else {
|
||||
// Logic to manage the map centering options
|
||||
switch centeringMode {
|
||||
case .allAnnotations:
|
||||
mapView.addAnnotations(positions)
|
||||
mapView.fitAllAnnotations()
|
||||
case .allPositions:
|
||||
mapView.fit(annotations: positions, andShow: true)
|
||||
case .clientGps:
|
||||
|
||||
let span = MKCoordinateSpan(latitudeDelta: 0.003, longitudeDelta: 0.003)
|
||||
let center = CLLocationCoordinate2D(latitude: LocationHelper.currentLocation.latitude, longitude: LocationHelper.currentLocation.longitude)
|
||||
let region = MKCoordinateRegion(center: center, span: span)
|
||||
mapView.setRegion(region, animated: true)
|
||||
mapView.setUserTrackingMode(.followWithHeading, animated: true)
|
||||
mapView.addAnnotations(positions)
|
||||
}
|
||||
mapView.mapType = mapViewType
|
||||
mapView.setUserTrackingMode(.none, animated: true)
|
||||
|
||||
// Other MKMapView Settings
|
||||
mapView.showsUserLocation = true
|
||||
mapView.preferredConfiguration.elevationStyle = .realistic
|
||||
mapView.isPitchEnabled = true
|
||||
mapView.isRotateEnabled = true
|
||||
mapView.isScrollEnabled = true
|
||||
mapView.isZoomEnabled = true
|
||||
mapView.showsBuildings = true
|
||||
mapView.showsCompass = true
|
||||
mapView.showsScale = true
|
||||
mapView.showsTraffic = true
|
||||
mapView.showsUserLocation = true
|
||||
#if targetEnvironment(macCatalyst)
|
||||
|
||||
#if targetEnvironment(macCatalyst)
|
||||
// Show the default always visible compass and the mac only controls
|
||||
mapView.showsCompass = true
|
||||
mapView.showsZoomControls = true
|
||||
#endif
|
||||
mapView.showsPitchControl = true
|
||||
#else
|
||||
|
||||
#if os(iOS)
|
||||
// Hide the default compass that only appears when you are not going north and instead always show the compass in the bottom right corner of the map
|
||||
mapView.showsCompass = false
|
||||
let compassButton = MKCompassButton(mapView: mapView) // Make a new compass
|
||||
compassButton.compassVisibility = .visible // Make it visible
|
||||
mapView.addSubview(compassButton) // Add it to the view
|
||||
compassButton.translatesAutoresizingMaskIntoConstraints = false
|
||||
compassButton.trailingAnchor.constraint(equalTo: mapView.trailingAnchor, constant: -5).isActive = true
|
||||
compassButton.bottomAnchor.constraint(equalTo: mapView.bottomAnchor, constant: -25).isActive = true
|
||||
#endif
|
||||
#endif
|
||||
mapView.delegate = context.coordinator
|
||||
return mapView
|
||||
}
|
||||
|
|
@ -82,8 +115,27 @@ struct MapViewSwiftUI: UIViewRepresentable {
|
|||
|
||||
DispatchQueue.main.async {
|
||||
mapView.removeAnnotations(mapView.annotations)
|
||||
mapView.addAnnotations(positions)
|
||||
mapView.addAnnotations(waypoints)
|
||||
switch centeringMode {
|
||||
case .allAnnotations:
|
||||
mapView.addAnnotations(positions)
|
||||
if recenter {
|
||||
mapView.fitAllAnnotations()
|
||||
}
|
||||
case .allPositions:
|
||||
if recenter {
|
||||
mapView.fit(annotations: positions, andShow: true)
|
||||
} else {
|
||||
mapView.addAnnotations(positions)
|
||||
}
|
||||
case .clientGps:
|
||||
mapView.addAnnotations(positions)
|
||||
if recenter {
|
||||
let span = MKCoordinateSpan(latitudeDelta: 0.003, longitudeDelta: 0.003)
|
||||
let region = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: LocationHelper.currentLocation.latitude, longitude: LocationHelper.currentLocation.longitude), span: span)
|
||||
mapView.setRegion(region, animated: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -114,20 +166,18 @@ struct MapViewSwiftUI: UIViewRepresentable {
|
|||
switch annotation {
|
||||
|
||||
case _ as MKClusterAnnotation:
|
||||
let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "nodeGroup") as? MKMarkerAnnotationView ?? MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: "NodeGroup")
|
||||
let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "nodeGroup") as? MKMarkerAnnotationView ?? MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: "WaypointGroup")
|
||||
annotationView.markerTintColor = .brown//.systemRed
|
||||
annotationView.displayPriority = .defaultLow
|
||||
annotationView.tag = -1
|
||||
return annotationView
|
||||
case let positionAnnotation as PositionEntity:
|
||||
let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "node") as? MKMarkerAnnotationView ?? MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: "Node")
|
||||
let reuseID = String(positionAnnotation.nodePosition?.num ?? 0) + "-" + String(positionAnnotation.time?.timeIntervalSince1970 ?? 0)
|
||||
let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "node") as? MKMarkerAnnotationView ?? MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: reuseID )
|
||||
annotationView.tag = -1
|
||||
annotationView.canShowCallout = true
|
||||
annotationView.glyphText = "📟"
|
||||
|
||||
let latest = parent.positions.last(where: { $0.nodePosition?.num ?? 0 == positionAnnotation.nodePosition?.num ?? -1 })
|
||||
|
||||
if latest == positionAnnotation {
|
||||
if positionAnnotation.latest {
|
||||
annotationView.markerTintColor = .systemRed
|
||||
annotationView.displayPriority = .required
|
||||
annotationView.titleVisibility = .visible
|
||||
|
|
@ -136,19 +186,33 @@ struct MapViewSwiftUI: UIViewRepresentable {
|
|||
annotationView.markerTintColor = UIColor(.indigo)
|
||||
annotationView.displayPriority = .defaultHigh
|
||||
annotationView.titleVisibility = .adaptive
|
||||
annotationView.clusteringIdentifier = "nodeGroup"
|
||||
}
|
||||
|
||||
annotationView.tag = -1
|
||||
annotationView.canShowCallout = true
|
||||
annotationView.titleVisibility = .adaptive
|
||||
let leftIcon = UIImageView(image: annotationView.glyphText?.image())
|
||||
leftIcon.backgroundColor = UIColor(.indigo)
|
||||
annotationView.leftCalloutAccessoryView = leftIcon
|
||||
let subtitle = UILabel()
|
||||
subtitle.text = "Latitude: \(String(format: "%.5f", positionAnnotation.coordinate.latitude)) \n"
|
||||
subtitle.text = "Long Name: \(positionAnnotation.nodePosition?.user?.longName ?? "Unknown") \n"
|
||||
subtitle.text! += "Latitude: \(String(format: "%.5f", positionAnnotation.coordinate.latitude)) \n"
|
||||
subtitle.text! += "Longitude: \(String(format: "%.5f", positionAnnotation.coordinate.longitude)) \n"
|
||||
let distanceFormatter = MKDistanceFormatter()
|
||||
subtitle.text! += "Altitude: \(distanceFormatter.string(fromDistance: Double(positionAnnotation.altitude))) \n"
|
||||
if positionAnnotation.nodePosition?.metadata != nil {
|
||||
|
||||
if DeviceRoles(rawValue: Int(positionAnnotation.nodePosition!.metadata?.role ?? 0)) == DeviceRoles.client ||
|
||||
DeviceRoles(rawValue: Int(positionAnnotation.nodePosition!.metadata?.role ?? 0)) == DeviceRoles.clientMute ||
|
||||
DeviceRoles(rawValue: Int(positionAnnotation.nodePosition!.metadata?.role ?? 0)) == DeviceRoles.routerClient{
|
||||
annotationView.glyphImage = UIImage(systemName: "flipphone")
|
||||
} else if DeviceRoles(rawValue: Int(positionAnnotation.nodePosition!.metadata?.role ?? 0)) == DeviceRoles.repeater {
|
||||
annotationView.glyphImage = UIImage(systemName: "repeat")
|
||||
} else if DeviceRoles(rawValue: Int(positionAnnotation.nodePosition!.metadata?.role ?? 0)) == DeviceRoles.router {
|
||||
annotationView.glyphImage = UIImage(systemName: "wifi.router.fill")
|
||||
} else if DeviceRoles(rawValue: Int(positionAnnotation.nodePosition!.metadata?.role ?? 0)) == DeviceRoles.tracker {
|
||||
annotationView.glyphImage = UIImage(systemName: "location.viewfinder")
|
||||
}
|
||||
|
||||
let pf = PositionFlags(rawValue: Int(positionAnnotation.nodePosition?.metadata?.positionFlags ?? 3))
|
||||
if pf.contains(.Satsinview) {
|
||||
subtitle.text! += "Sats in view: \(String(positionAnnotation.satsInView)) \n"
|
||||
|
|
@ -162,8 +226,12 @@ struct MapViewSwiftUI: UIViewRepresentable {
|
|||
subtitle.text! += "Speed: \(formatter.string(from: Measurement(value: Double(positionAnnotation.speed), unit: UnitSpeed.kilometersPerHour))) \n"
|
||||
}
|
||||
if pf.contains(.Heading) {
|
||||
|
||||
annotationView.glyphImage = UIImage(systemName: "location.north.fill")?.rotate(radians: Float(degreesToRadians(Double(positionAnnotation.heading))))
|
||||
subtitle.text! += "Heading: \(String(positionAnnotation.heading)) \n"
|
||||
}
|
||||
} else {
|
||||
annotationView.glyphImage = UIImage(systemName: "flipphone")
|
||||
}
|
||||
subtitle.text! += positionAnnotation.time?.formatted() ?? "Unknown \n"
|
||||
subtitle.numberOfLines = 0
|
||||
|
|
@ -173,7 +241,7 @@ struct MapViewSwiftUI: UIViewRepresentable {
|
|||
annotationView.rightCalloutAccessoryView = detailsIcon
|
||||
return annotationView
|
||||
case let waypointAnnotation as WaypointEntity:
|
||||
let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "waypoint") as? MKMarkerAnnotationView ?? MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: "Waypoint")
|
||||
let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "waypoint") as? MKMarkerAnnotationView ?? MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: String(waypointAnnotation.id))
|
||||
annotationView.tag = Int(waypointAnnotation.id)
|
||||
annotationView.isEnabled = true
|
||||
annotationView.canShowCallout = true
|
||||
|
|
@ -243,9 +311,9 @@ struct MapViewSwiftUI: UIViewRepresentable {
|
|||
}
|
||||
|
||||
public func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
|
||||
|
||||
|
||||
if let index = self.overlays.firstIndex(where: { overlay_ in overlay_.shape.hash == overlay.hash }) {
|
||||
|
||||
|
||||
let unwrappedOverlay = self.overlays[index]
|
||||
if let circleOverlay = unwrappedOverlay.shape as? MKCircle {
|
||||
let renderer = MKCircleRenderer(circle: circleOverlay)
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ struct NodeDetail: View {
|
|||
@Environment(\.managedObjectContext) var context
|
||||
@EnvironmentObject var bleManager: BLEManager
|
||||
@Environment(\.colorScheme) var colorScheme: ColorScheme
|
||||
@AppStorage("meshMapType") private var meshMapType = "standard"
|
||||
@State private var mapType: MKMapType = .standard
|
||||
@State var waypointCoordinate: CLLocationCoordinate2D?
|
||||
@State var editingWaypoint: Int = 0
|
||||
|
|
@ -39,6 +40,7 @@ struct NodeDetail: View {
|
|||
/// The current weather condition for the city.
|
||||
@State private var condition: WeatherCondition?
|
||||
@State private var temperature: Measurement<UnitTemperature>?
|
||||
@State private var humidity: Int?
|
||||
@State private var symbolName: String = "cloud.fill"
|
||||
|
||||
@State private var attributionLink: URL?
|
||||
|
|
@ -65,7 +67,9 @@ struct NodeDetail: View {
|
|||
editingWaypoint = wpId
|
||||
presentingWaypointForm = true
|
||||
}
|
||||
}, positions: annotations, waypoints: Array(waypoints), mapViewType: mapType,
|
||||
}, positions: annotations, waypoints: Array(waypoints),
|
||||
mapViewType: mapType,
|
||||
centeringMode: .allPositions,
|
||||
centerOnPositionsOnly: true,
|
||||
customMapOverlay: self.customMapOverlay,
|
||||
overlays: self.overlays
|
||||
|
|
@ -84,8 +88,12 @@ struct NodeDetail: View {
|
|||
.pickerStyle(.menu)
|
||||
.padding(5)
|
||||
VStack {
|
||||
Label(temperature?.formatted() ?? "??", systemImage: symbolName)
|
||||
Label(temperature?.formatted(.measurement(width: .narrow)) ?? "??", systemImage: symbolName)
|
||||
.font(.caption)
|
||||
|
||||
|
||||
Label("\(humidity ?? 0)%", systemImage: "humidity")
|
||||
.font(.caption2)
|
||||
}
|
||||
.padding(10)
|
||||
.background(.thinMaterial, in: RoundedRectangle(cornerRadius: 12, style: .continuous))
|
||||
|
|
@ -351,12 +359,11 @@ struct NodeDetail: View {
|
|||
}
|
||||
}
|
||||
|
||||
if (self.bleManager.connectedPeripheral != nil && self.bleManager.connectedPeripheral.num == node.num)
|
||||
|| (self.bleManager.connectedPeripheral != nil && node.metadata != nil) {
|
||||
if (self.bleManager.connectedPeripheral != nil && node.metadata != nil) {
|
||||
|
||||
HStack {
|
||||
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context)
|
||||
if node.metadata?.canShutdown ?? false {
|
||||
if node.metadata?.canShutdown ?? false || hwModelString == "RAK4631" {//node.metadata?.hwModel ?? "UNSET" == "RAK4631" {
|
||||
|
||||
Button(action: {
|
||||
showingShutdownConfirm = true
|
||||
|
|
@ -436,6 +443,28 @@ struct NodeDetail: View {
|
|||
})
|
||||
.onAppear {
|
||||
self.bleManager.context = context
|
||||
switch meshMapType {
|
||||
case "standard":
|
||||
mapType = .standard
|
||||
break
|
||||
case "mutedStandard":
|
||||
mapType = .mutedStandard
|
||||
break
|
||||
case "hybrid":
|
||||
mapType = .hybrid
|
||||
break
|
||||
case "hybridFlyover":
|
||||
mapType = .hybridFlyover
|
||||
break
|
||||
case "satellite":
|
||||
mapType = .satellite
|
||||
break
|
||||
case "satelliteFlyover":
|
||||
mapType = .satelliteFlyover
|
||||
break
|
||||
default:
|
||||
mapType = .hybridFlyover
|
||||
}
|
||||
}
|
||||
.task(id: node.num) {
|
||||
do {
|
||||
|
|
@ -447,6 +476,7 @@ struct NodeDetail: View {
|
|||
let weather = try await WeatherService.shared.weather(for: mostRecent.nodeLocation!)
|
||||
condition = weather.currentWeather.condition
|
||||
temperature = weather.currentWeather.temperature
|
||||
humidity = Int(weather.currentWeather.humidity * 100)
|
||||
symbolName = weather.currentWeather.symbolName
|
||||
|
||||
let attribution = try await WeatherService.shared.attribution
|
||||
|
|
|
|||
|
|
@ -29,6 +29,9 @@ struct NodeMap: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
@AppStorage("meshMapType") private var meshMapType = "hybridFlyover"
|
||||
@AppStorage("meshMapCenteringMode") private var meshMapCenteringMode = 0
|
||||
|
||||
//&& nodePosition != nil
|
||||
@FetchRequest(sortDescriptors: [NSSortDescriptor(key: "time", ascending: true)],
|
||||
predicate: NSPredicate(format: "time >= %@ && nodePosition != nil", Calendar.current.startOfDay(for: Date()) as NSDate), animation: .none)
|
||||
|
|
@ -41,6 +44,7 @@ struct NodeMap: View {
|
|||
private var waypoints: FetchedResults<WaypointEntity>
|
||||
|
||||
@State private var mapType: MKMapType = .standard
|
||||
@State private var mapCenteringMode: CenteringMode = .allAnnotations
|
||||
@State var waypointCoordinate: CLLocationCoordinate2D = LocationHelper.DefaultLocation
|
||||
@State var editingWaypoint: Int = 0
|
||||
@State private var presentingWaypointForm = false
|
||||
|
|
@ -71,7 +75,8 @@ struct NodeMap: View {
|
|||
}
|
||||
}, positions: Array(positions),
|
||||
waypoints: Array(waypoints),
|
||||
mapViewType: mapType ?? MKMapType.standard,
|
||||
mapViewType: mapType,
|
||||
centeringMode: mapCenteringMode,
|
||||
centerOnPositionsOnly: false,
|
||||
customMapOverlay: self.customMapOverlay,
|
||||
overlays: self.overlays
|
||||
|
|
@ -109,6 +114,29 @@ struct NodeMap: View {
|
|||
.onAppear(perform: {
|
||||
self.bleManager.context = context
|
||||
self.bleManager.userSettings = userSettings
|
||||
mapCenteringMode = CenteringMode(rawValue: meshMapCenteringMode) ?? CenteringMode.allAnnotations
|
||||
switch meshMapType {
|
||||
case "standard":
|
||||
mapType = .standard
|
||||
break
|
||||
case "mutedStandard":
|
||||
mapType = .mutedStandard
|
||||
break
|
||||
case "hybrid":
|
||||
mapType = .hybrid
|
||||
break
|
||||
case "hybridFlyover":
|
||||
mapType = .hybridFlyover
|
||||
break
|
||||
case "satellite":
|
||||
mapType = .satellite
|
||||
break
|
||||
case "satelliteFlyover":
|
||||
mapType = .satelliteFlyover
|
||||
break
|
||||
default:
|
||||
mapType = .hybridFlyover
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,10 +13,6 @@ struct AppSettings: View {
|
|||
@State private var isPresentingCoreDataResetConfirm = false
|
||||
@State private var preferredDeviceConnected = false
|
||||
|
||||
var perferredPeripheral: String {
|
||||
UserDefaults.standard.object(forKey: "preferredPeripheralName") as? String ?? ""
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
Form {
|
||||
|
|
@ -39,13 +35,6 @@ struct AppSettings: View {
|
|||
}
|
||||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
|
||||
Picker("map.type", selection: $userSettings.meshMapType) {
|
||||
ForEach(MeshMapType.allCases) { map in
|
||||
Text(map.description)
|
||||
}
|
||||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -70,6 +59,31 @@ struct AppSettings: View {
|
|||
.listRowSeparator(.visible)
|
||||
}
|
||||
}
|
||||
|
||||
Section(header: Text("global map options")) {
|
||||
|
||||
Picker("map.type", selection: $userSettings.meshMapType) {
|
||||
ForEach(MeshMapType.allCases) { map in
|
||||
Text(map.description)
|
||||
}
|
||||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
}
|
||||
|
||||
Section(header: Text("mesh map options")) {
|
||||
Picker("map.centering", selection: $userSettings.meshMapCenteringMode) {
|
||||
ForEach(CenteringMode.allCases) { cm in
|
||||
Text(cm.description)
|
||||
}
|
||||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
|
||||
Toggle(isOn: $userSettings.meshMapRecentering) {
|
||||
|
||||
Label("map.recentering", systemImage: "rectangle.center.inset.filled")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
}
|
||||
}
|
||||
HStack {
|
||||
Button {
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
//
|
||||
// LicensedUser.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Created by Garth Vander Houwen on 2/4/23.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
|
@ -81,7 +81,7 @@ struct Settings: View {
|
|||
let connectedNode = nodes.first(where: { $0.num == connectedNodeNum })
|
||||
connectedNodeNum = Int(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.num : 0)
|
||||
|
||||
if node?.metadata == nil && node!.num != connectedNodeNum {
|
||||
if node?.metadata == nil {
|
||||
let adminMessageId = bleManager.requestDeviceMetadata(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode!.myInfo!.adminIndex, context: context)
|
||||
|
||||
if adminMessageId > 0 {
|
||||
|
|
|
|||
|
|
@ -288,7 +288,7 @@ struct ShareChannels: View {
|
|||
loRaConfig.usePreset = node?.loRaConfig?.usePreset ?? true
|
||||
loRaConfig.channelNum = UInt32(node?.loRaConfig?.channelNum ?? 0)
|
||||
channelSet.loraConfig = loRaConfig
|
||||
if node != nil && node?.myInfo != nil {
|
||||
if node?.myInfo?.channels != nil && node?.myInfo?.channels?.count ?? 0 > 0 {
|
||||
for ch in node!.myInfo!.channels!.array as! [ChannelEntity] {
|
||||
if ch.role > 0 {
|
||||
|
||||
|
|
|
|||
|
|
@ -48,8 +48,8 @@ struct UserConfig: View {
|
|||
.onChange(of: longName, perform: { value in
|
||||
let totalBytes = longName.utf8.count
|
||||
// Only mess with the value if it is too big
|
||||
if totalBytes > 36 {
|
||||
let firstNBytes = Data(longName.utf8.prefix(36))
|
||||
if totalBytes > (isLicensed ? 8 : 36) {
|
||||
let firstNBytes = Data(longName.utf8.prefix(isLicensed ? 8 : 36))
|
||||
if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) {
|
||||
// Set the longName back to the last place where it was the right size
|
||||
longName = maxBytesString
|
||||
|
|
@ -59,7 +59,7 @@ struct UserConfig: View {
|
|||
}
|
||||
.keyboardType(.default)
|
||||
.disableAutocorrection(true)
|
||||
Text("\(String(isLicensed ? "Call Sign" : "Long Name")) can be up to 36 bytes long.")
|
||||
Text("\(String(isLicensed ? "Call Sign" : "Long Name")) can be up to \(isLicensed ? "8" : "36") bytes long.")
|
||||
.font(.caption2)
|
||||
|
||||
HStack {
|
||||
|
|
|
|||
|
|
@ -137,6 +137,8 @@
|
|||
"lora"="LoRa";
|
||||
"lora.config"="LoRa Einstellungen";
|
||||
"map"="Mesh Karte";
|
||||
"map.centering"="Centering";
|
||||
"map.recentering"="Automatic Re-centering";
|
||||
"map.type"="kartentyp";
|
||||
"mesh.log"="Mesh Log";
|
||||
"mesh.log.bluetooth.config %@"="Bluetooth Konfiguration empfangen: %@";
|
||||
|
|
|
|||
|
|
@ -137,7 +137,9 @@
|
|||
"lora"="LoRa";
|
||||
"lora.config"="LoRa Config";
|
||||
"map"="Mesh Map";
|
||||
"map.type"="Map Type";
|
||||
"map.type"="Default Type";
|
||||
"map.centering"="Centering Mode";
|
||||
"map.recentering"="Automatic Re-centering";
|
||||
"mesh.log"="Mesh Log";
|
||||
"mesh.log.bluetooth.config %@"="Bluetooth config received: %@";
|
||||
"mesh.log.cannedmessage.config %@"="Canned Message module config received: %@";
|
||||
|
|
|
|||
|
|
@ -137,6 +137,8 @@
|
|||
"lora"="LoRa";
|
||||
"lora.config"="LoRa 配置";
|
||||
"map"="Mesh 地图";
|
||||
"map.centering"="Centering";
|
||||
"map.recentering"="Automatic Re-centering";
|
||||
"map.type"="地图类型";
|
||||
"mesh.log"="Mesh 日志";
|
||||
"mesh.log.bluetooth.config %@"="Bluetooth config received: %@";
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue