Merge pull request #106 from meshtastic/feature/user_settings

More settings updates
This commit is contained in:
Garth Vander Houwen 2022-06-29 20:12:07 -07:00 committed by GitHub
commit a94cc8f11e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 254 additions and 74 deletions

View file

@ -847,7 +847,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.3.21;
MARKETING_VERSION = 1.3.22;
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTS_MACCATALYST = YES;
@ -879,7 +879,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.3.21;
MARKETING_VERSION = 1.3.22;
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTS_MACCATALYST = YES;

View file

@ -55,13 +55,13 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
let FROMRADIO_UUID = CBUUID(string: "0x8BA2BCC2-EE02-4A55-A531-C525C5E454D5")
let FROMNUM_UUID = CBUUID(string: "0xED9DA18C-A800-4F66-A670-AA7547E34453")
private var meshLoggingEnabled: Bool = false
private var meshLoggingEnabled: Bool = true
let meshLog = documentsFolder.appendingPathComponent("meshlog.txt")
// MARK: init BLEManager
override init() {
self.meshLoggingEnabled = UserDefaults.standard.object(forKey: "meshActivityLog") as? Bool ?? false
//self.meshLoggingEnabled = UserDefaults.standard.object(forKey: "meshActivityLog") as? Bool ?? false
self.lastConnectionError = ""
self.lastConnnectionVersion = "0.0.0"
super.init()
@ -443,10 +443,17 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
localConfig(config: decodedInfo.config, meshlogging: meshLoggingEnabled, context: context!, nodeNum: self.connectedPeripheral.num, nodeLongName: self.connectedPeripheral.longName)
} else if decodedInfo.moduleConfig.isInitialized {
moduleConfig(config: decodedInfo.moduleConfig, meshlogging: meshLoggingEnabled, context: context!, nodeNum: self.connectedPeripheral.num, nodeLongName: self.connectedPeripheral.longName)
} else {
if decodedInfo.configCompleteID == 0 {
if meshLoggingEnabled { MeshLogger.log(" MESH PACKET received for App UNHANDLED \(try! decodedInfo.packet.jsonString())") }
} else {
if meshLoggingEnabled { MeshLogger.log(" MESH PACKET received for Unknown App UNHANDLED \(try! decodedInfo.packet.jsonString())") }
}
}
@ -1038,10 +1045,12 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
return false
}
public func getModuleConfig (configType: AdminMessage.ModuleConfigType, destNum: Int64, wantResponse: Bool) -> Bool {
public func getChannelSet (destNum: Int64, wantResponse: Bool) -> Bool {
var adminPacket = AdminMessage()
adminPacket.getModuleConfigRequest = configType
adminPacket.getChannelRequest = 1
var meshPacket: MeshPacket = MeshPacket()
meshPacket.to = UInt32(connectedPeripheral.num)

View file

@ -328,9 +328,81 @@ func localConfig (config: Config, meshlogging: Bool, context:NSManagedObjectCont
}
}
func moduleConfig (config: ModuleConfig, meshlogging: Bool, context:NSManagedObject, nodeNum: Int64, nodeLongName: String) {
func moduleConfig (config: ModuleConfig, meshlogging: Bool, context:NSManagedObjectContext, nodeNum: Int64, nodeLongName: String) {
// We don't care about any of the WiFi related MQTT settings
if config.payloadVariant == ModuleConfig.OneOf_PayloadVariant.rangeTest(config.rangeTest) {
var isDefault = false
if (try! config.rangeTest.jsonString()) == "{}" {
isDefault = true
print("⛰️ Default Range Test Module config")
}
let fetchNodeInfoRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "NodeInfoEntity")
fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum))
do {
let fetchedNode = try context.fetch(fetchNodeInfoRequest) as! [NodeInfoEntity]
// Found a node, save Device Config
if !fetchedNode.isEmpty {
if fetchedNode[0].rangeTestConfig == nil {
let newRangeTestConfig = RangeTestConfigEntity(context: context)
if isDefault {
newRangeTestConfig.sender = 0
newRangeTestConfig.enabled = false
newRangeTestConfig.save = false
} else {
newRangeTestConfig.sender = Int32(config.rangeTest.sender)
newRangeTestConfig.enabled = !config.rangeTest.enabled
newRangeTestConfig.save = config.rangeTest.save
}
fetchedNode[0].rangeTestConfig = newRangeTestConfig
} else {
if isDefault {
fetchedNode[0].rangeTestConfig?.sender = 0
fetchedNode[0].rangeTestConfig?.enabled = false
fetchedNode[0].rangeTestConfig?.save = false
} else {
// Client default protobuf value of 0
fetchedNode[0].rangeTestConfig?.sender = Int32(config.rangeTest.sender)
fetchedNode[0].rangeTestConfig?.enabled = !config.rangeTest.enabled
fetchedNode[0].rangeTestConfig?.save = config.rangeTest.save
}
}
do {
try context.save()
if meshlogging { MeshLogger.log("💾 Updated Range Test Config for node number: \(String(nodeNum))") }
} catch {
context.rollback()
let nsError = error as NSError
print("💥 Error Updating Core Data RangeTestConfigEntity: \(nsError)")
}
}
} catch {
}
}
}
func myInfoPacket (myInfo: MyNodeInfo, meshLogging: Bool, context: NSManagedObjectContext) -> MyInfoEntity? {
@ -782,16 +854,20 @@ func routingPacket (packet: MeshPacket, meshLogging: Bool, context: NSManagedObj
do {
let fetchedMessage = try context.fetch(fetchMessageRequest)[0] as? MessageEntity
let fetchedMessage = try context.fetch(fetchMessageRequest) as? [MessageEntity]
if fetchedMessage != nil {
if fetchedMessage?.count ?? 0 > 0 {
fetchedMessage!.receivedACK = true
fetchedMessage!.ackSNR = packet.rxSnr
fetchedMessage!.ackTimestamp = Int32(packet.rxTime)
fetchedMessage!.objectWillChange.send()
fetchedMessage!.fromUser?.objectWillChange.send()
fetchedMessage!.toUser?.objectWillChange.send()
fetchedMessage![0].receivedACK = true
fetchedMessage![0].ackSNR = packet.rxSnr
fetchedMessage![0].ackTimestamp = Int32(packet.rxTime)
fetchedMessage![0].objectWillChange.send()
fetchedMessage![0].fromUser?.objectWillChange.send()
fetchedMessage![0].toUser?.objectWillChange.send()
} else {
return
}
try context.save()

View file

@ -4,7 +4,7 @@
<dict>
<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:meshtastic.org/*</string>
<string>applinks:meshtastic.org/e/*</string>
</array>
<key>com.apple.security.app-sandbox</key>
<true/>

View file

@ -20,21 +20,17 @@ struct MeshtasticAppleApp: App {
.environmentObject(bleManager)
.environmentObject(userSettings)
.onContinueUserActivity(NSUserActivityTypeBrowsingWeb) { userActivity in
print("Continue activity \(userActivity)")
print("QR Code URL received from the Camera \(userActivity)")
guard let url = userActivity.webpageURL else {
return
}
print("User wants to open URL: \(url)")
// TODO same handling as done in onOpenURL()
}
.onOpenURL(perform: { (url) in
print("URL OPENED")
print(url)
//we are expecting a .mbtiles map file that contains raster data
//save it to the documents directory, and name it offline_map.mbtiles
let fileManager = FileManager.default

View file

@ -63,7 +63,7 @@ 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.meshActivityLog = UserDefaults.standard.object(forKey: "meshActivityLog") as? Bool ?? false
self.meshActivityLog = UserDefaults.standard.object(forKey: "meshActivityLog") as? Bool ?? true
self.meshMapType = UserDefaults.standard.string(forKey: "meshMapType") ?? "hybrid"
self.meshMapCustomTileServer = UserDefaults.standard.string(forKey: "meshMapCustomTileServer") ?? ""
}

View file

@ -1855,6 +1855,16 @@ struct FromRadio {
set {_uniqueStorage()._payloadVariant = .rebooted(newValue)}
}
///
/// Include module config
var moduleConfig: ModuleConfig {
get {
if case .moduleConfig(let v)? = _storage._payloadVariant {return v}
return ModuleConfig()
}
set {_uniqueStorage()._payloadVariant = .moduleConfig(newValue)}
}
var unknownFields = SwiftProtobuf.UnknownStorage()
///
@ -1889,6 +1899,9 @@ struct FromRadio {
/// Not used on all transports, currently just used for the serial console.
/// NOTE: This ID must not change - to keep (minimal) compatibility with <1.2 version of android apps.
case rebooted(Bool)
///
/// Include module config
case moduleConfig(ModuleConfig)
#if !swift(>=4.1)
static func ==(lhs: FromRadio.OneOf_PayloadVariant, rhs: FromRadio.OneOf_PayloadVariant) -> Bool {
@ -1924,6 +1937,10 @@ struct FromRadio {
guard case .rebooted(let l) = lhs, case .rebooted(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.moduleConfig, .moduleConfig): return {
guard case .moduleConfig(let l) = lhs, case .moduleConfig(let r) = rhs else { preconditionFailure() }
return l == r
}()
default: return false
}
}
@ -3297,6 +3314,7 @@ extension FromRadio: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementation
7: .standard(proto: "log_record"),
8: .standard(proto: "config_complete_id"),
9: .same(proto: "rebooted"),
10: .same(proto: "moduleConfig"),
]
fileprivate class _StorageClass {
@ -3397,6 +3415,19 @@ extension FromRadio: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementation
_storage._payloadVariant = .rebooted(v)
}
}()
case 10: try {
var v: ModuleConfig?
var hadOneofValue = false
if let current = _storage._payloadVariant {
hadOneofValue = true
if case .moduleConfig(let m) = current {v = m}
}
try decoder.decodeSingularMessageField(value: &v)
if let v = v {
if hadOneofValue {try decoder.handleConflictingOneOf()}
_storage._payloadVariant = .moduleConfig(v)
}
}()
case 11: try {
var v: MeshPacket?
var hadOneofValue = false
@ -3450,6 +3481,10 @@ extension FromRadio: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementation
guard case .rebooted(let v)? = _storage._payloadVariant else { preconditionFailure() }
try visitor.visitSingularBoolField(value: v, fieldNumber: 9)
}()
case .moduleConfig?: try {
guard case .moduleConfig(let v)? = _storage._payloadVariant else { preconditionFailure() }
try visitor.visitSingularMessageField(value: v, fieldNumber: 10)
}()
case .packet?: try {
guard case .packet(let v)? = _storage._payloadVariant else { preconditionFailure() }
try visitor.visitSingularMessageField(value: v, fieldNumber: 11)

View file

@ -27,13 +27,6 @@ struct ContentView: View {
}
.tag(Tab.contacts)
// Channels()
// .tabItem {
// Label("Messages", systemImage: "text.bubble")
// .symbolRenderingMode(.hierarchical)
// .symbolVariant(.none)
// }
// .tag(Tab.messages)
Connect()
.tabItem {
Label("Bluetooth", systemImage: "antenna.radiowaves.left.and.right")

View file

@ -157,16 +157,9 @@ struct AppSettings: View {
}
Section(header: Text("DEBUG")) {
Toggle(isOn: $userSettings.meshActivityLog) {
Label("Log all Mesh activity", systemImage: "network")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
if userSettings.meshActivityLog {
NavigationLink(destination: MeshLog()) {
Text("View Mesh Log")
}
.listRowSeparator(.visible)
NavigationLink(destination: MeshLog()) {
Text("View Mesh Log")
}
}
}

View file

@ -56,7 +56,7 @@ enum GpsFormats: Int, CaseIterable, Identifiable {
}
// Default of 0 is One Minute
enum ScreenOnSeconds: Int, CaseIterable, Identifiable {
enum ScreenOnIntervals: Int, CaseIterable, Identifiable {
case fifteenSeconds = 15
case thirtySeconds = 30
@ -90,7 +90,7 @@ enum ScreenOnSeconds: Int, CaseIterable, Identifiable {
}
// Default of 0 is off
enum ScreenCarouselSeconds: Int, CaseIterable, Identifiable {
enum ScreenCarouselIntervals: Int, CaseIterable, Identifiable {
case off = 0
case fifteenSeconds = 15
@ -145,8 +145,8 @@ struct DisplayConfig: View {
Section(header: Text("Timing")) {
Picker("Screen on for", selection: $screenOnSeconds ) {
ForEach(ScreenOnSeconds.allCases) { sos in
Text(sos.description)
ForEach(ScreenOnIntervals.allCases) { soi in
Text(soi.description)
}
}
.pickerStyle(DefaultPickerStyle())
@ -155,8 +155,8 @@ struct DisplayConfig: View {
.font(.caption)
Picker("Carousel Interval", selection: $screenCarouselInterval ) {
ForEach(ScreenCarouselSeconds.allCases) { scs in
Text(scs.description)
ForEach(ScreenCarouselIntervals.allCases) { sci in
Text(sci.description)
}
}
.pickerStyle(DefaultPickerStyle())

View file

@ -6,6 +6,46 @@
//
import SwiftUI
enum OutputIntervals: Int, CaseIterable, Identifiable {
case oneSecond = 0
case twoSeconds = 2000
case threeSeconds = 3000
case fourSeconds = 4000
case fiveSeconds = 5000
case tenSeconds = 10000
case fifteenSeconds = 15000
case thirtySeconds = 30000
case oneMinute = 60000
var id: Int { self.rawValue }
var description: String {
get {
switch self {
case .oneSecond:
return "One Second"
case .twoSeconds:
return "Two Seconds"
case .threeSeconds:
return "Three Seconds"
case .fourSeconds:
return "Four Seconds"
case .fiveSeconds:
return "Five Seconds"
case .tenSeconds:
return "Ten Seconds"
case .fifteenSeconds:
return "Fifteen Seconds"
case .thirtySeconds:
return "Thirty Seconds"
case .oneMinute:
return "One Minute"
}
}
}
}
struct ExternalNotificationConfig: View {
@Environment(\.managedObjectContext) var context
@ -75,6 +115,16 @@ struct ExternalNotificationConfig: View {
Text("Specifies the GPIO that your external circuit is attached to on the device.")
.font(.caption)
.listRowSeparator(.visible)
Picker("GPIO Output Duration", selection: $outputMilliseconds ) {
ForEach(OutputIntervals.allCases) { oi in
Text(oi.description)
}
}
.pickerStyle(DefaultPickerStyle())
Text("Specifies how long the monitored GPIO should output.")
.font(.caption)
.listRowSeparator(.visible)
}
}
.navigationTitle("External Notification Config")

View file

@ -6,6 +6,37 @@
//
import SwiftUI
// Default of 0 is off
enum SenderIntervals: Int, CaseIterable, Identifiable {
case off = 0
case thirtySeconds = 30
case oneMinute = 60
case fiveMinutes = 300
case tenMinutes = 600
case fifteenMinutes = 900
var id: Int { self.rawValue }
var description: String {
get {
switch self {
case .off:
return "Off"
case .thirtySeconds:
return "Thirty Seconds"
case .oneMinute:
return "One Minute"
case .fiveMinutes:
return "Five Minutes"
case .tenMinutes:
return "Ten Minutes"
case .fifteenMinutes:
return "Fifteen Minutes"
}
}
}
}
struct RangeTestConfig: View {
@Environment(\.managedObjectContext) var context
@ -18,7 +49,7 @@ struct RangeTestConfig: View {
@State var hasChanges = false
@State var enabled = false
@State var sender = false
@State var sender = 0
@State var save = false
var body: some View {
@ -35,12 +66,13 @@ struct RangeTestConfig: View {
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
Toggle(isOn: $sender) {
Label("Sender", systemImage: "paperplane")
Picker("Sender Interval", selection: $sender ) {
ForEach(SenderIntervals.allCases) { sci in
Text(sci.description)
}
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
Text("This device will send out range test messages.")
.pickerStyle(DefaultPickerStyle())
Text("This device will send out range test messages on the selected interval.")
.font(.caption)
Toggle(isOn: $save) {
@ -52,8 +84,8 @@ struct RangeTestConfig: View {
Text("Saves a CSV with the range test message details, only available on ESP32 devices with a web server.")
.font(.caption)
}
}
.disabled(!(node.myInfo?.hasWifi ?? false))
Button {
@ -63,7 +95,7 @@ struct RangeTestConfig: View {
Label("Save", systemImage: "square.and.arrow.down")
}
.disabled(bleManager.connectedPeripheral == nil || !hasChanges)
.disabled(bleManager.connectedPeripheral == nil || !hasChanges || !(node.myInfo?.hasWifi ?? false))
.buttonStyle(.bordered)
.buttonBorderShape(.capsule)
.controlSize(.large)
@ -78,7 +110,7 @@ struct RangeTestConfig: View {
var rtc = ModuleConfig.RangeTestConfig()
rtc.enabled = enabled
rtc.save = save
rtc.sender = sender ? 1 : 0
rtc.sender = UInt32(sender)
if bleManager.saveRangeTestModuleConfig(config: rtc, destNum: bleManager.connectedPeripheral.num, wantResponse: false) {
@ -104,34 +136,26 @@ struct RangeTestConfig: View {
if self.initialLoad{
self.bleManager.context = context
// self.enabled = node.rangeTestConfig?.enabled ?? false
// self.save = node.rangeTestConfig?.save ?? false
//
// if node.rangeTestConfig?.sender != nil {
//
// self.sender = node.rangeTestConfig!.sender == 1 ? true : false
//
// } else {
// self.sender = false
// }
// self.sender = node.rangeTestConfig?.sender != nil
self.enabled = node.rangeTestConfig?.enabled ?? false
self.save = node.rangeTestConfig?.save ?? false
self.sender = Int(node.rangeTestConfig?.sender ?? 0)
self.hasChanges = false
self.initialLoad = false
}
}
.onChange(of: enabled) { newEnabled in
//if newEnabled != node.rangeTestConfig!.enabled {
if newEnabled != node.rangeTestConfig!.enabled {
hasChanges = true
//}
}
}
.onChange(of: save) { newSave in
//if newSave != node.rangeTestConfig!.save {
if newSave != node.rangeTestConfig!.save {
hasChanges = true
//}
}
}
.onChange(of: sender) { newSender in

View file

@ -43,12 +43,13 @@ struct Settings: View {
Section("Radio Configuration") {
NavigationLink {
ShareChannel()
ShareChannel(node: nodes.first(where: { $0.num == connectedNodeNum }) ?? NodeInfoEntity())
} label: {
Image(systemName: "qrcode")
.symbolRenderingMode(.hierarchical)
Text("Share Channel QR Code")
}
.disabled(bleManager.connectedPeripheral == nil)
Text("Radio config views will be be enabled when there is a connected node. Save buttons will be enabled when there are config changes to save.")
.font(.caption)
@ -144,8 +145,9 @@ struct Settings: View {
Text("Range Test (ESP32 Only)")
}
.disabled(!(bleManager.connectedPeripheral == nil))
//nodes.first(where: { $0.num == connectedNodeNum })?.myInfo?.hasWifi ?? true) ||
.disabled(true)
//.disabled(bleManager.connectedPeripheral == nil)
//nodes.first(where: { $0.num == connectedNodeNum })?.myInfo?.hasWifi ?? true)//||
// nodes.first(where: { $0.num == connectedNodeNum })!.rangeTestConfig != nil)
NavigationLink {

View file

@ -37,6 +37,8 @@ struct ShareChannel: View {
@EnvironmentObject var bleManager: BLEManager
@EnvironmentObject var userSettings: UserSettings
var node: NodeInfoEntity
@State private var text = "https://meshtastic.org/e/#test"
var qrCodeImage = QrCodeImage()