// // MQTT.swift // Meshtastic // // Copyright (c) Garth Vander Houwen 9/4/22. // import SwiftUI struct MQTTConfig: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager @Environment(\.dismiss) private var goBack var node: NodeInfoEntity? @State private var isPresentingSaveConfirm: Bool = false @State var hasChanges: Bool = false @State var enabled = false @State var proxyToClientEnabled = false @State var address = "" @State var username = "" @State var password = "" @State var encryptionEnabled = true @State var jsonEnabled = false @State var tlsEnabled = true @State var root = "msh" @State var mqttConnected: Bool = false var body: some View { VStack { Form { if node != nil && node?.loRaConfig != nil { let rc = RegionCodes(rawValue: Int(node?.loRaConfig?.regionCode ?? 0)) if rc?.dutyCycle ?? 0 <= 10 { Text("Your region has a \(rc?.dutyCycle ?? 0)% duty cycle. MQTT is not advised when you are duty cycle restricted, the extra traffic will quickly overwhelm your LoRa mesh.") .font(.callout) .foregroundColor(.red) } } ConfigHeader(title: "MQTT", config: \.mqttConfig, node: node, onAppear: setMqttValues) Section(header: Text("options")) { Toggle(isOn: $enabled) { Label("enabled", systemImage: "dot.radiowaves.right") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) Toggle(isOn: $proxyToClientEnabled) { Label("mqtt.clientproxy", systemImage: "iphone.radiowaves.left.and.right") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) if enabled && proxyToClientEnabled { Toggle(isOn: $mqttConnected) { Label(mqttConnected ? "mqtt.disconnect".localized : "mqtt.connect".localized, systemImage: "server.rack") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) } Text("If both MQTT and the client proxy are enabled your mobile device will utilize an available network connection to connect to the specified MQTT server.") .font(.caption2) Toggle(isOn: $encryptionEnabled) { Label("Encryption Enabled", systemImage: "lock.icloud") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) Toggle(isOn: $jsonEnabled) { Label("JSON Enabled", systemImage: "ellipsis.curlybraces") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) Text("JSON mode is a limited, unencrypted MQTT output that can crash your node it should not be enabled unless you are locally integrating with home assistant") .font(.caption2) Toggle(isOn: $tlsEnabled) { Label("TLS Enabled", systemImage: "checkmark.shield.fill") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) Text("Your MQTT Server must support TLS.") .font(.caption2) } Section(header: Text("Custom Server")) { HStack { Label("Address", systemImage: "server.rack") TextField("Server Address", text: $address) .foregroundColor(.gray) .autocapitalization(.none) .disableAutocorrection(true) .onChange(of: address, perform: { _ in let totalBytes = address.utf8.count // Only mess with the value if it is too big if totalBytes > 62 { let firstNBytes = Data(username.utf8.prefix(62)) if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) { // Set the shortName back to the last place where it was the right size address = maxBytesString } } hasChanges = true }) .foregroundColor(.gray) .keyboardType(.default) } .autocorrectionDisabled() HStack { Label("mqtt.username", systemImage: "person.text.rectangle") TextField("mqtt.username", text: $username) .foregroundColor(.gray) .autocapitalization(.none) .disableAutocorrection(true) .onChange(of: username, perform: { _ in let totalBytes = username.utf8.count // Only mess with the value if it is too big if totalBytes > 62 { let firstNBytes = Data(username.utf8.prefix(62)) if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) { // Set the shortName back to the last place where it was the right size username = maxBytesString } } hasChanges = true }) .foregroundColor(.gray) } .keyboardType(.default) .scrollDismissesKeyboard(.interactively) HStack { Label("password", systemImage: "wallet.pass") TextField("password", text: $password) .foregroundColor(.gray) .autocapitalization(.none) .disableAutocorrection(true) .onChange(of: password, perform: { _ in let totalBytes = password.utf8.count // Only mess with the value if it is too big if totalBytes > 62 { let firstNBytes = Data(password.utf8.prefix(62)) if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) { // Set the shortName back to the last place where it was the right size password = maxBytesString } } hasChanges = true }) .foregroundColor(.gray) } .keyboardType(.default) .scrollDismissesKeyboard(.interactively) HStack { Label("Root Topic", systemImage: "tree") TextField("Root Topic", text: $root) .foregroundColor(.gray) .onChange(of: root, perform: { _ in let totalBytes = root.utf8.count // Only mess with the value if it is too big if totalBytes > 14 { let firstNBytes = Data(root.utf8.prefix(14)) if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) { // Set the shortName back to the last place where it was the right size root = maxBytesString } } }) .foregroundColor(.gray) } .keyboardType(.asciiCapable) .scrollDismissesKeyboard(.interactively) .disableAutocorrection(true) Text("The root topic to use for MQTT messages. Default is \"msh\". This is useful if you want to use a single MQTT server for multiple meshtastic networks and separate them via ACLs") .font(.caption2) } Text("You can set uplink and downlink for each channel.") .font(.callout) } .scrollDismissesKeyboard(.interactively) .disabled(self.bleManager.connectedPeripheral == nil || node?.mqttConfig == 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, titleVisibility: .visible ) { let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1, context: context) if connectedNode != nil { let nodeName = node?.user?.longName ?? "unknown".localized let buttonText = String.localizedStringWithFormat("save.config %@".localized, nodeName) Button(buttonText) { var mqtt = ModuleConfig.MQTTConfig() mqtt.enabled = self.enabled mqtt.proxyToClientEnabled = self.proxyToClientEnabled mqtt.address = self.address mqtt.username = self.username mqtt.password = self.password mqtt.root = self.root mqtt.encryptionEnabled = self.encryptionEnabled mqtt.jsonEnabled = self.jsonEnabled mqtt.tlsEnabled = self.tlsEnabled let adminMessageId = bleManager.saveMQTTConfig(config: mqtt, fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 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 goBack() } } } } message: { Text("config.save.confirm") } .navigationTitle("mqtt.config") .navigationBarItems(trailing: ZStack { ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?", mqttProxyConnected: bleManager.mqttProxyConnected) }) .onAppear { if self.bleManager.context == nil { self.bleManager.context = context } setMqttValues() // Need to request a TelemetryModuleConfig from the remote node before allowing changes if bleManager.connectedPeripheral != nil && node?.mqttConfig == nil { print("empty mqtt module config") let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context) if node != nil && connectedNode != nil { _ = bleManager.requestMqttModuleConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) } } } .onChange(of: address) { newAddress in if node != nil && node?.mqttConfig != nil { if newAddress != node!.mqttConfig!.address { hasChanges = true } } } .onChange(of: username) { newUsername in if node != nil && node?.mqttConfig != nil { if newUsername != node!.mqttConfig!.username { hasChanges = true } } } .onChange(of: password) { newPassword in if node != nil && node?.mqttConfig != nil { if newPassword != node!.mqttConfig!.password { hasChanges = true } } } .onChange(of: root) { newRoot in if node != nil && node?.mqttConfig != nil { if newRoot != node!.mqttConfig!.root { hasChanges = true } } } .onChange(of: enabled) { newEnabled in if node != nil && node?.mqttConfig != nil { if newEnabled != node!.mqttConfig!.enabled { hasChanges = true } } } .onChange(of: proxyToClientEnabled) { newProxyToClientEnabled in if node != nil && node?.mqttConfig != nil { if newProxyToClientEnabled != node!.mqttConfig!.proxyToClientEnabled { hasChanges = true } if newProxyToClientEnabled { jsonEnabled = false } } } .onChange(of: encryptionEnabled) { newEncryptionEnabled in if node != nil && node?.mqttConfig != nil { if newEncryptionEnabled != node!.mqttConfig!.encryptionEnabled { hasChanges = true } } } .onChange(of: jsonEnabled) { newJsonEnabled in if node != nil && node?.mqttConfig != nil { if newJsonEnabled != node!.mqttConfig!.jsonEnabled { hasChanges = true } } } .onChange(of: tlsEnabled) { newTlsEnabled in if node != nil && node?.mqttConfig != nil { if newTlsEnabled != node!.mqttConfig!.tlsEnabled { hasChanges = true } } } .onChange(of: mqttConnected) { newMqttConnected in if newMqttConnected == false { if bleManager.mqttProxyConnected { bleManager.mqttManager.disconnect() } } else { if !bleManager.mqttProxyConnected && node != nil { bleManager.mqttManager.connectFromConfigSettings(node: node!) } } } } func setMqttValues() { self.enabled = (node?.mqttConfig?.enabled ?? false) self.proxyToClientEnabled = (node?.mqttConfig?.proxyToClientEnabled ?? false) self.address = node?.mqttConfig?.address ?? "" self.username = node?.mqttConfig?.username ?? "" self.password = node?.mqttConfig?.password ?? "" self.root = node?.mqttConfig?.root ?? "msh" self.encryptionEnabled = (node?.mqttConfig?.encryptionEnabled ?? false) self.jsonEnabled = (node?.mqttConfig?.jsonEnabled ?? false) self.tlsEnabled = (node?.mqttConfig?.tlsEnabled ?? false) self.mqttConnected = bleManager.mqttProxyConnected self.hasChanges = false } }