Merge pull request #132 from meshtastic/add_wifi_config

Add wifi config
This commit is contained in:
Garth Vander Houwen 2022-08-02 09:50:35 -07:00 committed by GitHub
commit 8a4c519ddb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 361 additions and 17 deletions

View file

@ -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 = "<group>"; };
DD8EBF42285058FA00426DCA /* DisplayConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplayConfig.swift; sourceTree = "<group>"; };
DD8ED9C328978D9D00B3B0AB /* MeshtasticDataModel v 5.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModel v 5.xcdatamodel"; sourceTree = "<group>"; };
DD8ED9C42898D51F00B3B0AB /* WiFiConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WiFiConfig.swift; sourceTree = "<group>"; };
DD90860A26F645B700DC5189 /* Meshtastic.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Meshtastic.entitlements; sourceTree = "<group>"; };
DD90860B26F684AF00DC5189 /* BatteryIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryIcon.swift; sourceTree = "<group>"; };
DD90860D26F69BAE00DC5189 /* NodeMap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeMap.swift; sourceTree = "<group>"; };
@ -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 */,

View file

@ -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)..<UInt32.max)
meshPacket.priority = MeshPacket.Priority.reliable
meshPacket.wantAck = wantResponse
meshPacket.hopLimit = 0
var dataMessage = DataMessage()
dataMessage.payload = try! adminPacket.serializedData()
dataMessage.portnum = PortNum.adminApp
meshPacket.decoded = dataMessage
let messageDescription = "Saved WiFi Config for \(toUser.longName ?? "Unknown")"
if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) {
return Int64(meshPacket.id)
}
return 0
}
public func saveCannedMessageModuleConfig(config: ModuleConfig.CannedMessageConfig, fromUser: UserEntity, toUser: UserEntity, wantResponse: Bool) -> 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)..<UInt32.max)
meshPacket.priority = MeshPacket.Priority.reliable
meshPacket.wantAck = wantResponse
meshPacket.decoded.wantResponse = wantResponse
var dataMessage = DataMessage()
dataMessage.payload = try! adminPacket.serializedData()

View file

@ -257,6 +257,11 @@ func localConfig (config: Config, meshlogging: Bool, context:NSManagedObjectCont
if (try! config.position.jsonString()) == "{}" {
isDefault = true
if meshlogging { MeshLogger.log("🗺️ Default Position config received \(String(nodeNum))") }
} else {
if meshlogging { MeshLogger.log("🗺️ Custom Position config received \(String(nodeNum))") }
}
let fetchNodeInfoRequest: NSFetchRequest<NSFetchRequestResult> = 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<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 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 {

View file

@ -106,6 +106,7 @@
<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"/>
<relationship name="wiFiConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="WiFiConfigEntity" inverseName="wiFiConfigNode" inverseEntity="WiFiConfigEntity"/>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="num"/>
@ -190,6 +191,14 @@
<fetchRequest name="fetchedPropertyFetchRequest" entity="MessageEntity" predicateString="((toUser.num == $FETCH_SOURCE.num) OR (fromUser.num == $FETCH_SOURCE.num)) AND isEmoji == false AND admin = false"/>
</fetchedProperty>
</entity>
<entity name="WiFiConfigEntity" representedClassName="WiFiConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="apHidden" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="apMode" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="num" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="password" optional="YES" attributeType="String" minValueString="1" maxValueString="60"/>
<attribute name="ssid" optional="YES" attributeType="String" minValueString="1" maxValueString="30"/>
<relationship name="wiFiConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="wiFiConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<elements>
<element name="CannedMessageConfigEntity" positionX="45" positionY="144" width="128" height="209"/>
<element name="DeviceConfigEntity" positionX="45" positionY="144" width="128" height="104"/>
@ -198,7 +207,7 @@
<element name="LoRaConfigEntity" positionX="45" positionY="144" width="128" height="119"/>
<element name="MessageEntity" positionX="-36" positionY="63" width="128" height="230"/>
<element name="MyInfoEntity" positionX="-18" positionY="81" width="128" height="209"/>
<element name="NodeInfoEntity" positionX="-63" positionY="-18" width="128" height="299"/>
<element name="NodeInfoEntity" positionX="-63" positionY="-18" width="128" height="314"/>
<element name="PositionConfigEntity" positionX="63" positionY="162" width="128" height="149"/>
<element name="PositionEntity" positionX="-54" positionY="54" width="128" height="119"/>
<element name="RangeTestConfigEntity" positionX="72" positionY="171" width="128" height="104"/>
@ -206,5 +215,6 @@
<element name="TelemetryConfigEntity" positionX="72" positionY="171" width="128" height="134"/>
<element name="TelemetryEntity" positionX="160" positionY="192" width="128" height="209"/>
<element name="UserEntity" positionX="0" positionY="144" width="128" height="230"/>
<element name="WiFiConfigEntity" positionX="45" positionY="144" width="128" height="119"/>
</elements>
</model>

View file

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

View file

@ -0,0 +1,204 @@
//
// 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")) {
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")) {
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(!(node != nil && node!.myInfo?.hasWifi ?? false))
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 = 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)
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
self.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
if node != nil && node!.wiFiConfig != nil {
if newSsid != node!.wiFiConfig!.ssid { hasChanges = true }
}
}
.onChange(of: password) { newPassword in
if node != nil && node!.wiFiConfig != nil {
if newPassword != node!.wiFiConfig!.password { hasChanges = true }
}
}
.onChange(of: apMode) { newApMode in
if node != nil && node!.wiFiConfig != nil {
if newApMode != node!.wiFiConfig!.apMode { hasChanges = true }
}
}
.onChange(of: apHidden) { newApHidden in
if node != nil && node!.wiFiConfig != nil {
if newApHidden != node!.wiFiConfig!.apHidden { hasChanges = true }
}
}
.navigationViewStyle(StackNavigationViewStyle())
}
}

View file

@ -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 (ESP32 Only)")
}
.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 {

View file

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

View file

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