diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 83833136..b54367e9 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -115,6 +115,7 @@ DDC2E18F26CE25FE0042C5E4 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC2E18E26CE25FE0042C5E4 /* ContentView.swift */; }; DDC2E1A726CEB3400042C5E4 /* LocationHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC2E1A626CEB3400042C5E4 /* LocationHelper.swift */; }; DDC3B274283F411B00AC321C /* LastHeardText.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC3B273283F411B00AC321C /* LastHeardText.swift */; }; + DDC4C9FF2A8D982900CE201C /* DetectionSensorConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC4C9FE2A8D982900CE201C /* DetectionSensorConfig.swift */; }; DDC4D568275499A500A4208E /* Persistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC4D567275499A500A4208E /* Persistence.swift */; }; DDC94FC129CE063B0082EA6E /* BatteryLevel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC94FC029CE063B0082EA6E /* BatteryLevel.swift */; }; DDC94FC229CE063B0082EA6E /* BatteryLevel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC94FC029CE063B0082EA6E /* BatteryLevel.swift */; }; @@ -318,6 +319,8 @@ DDC2E18E26CE25FE0042C5E4 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; DDC2E1A626CEB3400042C5E4 /* LocationHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationHelper.swift; sourceTree = ""; }; DDC3B273283F411B00AC321C /* LastHeardText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LastHeardText.swift; sourceTree = ""; }; + DDC4C9FE2A8D982900CE201C /* DetectionSensorConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetectionSensorConfig.swift; sourceTree = ""; }; + DDC4CA012A8DAA3800CE201C /* MeshtasticDataModelV16.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV16.xcdatamodel; sourceTree = ""; }; DDC4D567275499A500A4208E /* Persistence.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Persistence.swift; sourceTree = ""; }; DDC94FC029CE063B0082EA6E /* BatteryLevel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryLevel.swift; sourceTree = ""; }; DDC94FC329CED7280082EA6E /* MeshtasticDataModelV10.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV10.xcdatamodel; sourceTree = ""; }; @@ -517,6 +520,7 @@ DDC94FCD29CF55310082EA6E /* RtttlConfig.swift */, DD6193782863875F00E59241 /* SerialConfig.swift */, DD415827285859C4009B0E59 /* TelemetryConfig.swift */, + DDC4C9FE2A8D982900CE201C /* DetectionSensorConfig.swift */, ); path = Module; sourceTree = ""; @@ -1109,6 +1113,7 @@ DDDB444E29F8AB0E00EE2349 /* Int.swift in Sources */, DD0F791B28713C8A00A6FDAD /* AdminMessageList.swift in Sources */, DD3CC6BC28E366DF00FA9159 /* Meshtastic.xcdatamodeld in Sources */, + DDC4C9FF2A8D982900CE201C /* DetectionSensorConfig.swift in Sources */, DD5E5210298EE33B00D21B61 /* telemetry.pb.swift in Sources */, DD5E5205298EE33B00D21B61 /* mesh.pb.swift in Sources */, DD8169F9271F1A6100F4AB02 /* MeshLogger.swift in Sources */, @@ -1622,6 +1627,7 @@ DD3CC6BA28E366DF00FA9159 /* Meshtastic.xcdatamodeld */ = { isa = XCVersionGroup; children = ( + DDC4CA012A8DAA3800CE201C /* MeshtasticDataModelV16.xcdatamodel */, DD14E72C2A80738F006E39BC /* MeshtasticDataModelV15.xcdatamodel */, DD0E9C222A30CE3A00580CBB /* MeshtasticDataModelV14.xcdatamodel */, DDB75A1F2A10766D006ED576 /* MeshtasticDataModelV13.xcdatamodel */, @@ -1638,7 +1644,7 @@ DD5D0A9A2931AD6B00F7EA61 /* MeshtasticDataModelV2.xcdatamodel */, DD3CC6BB28E366DF00FA9159 /* MeshtasticDataModel.xcdatamodel */, ); - currentVersion = DD14E72C2A80738F006E39BC /* MeshtasticDataModelV15.xcdatamodel */; + currentVersion = DDC4CA012A8DAA3800CE201C /* MeshtasticDataModelV16.xcdatamodel */; name = Meshtastic.xcdatamodeld; path = Meshtastic/Meshtastic.xcdatamodeld; sourceTree = ""; diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 4a510d41..05747bb2 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -10,48 +10,6 @@ import CocoaMQTT // --------------------------------------------------------------------------------------- class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate, ObservableObject { - // MqttClientProxyManagerDelegate - func onMqttConnected() { - //mqttManager.status = .connected - mqttProxyConnected = true - print("📲 Mqtt Client Proxy onMqttConnected now subscribing to \(mqttManager.topic).") - mqttManager.mqttClientProxy?.subscribe(mqttManager.topic) - } - - func onMqttDisconnected() { - // mqttManager.status = .disconnected - mqttProxyConnected = false - print("MQTT Disconnected") - } - - func onMqttMessageReceived(message: CocoaMQTTMessage) { - - print("📲 Mqtt Client Proxy onMqttMessageReceived for topic: \(message.topic)") - if message.topic.contains("/stat/") { - return - } - var proxyMessage = MqttClientProxyMessage() - proxyMessage.topic = message.topic - proxyMessage.data = Data(message.payload) - proxyMessage.retained = message.retained - - var toRadio: ToRadio! - toRadio = ToRadio() - toRadio.mqttClientProxyMessage = proxyMessage - let binaryData: Data = try! toRadio.serializedData() - if connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected { - connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse) - print("📲 Sent Mqtt client proxy message to the connected device.") - } - - } - - func onMqttError(message: String) { - mqttProxyConnected = false - print("MQTT Error") - } - - private static var documentsFolder: URL { do { return try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true) @@ -316,6 +274,45 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } } + // MARK: MqttClientProxyManagerDelegate Methods + func onMqttConnected() { + mqttProxyConnected = true + print("📲 Mqtt Client Proxy onMqttConnected now subscribing to \(mqttManager.topic).") + mqttManager.mqttClientProxy?.subscribe(mqttManager.topic) + } + + func onMqttDisconnected() { + mqttProxyConnected = false + print("MQTT Disconnected") + } + + func onMqttMessageReceived(message: CocoaMQTTMessage) { + + print("📲 Mqtt Client Proxy onMqttMessageReceived for topic: \(message.topic)") + if message.topic.contains("/stat/") { + return + } + var proxyMessage = MqttClientProxyMessage() + proxyMessage.topic = message.topic + proxyMessage.data = Data(message.payload) + proxyMessage.retained = message.retained + + var toRadio: ToRadio! + toRadio = ToRadio() + toRadio.mqttClientProxyMessage = proxyMessage + let binaryData: Data = try! toRadio.serializedData() + if connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected { + connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse) + print("📲 Sent Mqtt client proxy message to the connected device.") + } + } + + func onMqttError(message: String) { + mqttProxyConnected = false + print("📲 Mqtt Client Proxy onMqttError: \(message)") + } + + // MARK: Protobuf Methods func requestDeviceMetadata(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32, context: NSManagedObjectContext) -> Int64 { guard connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected else { return 0 } diff --git a/Meshtastic/Helpers/Mqtt/MqttClientProxyManager.swift b/Meshtastic/Helpers/Mqtt/MqttClientProxyManager.swift index e4a27531..0f840c28 100644 --- a/Meshtastic/Helpers/Mqtt/MqttClientProxyManager.swift +++ b/Meshtastic/Helpers/Mqtt/MqttClientProxyManager.swift @@ -24,10 +24,6 @@ class MqttClientProxyManager { var mqttClientProxy: CocoaMQTT? var topic = "msh/2/c" - private init() { - - } - func connectFromConfigSettings(node: NodeInfoEntity) { let defaultServerAddress = "mqtt.meshtastic.org" @@ -140,7 +136,6 @@ extension MqttClientProxyManager: CocoaMQTTDelegate { } print(errorDescription) delegate?.onMqttError(message: errorDescription) - self.disconnect() } } diff --git a/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion b/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion index fdb1b5c8..7ccbfdf1 100644 --- a/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion +++ b/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion @@ -3,6 +3,6 @@ _XCCurrentVersionName - MeshtasticDataModelV15.xcdatamodel + MeshtasticDataModelV16.xcdatamodel diff --git a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV16.xcdatamodel/contents b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV16.xcdatamodel/contents new file mode 100644 index 00000000..f3885386 --- /dev/null +++ b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV16.xcdatamodel/contents @@ -0,0 +1,337 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Meshtastic/Views/Settings/Config/Module/DetectionSensorConfig.swift b/Meshtastic/Views/Settings/Config/Module/DetectionSensorConfig.swift new file mode 100644 index 00000000..18ce6479 --- /dev/null +++ b/Meshtastic/Views/Settings/Config/Module/DetectionSensorConfig.swift @@ -0,0 +1,121 @@ +// +// DetectionSensorModule.swift +// Meshtastic +// +// Copyright(c) Garth Vander Houwen 8/16/23. +// +import SwiftUI + +struct DetectionSensorConfig: 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 + + var body: some View { + + Form { + if node != nil && node?.metadata == nil && node?.num ?? 0 != bleManager.connectedPeripheral?.num ?? 0 { + Text("There has been no response to a request for device metadata over the admin channel for this node.") + .font(.callout) + .foregroundColor(.orange) + + } else if node != nil && node?.num ?? 0 != bleManager.connectedPeripheral?.num ?? 0 { + // Let users know what is going on if they are using remote admin and don't have the config yet + if node?.mqttConfig == nil { + Text("Detection Sensor config data was requested over the admin channel but no response has been returned from the remote node. You can check the status of admin message requests in the admin message log.") + .font(.callout) + .foregroundColor(.orange) + } else { + Text("Remote administration for: \(node?.user?.longName ?? "Unknown")") + .font(.title3) + .onAppear { + setDetectionSensorValues() + } + } + } else if node != nil && node?.num ?? 0 == bleManager.connectedPeripheral?.num ?? 0 { + Text("Configuration for: \(node?.user?.longName ?? "Unknown")") + .font(.title3) + } else { + Text("Please connect to a radio to configure settings.") + .font(.callout) + .foregroundColor(.orange) + } + Section(header: Text("options")) { + Toggle(isOn: $enabled) { + Label("enabled", systemImage: "dot.radiowaves.right") + } + } + } + .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 dsc = DetectionSensorConfig() + dsc.enabled = self.enabled +// 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 : "????") + }) + .onAppear { + self.bleManager.context = context + setDetectionSensorValues() + + // 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: enabled) { newEnabled in + if node != nil && node?.detectionSensorConfig != nil { + if newEnabled != node!.detectionSensorConfig!.enabled { hasChanges = true } + } + } + } + + func setDetectionSensorValues() { + self.enabled = (node?.detectionSensorConfig?.enabled ?? false) + self.hasChanges = false + } +} diff --git a/Meshtastic/Views/Settings/Settings.swift b/Meshtastic/Views/Settings/Settings.swift index e0a6b5cd..5b36fe6d 100644 --- a/Meshtastic/Views/Settings/Settings.swift +++ b/Meshtastic/Views/Settings/Settings.swift @@ -31,6 +31,7 @@ struct Settings: View { case networkConfig case positionConfig case cannedMessagesConfig + case detectionSensorConfig case externalNotificationConfig case mqttConfig case rangeTestConfig @@ -203,6 +204,17 @@ struct Settings: View { } .tag(SettingsSidebar.cannedMessagesConfig) + NavigationLink { + DetectionSensorConfig(node: nodes.first(where: { $0.num == selectedNode })) + } label: { + + Image(systemName: "sensor") + .symbolRenderingMode(.hierarchical) + + Text("detection.sensor") + } + .tag(SettingsSidebar.detectionSensorConfig) + NavigationLink { ExternalNotificationConfig(node: nodes.first(where: { $0.num == selectedNode })) } label: {