From 096930efd79e780e04e135f8ad8bb90a6319cdff Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Mon, 1 Aug 2022 21:57:03 -0700 Subject: [PATCH 1/3] Start of wifi config --- Meshtastic.xcodeproj/project.pbxproj | 4 + .../contents | 12 +- Meshtastic/Views/Bluetooth/Connect.swift | 2 +- .../Views/Settings/Config/WiFiConfig.swift | 147 ++++++++++++++++++ Meshtastic/Views/Settings/Settings.swift | 14 +- Meshtastic/Views/Settings/ShareChannel.swift | 1 + 6 files changed, 176 insertions(+), 4 deletions(-) create mode 100644 Meshtastic/Views/Settings/Config/WiFiConfig.swift diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index f8a5d04a..205b9eb3 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -51,6 +51,7 @@ DD86D4112881D16900BAEB7A /* WriteCsvFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD86D4102881D16900BAEB7A /* WriteCsvFile.swift */; }; DD882F5D2772E4640005BF05 /* Contacts.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD882F5C2772E4640005BF05 /* Contacts.swift */; }; DD8EBF43285058FA00426DCA /* DisplayConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8EBF42285058FA00426DCA /* DisplayConfig.swift */; }; + DD8ED9C52898D51F00B3B0AB /* WiFiConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8ED9C42898D51F00B3B0AB /* WiFiConfig.swift */; }; DD90860C26F684AF00DC5189 /* BatteryIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD90860B26F684AF00DC5189 /* BatteryIcon.swift */; }; DD90860E26F69BAE00DC5189 /* NodeMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD90860D26F69BAE00DC5189 /* NodeMap.swift */; }; DD913639270DFF4C00D7ACF3 /* LocalNotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD913638270DFF4C00D7ACF3 /* LocalNotificationManager.swift */; }; @@ -146,6 +147,7 @@ DD882F5C2772E4640005BF05 /* Contacts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Contacts.swift; sourceTree = ""; }; DD8EBF42285058FA00426DCA /* DisplayConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplayConfig.swift; sourceTree = ""; }; DD8ED9C328978D9D00B3B0AB /* MeshtasticDataModel v 5.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModel v 5.xcdatamodel"; sourceTree = ""; }; + DD8ED9C42898D51F00B3B0AB /* WiFiConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WiFiConfig.swift; sourceTree = ""; }; DD90860A26F645B700DC5189 /* Meshtastic.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Meshtastic.entitlements; sourceTree = ""; }; DD90860B26F684AF00DC5189 /* BatteryIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryIcon.swift; sourceTree = ""; }; DD90860D26F69BAE00DC5189 /* NodeMap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeMap.swift; sourceTree = ""; }; @@ -275,6 +277,7 @@ DD8EBF42285058FA00426DCA /* DisplayConfig.swift */, DD2553562855B02500E55709 /* LoRaConfig.swift */, DD2553582855B52700E55709 /* PositionConfig.swift */, + DD8ED9C42898D51F00B3B0AB /* WiFiConfig.swift */, DD61937B2863877A00E59241 /* Module */, ); path = Config; @@ -680,6 +683,7 @@ C9483F6D2773017500998F6B /* MapView.swift in Sources */, DDAF8C5826ED07FD0058C060 /* mesh.pb.swift in Sources */, DD8169FB271F1F3A00F4AB02 /* MeshLog.swift in Sources */, + DD8ED9C52898D51F00B3B0AB /* WiFiConfig.swift in Sources */, DDC3B274283F411B00AC321C /* LastHeardText.swift in Sources */, DD2E65262767A01F00E45FC5 /* NodeDetail.swift in Sources */, DD86D4112881D16900BAEB7A /* WriteCsvFile.swift in Sources */, diff --git a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModel v 5.xcdatamodel/contents b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModel v 5.xcdatamodel/contents index cc2670e5..7acb195b 100644 --- a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModel v 5.xcdatamodel/contents +++ b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModel v 5.xcdatamodel/contents @@ -106,6 +106,7 @@ + @@ -190,6 +191,14 @@ + + + + + + + + @@ -198,7 +207,7 @@ - + @@ -206,5 +215,6 @@ + \ No newline at end of file diff --git a/Meshtastic/Views/Bluetooth/Connect.swift b/Meshtastic/Views/Bluetooth/Connect.swift index b8e59b63..8672b5d7 100644 --- a/Meshtastic/Views/Bluetooth/Connect.swift +++ b/Meshtastic/Views/Bluetooth/Connect.swift @@ -22,7 +22,7 @@ struct Connect: View { @State var isPreferredRadio: Bool = false @State var firmwareVersion = "0.0.0" - @State var minimumVersion = "1.3.27" + @State var minimumVersion = "1.3.28" @State var invalidVersion = false diff --git a/Meshtastic/Views/Settings/Config/WiFiConfig.swift b/Meshtastic/Views/Settings/Config/WiFiConfig.swift new file mode 100644 index 00000000..92aa068d --- /dev/null +++ b/Meshtastic/Views/Settings/Config/WiFiConfig.swift @@ -0,0 +1,147 @@ +// +// WiFiConfig.swift +// Meshtastic +// +// Copyright (c) Garth Vander Houwen 8/1/2022 +// + +import SwiftUI + +struct WiFiConfig: View { + + @Environment(\.managedObjectContext) var context + @EnvironmentObject var bleManager: BLEManager + + var node: NodeInfoEntity? + + @State private var isPresentingSaveConfirm: Bool = false + @State var initialLoad: Bool = true + @State var hasChanges: Bool = false + + @State var ssid = "" + @State var password = "" + + @State var apMode = false + @State var apHidden = false + + var body: some View { + + VStack { + + Text("Enabling WiFi will disable bluetooth, only one connection method works at a time. Saving these settings will disconnect your device from the app.") + .font(.title3) + .padding() + + Form { + + Section(header: Text("SSID & Password")) { + + + } + Section(header: Text("AP Settings")) { + + Toggle(isOn: $apMode) { + + Label("Soft AP Mode", systemImage: "wifi") + } + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + Text("If set the software access point mode will be activated.") + .font(.caption) + + Toggle(isOn: $apHidden) { + + Label("Hidden AP", systemImage: "eye.slash") + } + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + Text("If set the SSID for the AP will be hidden.") + .font(.caption) + + } + } + .disabled(bleManager.connectedPeripheral == nil) + + Button { + + isPresentingSaveConfirm = true + + } label: { + + Label("Save", systemImage: "square.and.arrow.down") + } + .disabled(bleManager.connectedPeripheral == nil || !hasChanges) + .buttonStyle(.bordered) + .buttonBorderShape(.capsule) + .controlSize(.large) + .padding() + .confirmationDialog( + + "Are you sure?", + isPresented: $isPresentingSaveConfirm + ) { + Button("Save WiFI Config to \(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown")?") { + + var wifi = Config.WiFiConfig() + wifi.ssid = ssid + wifi.psk = password + wifi.apMode = apMode + wifi.apHidden = apHidden + + //let adminMessageId = bleManager.saveWiFiConfig(config: wiFi, fromUser: node!.user!, toUser: node!.user!, wantResponse: true) + let adminMessageId = 0 + if adminMessageId > 0 { + + // Should show a saved successfully alert once I know that to be true + // for now just disable the button after a successful save + hasChanges = false + + } else { + + } + } + } + } + .navigationTitle("WiFi Config") + .navigationBarItems(trailing: + + ZStack { + + ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????") + }) + .onAppear { + + if self.initialLoad{ + + self.bleManager.context = context + + self.ssid = node!.wiFiConfig?.ssid ?? "" + self.password = node!.wiFiConfig?.password ?? "" + self.apMode = (node!.wiFiConfig?.apMode ?? false) + self.apHidden = (node!.wiFiConfig?.apHidden ?? false) + + self.hasChanges = false + self.initialLoad = false + } + } + .onChange(of: ssid) { newSsid in + + hasChanges = true + //if node != nil && node!.wiFiConfig != nil { newSsid != node!.wiFiConfig.ssid { hasChanges = true }} + } + .onChange(of: password) { newPassword in + + hasChanges = true + //if node != nil && node!.wiFiConfig != nil { newPassword != node!.wiFiConfig!.password { hasChanges = true }} + } + .onChange(of: apMode) { newAPMode in + + hasChanges = true + //if node != nil && node!.wiFiConfig != nil { newAPMode != node!.wiFiConfig!.apMode { self.hasChanges = true }} + } + .onChange(of: apHidden) { newAPHidden in + + hasChanges = true + //if node != nil && node!.wiFiConfig != nil { newAPHidden != node!.wiFiConfig!.apHidden { hasChanges = true }} + } + .navigationViewStyle(StackNavigationViewStyle()) + } +} diff --git a/Meshtastic/Views/Settings/Settings.swift b/Meshtastic/Views/Settings/Settings.swift index 8eb6180c..d656b809 100644 --- a/Meshtastic/Views/Settings/Settings.swift +++ b/Meshtastic/Views/Settings/Settings.swift @@ -100,6 +100,17 @@ struct Settings: View { } .disabled(bleManager.connectedPeripheral == nil) + NavigationLink { + WiFiConfig(node: nodes.first(where: { $0.num == connectedNodeNum })) + } label: { + + Image(systemName: "wifi") + .symbolRenderingMode(.hierarchical) + + Text("WiFi") + } + .disabled(bleManager.connectedPeripheral == nil) + Text("Default settings values are prefered as they consume no bandwidth when sent over the mesh.") .font(.caption2) .fixedSize(horizontal: false, vertical: true) @@ -193,8 +204,7 @@ struct Settings: View { // Not Implemented: // Store Forward Config - Not Working, TBEAM Only - // WiFi Config - Would break connection to device - // MQTT Config - Part of WiFi + // MQTT Config - Can do from WebUI once WiFi is enabled } .onAppear { diff --git a/Meshtastic/Views/Settings/ShareChannel.swift b/Meshtastic/Views/Settings/ShareChannel.swift index 0615b803..d2eb08e0 100644 --- a/Meshtastic/Views/Settings/ShareChannel.swift +++ b/Meshtastic/Views/Settings/ShareChannel.swift @@ -70,6 +70,7 @@ struct ShareChannel: View { ) Spacer() Text("Channel Name (Long/Slow)").font(.title) + Text(String(node!.myInfo!.maxChannels)) Spacer() } .frame(width: bounds.size.width, height: bounds.size.height) From 045fb598d532a349f78a3a1e66a138c18cdccdb0 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 2 Aug 2022 09:44:59 -0700 Subject: [PATCH 2/3] Finish wifi config --- Meshtastic/Helpers/BLEManager.swift | 32 ++++++- Meshtastic/Helpers/MeshPackets.swift | 88 +++++++++++++++++- .../Views/Settings/Config/WiFiConfig.swift | 91 +++++++++++++++---- Meshtastic/Views/Settings/UserConfig.swift | 21 +++-- 4 files changed, 202 insertions(+), 30 deletions(-) diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 8bc54686..0e093920 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -1112,6 +1112,35 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph return 0 } + public func saveWiFiConfig(config: Config.WiFiConfig, fromUser: UserEntity, toUser: UserEntity, wantResponse: Bool) -> Int64 { + + var adminPacket = AdminMessage() + adminPacket.setConfig.wifi = config + + var meshPacket: MeshPacket = MeshPacket() + meshPacket.to = UInt32(connectedPeripheral.num) + meshPacket.from = 0 //UInt32(connectedPeripheral.num) + meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Int64 { var adminPacket = AdminMessage() @@ -1174,14 +1203,13 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph var adminPacket = AdminMessage() adminPacket.getCannedMessageModulePart1Request = true - //adminPacket.getOwnerRequest = true - var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(connectedPeripheral.num) meshPacket.from = 0 //UInt32(connectedPeripheral.num) meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. = NSFetchRequest.init(entityName: "NodeInfoEntity") @@ -334,6 +339,87 @@ func localConfig (config: Config, meshlogging: Bool, context:NSManagedObjectCont } } + + if config.payloadVariant == Config.OneOf_PayloadVariant.wifi(config.wifi) { + + var isDefault = false + + if (try! config.wifi.jsonString()) == "{}" { + + isDefault = true + if meshlogging { MeshLogger.log("📶 Default WiFi config received \(String(nodeNum))") } + + } else { + + if meshlogging { MeshLogger.log("📶 Custom WiFi config received \(String(nodeNum))") } + } + + let fetchNodeInfoRequest: NSFetchRequest = 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 WiFi Config + if !fetchedNode.isEmpty { + + if fetchedNode[0].wiFiConfig == nil { + + let newWiFiConfig = WiFiConfigEntity(context: context) + + if isDefault { + + newWiFiConfig.ssid = "" + newWiFiConfig.password = "" + newWiFiConfig.apMode = false + newWiFiConfig.apHidden = false + + } else { + + newWiFiConfig.ssid = config.wifi.ssid + newWiFiConfig.password = config.wifi.psk + newWiFiConfig.apMode = config.wifi.apMode + newWiFiConfig.apHidden = config.wifi.apHidden + } + newWiFiConfig.num = fetchedNode[0].num + fetchedNode[0].wiFiConfig = newWiFiConfig + + } else { + + if isDefault { + + fetchedNode[0].wiFiConfig?.ssid = "" + fetchedNode[0].wiFiConfig?.password = "" + fetchedNode[0].wiFiConfig?.apMode = false + fetchedNode[0].wiFiConfig?.apHidden = false + + } else { + + fetchedNode[0].wiFiConfig?.ssid = config.wifi.ssid + fetchedNode[0].wiFiConfig?.password = config.wifi.psk + fetchedNode[0].wiFiConfig?.apMode = config.wifi.apMode + fetchedNode[0].wiFiConfig?.apHidden = config.wifi.apHidden + } + } + + do { + + try context.save() + if meshlogging { MeshLogger.log("💾 Updated WiFi Config for node number: \(String(nodeNum))") } + + } catch { + + context.rollback() + + let nsError = error as NSError + print("💥 Error Updating Core Data WifionfigEntity: \(nsError)") + } + } + + } catch { + + } + } } func moduleConfig (config: ModuleConfig, meshlogging: Bool, context:NSManagedObjectContext, nodeNum: Int64, nodeLongName: String) { @@ -425,7 +511,7 @@ func moduleConfig (config: ModuleConfig, meshlogging: Bool, context:NSManagedObj try context.save() if meshlogging { MeshLogger.log("💾 Updated Canned Message Module Config for node number: \(String(nodeNum))") } - print(try config.cannedMessage.jsonString()) + } catch { diff --git a/Meshtastic/Views/Settings/Config/WiFiConfig.swift b/Meshtastic/Views/Settings/Config/WiFiConfig.swift index 92aa068d..f4c1d7d7 100644 --- a/Meshtastic/Views/Settings/Config/WiFiConfig.swift +++ b/Meshtastic/Views/Settings/Config/WiFiConfig.swift @@ -36,6 +36,55 @@ struct WiFiConfig: View { Section(header: Text("SSID & Password")) { + HStack { + Label("SSID", systemImage: "wifi") + TextField("SSID", text: $ssid) + .foregroundColor(.gray) + .onChange(of: ssid, perform: { value in + + let totalBytes = ssid.utf8.count + + // Only mess with the value if it is too big + if totalBytes > 30 { + + let firstNBytes = Data(ssid.utf8.prefix(30)) + + if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) { + + // Set the shortName back to the last place where it was the right size + ssid = maxBytesString + } + } + }) + .foregroundColor(.gray) + } + .keyboardType(.default) + .disableAutocorrection(true) + + HStack { + Label("Password", systemImage: "wallet.pass") + TextField("Password", text: $password) + .foregroundColor(.gray) + .onChange(of: password, perform: { value in + + let totalBytes = password.utf8.count + + // Only mess with the value if it is too big + if totalBytes > 60 { + + let firstNBytes = Data(ssid.utf8.prefix(60)) + + if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) { + + // Set the shortName back to the last place where it was the right size + ssid = maxBytesString + } + } + }) + .foregroundColor(.gray) + } + .keyboardType(.default) + .disableAutocorrection(true) } Section(header: Text("AP Settings")) { @@ -81,18 +130,18 @@ struct WiFiConfig: View { Button("Save WiFI Config to \(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown")?") { var wifi = Config.WiFiConfig() - wifi.ssid = ssid - wifi.psk = password - wifi.apMode = apMode - wifi.apHidden = apHidden + wifi.ssid = self.ssid + wifi.psk = self.password + wifi.apMode = self.apMode + wifi.apHidden = self.apHidden + + let adminMessageId = bleManager.saveWiFiConfig(config: wifi, fromUser: node!.user!, toUser: node!.user!, wantResponse: true) - //let adminMessageId = bleManager.saveWiFiConfig(config: wiFi, fromUser: node!.user!, toUser: node!.user!, wantResponse: true) - let adminMessageId = 0 if adminMessageId > 0 { // Should show a saved successfully alert once I know that to be true // for now just disable the button after a successful save - hasChanges = false + self.hasChanges = false } else { @@ -124,23 +173,31 @@ struct WiFiConfig: View { } .onChange(of: ssid) { newSsid in - hasChanges = true - //if node != nil && node!.wiFiConfig != nil { newSsid != node!.wiFiConfig.ssid { hasChanges = true }} + if node != nil && node!.wiFiConfig != nil { + + if newSsid != node!.wiFiConfig!.ssid { hasChanges = true } + } } .onChange(of: password) { newPassword in - hasChanges = true - //if node != nil && node!.wiFiConfig != nil { newPassword != node!.wiFiConfig!.password { hasChanges = true }} + if node != nil && node!.wiFiConfig != nil { + + if newPassword != node!.wiFiConfig!.password { hasChanges = true } + } } - .onChange(of: apMode) { newAPMode in + .onChange(of: apMode) { newApMode in - hasChanges = true - //if node != nil && node!.wiFiConfig != nil { newAPMode != node!.wiFiConfig!.apMode { self.hasChanges = true }} + if node != nil && node!.wiFiConfig != nil { + + if newApMode != node!.wiFiConfig!.apMode { hasChanges = true } + } } - .onChange(of: apHidden) { newAPHidden in + .onChange(of: apHidden) { newApHidden in - hasChanges = true - //if node != nil && node!.wiFiConfig != nil { newAPHidden != node!.wiFiConfig!.apHidden { hasChanges = true }} + if node != nil && node!.wiFiConfig != nil { + + if newApHidden != node!.wiFiConfig!.apHidden { hasChanges = true } + } } .navigationViewStyle(StackNavigationViewStyle()) } diff --git a/Meshtastic/Views/Settings/UserConfig.swift b/Meshtastic/Views/Settings/UserConfig.swift index eedb3504..6d9d304d 100644 --- a/Meshtastic/Views/Settings/UserConfig.swift +++ b/Meshtastic/Views/Settings/UserConfig.swift @@ -48,6 +48,17 @@ struct UserConfig: View { } } }) + + } + .keyboardType(.default) + .disableAutocorrection(true) + Text("Long name can be up to 36 bytes long.") + .font(.caption) + + HStack { + Label("Short Name", systemImage: "circlebadge.fill") + TextField("Long Name", text: $shortName) + .foregroundColor(.gray) .onChange(of: shortName, perform: { value in let totalBytes = shortName.utf8.count @@ -66,16 +77,6 @@ struct UserConfig: View { }) .foregroundColor(.gray) } - .keyboardType(.default) - .disableAutocorrection(true) - Text("Long name can be up to 36 bytes long.") - .font(.caption) - - HStack { - Label("Short Name", systemImage: "circlebadge.fill") - TextField("Long Name", text: $shortName) - .foregroundColor(.gray) - } .keyboardType(.asciiCapable) .disableAutocorrection(true) Text("The short name is used in maps and messaging and will be appended to the last 4 of the device MAC address to set the device's BLE Name. It can be up to 4 bytes long.") From 2756db10ec3affca58372caaa95275add9ce28ca Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 2 Aug 2022 09:49:11 -0700 Subject: [PATCH 3/3] Restrict wifi to devices with wifi --- Meshtastic/Views/Settings/Config/WiFiConfig.swift | 2 +- Meshtastic/Views/Settings/Settings.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Meshtastic/Views/Settings/Config/WiFiConfig.swift b/Meshtastic/Views/Settings/Config/WiFiConfig.swift index f4c1d7d7..246c2e0b 100644 --- a/Meshtastic/Views/Settings/Config/WiFiConfig.swift +++ b/Meshtastic/Views/Settings/Config/WiFiConfig.swift @@ -107,7 +107,7 @@ struct WiFiConfig: View { } } - .disabled(bleManager.connectedPeripheral == nil) + .disabled(!(node != nil && node!.myInfo?.hasWifi ?? false)) Button { diff --git a/Meshtastic/Views/Settings/Settings.swift b/Meshtastic/Views/Settings/Settings.swift index d656b809..e1887074 100644 --- a/Meshtastic/Views/Settings/Settings.swift +++ b/Meshtastic/Views/Settings/Settings.swift @@ -107,7 +107,7 @@ struct Settings: View { Image(systemName: "wifi") .symbolRenderingMode(.hierarchical) - Text("WiFi") + Text("WiFi (ESP32 Only)") } .disabled(bleManager.connectedPeripheral == nil)