mirror of
https://github.com/meshtastic/Meshtastic-Apple.git
synced 2026-04-20 22:13:56 +00:00
Merge pull request #853 from meshtastic/pki
Encrypted Direct Messages Config and Display
This commit is contained in:
commit
7c72ff4e40
60 changed files with 1741 additions and 691 deletions
|
|
@ -334,9 +334,6 @@
|
|||
},
|
||||
"Ack Time: %@" : {
|
||||
|
||||
},
|
||||
"Acknowledged" : {
|
||||
|
||||
},
|
||||
"Acknowledged by another node" : {
|
||||
|
||||
|
|
@ -481,6 +478,12 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Admin & Direct Message Keys" : {
|
||||
|
||||
},
|
||||
"Admin Key" : {
|
||||
|
||||
},
|
||||
"admin.log" : {
|
||||
"extractionState" : "manual",
|
||||
|
|
@ -703,7 +706,10 @@
|
|||
"All" : {
|
||||
|
||||
},
|
||||
"All device and app data will be deleted. You will also need to forget your devices under Settings > Bluetooth." : {
|
||||
"All device and app data will be deleted." : {
|
||||
|
||||
},
|
||||
"Allow incoming device control over the insecure legacy admin channel." : {
|
||||
|
||||
},
|
||||
"Allow Position Requests" : {
|
||||
|
|
@ -1843,6 +1849,9 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Bluetooth Logs" : {
|
||||
|
||||
},
|
||||
"bluetooth.config" : {
|
||||
"localizations" : {
|
||||
|
|
@ -4801,9 +4810,6 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Contacts" : {
|
||||
|
||||
},
|
||||
"contacts %@" : {
|
||||
"extractionState" : "migrated",
|
||||
|
|
@ -5337,6 +5343,9 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Developer" : {
|
||||
|
||||
},
|
||||
"Developers" : {
|
||||
|
||||
|
|
@ -5401,6 +5410,9 @@
|
|||
},
|
||||
"Device GPS" : {
|
||||
|
||||
},
|
||||
"Device is managed by a mesh administrator." : {
|
||||
|
||||
},
|
||||
"Device Logging Enabled" : {
|
||||
|
||||
|
|
@ -6300,6 +6312,15 @@
|
|||
},
|
||||
"Direct" : {
|
||||
|
||||
},
|
||||
"Direct Message Help" : {
|
||||
|
||||
},
|
||||
"Direct messages are using the new public key infrastructure for encryption. Reguires firmware version 2.5 or greater." : {
|
||||
|
||||
},
|
||||
"Direct messages are using the shared key for the channel." : {
|
||||
|
||||
},
|
||||
"direct.messages" : {
|
||||
"localizations" : {
|
||||
|
|
@ -6682,9 +6703,6 @@
|
|||
},
|
||||
"Drag & Drop is the recommended way to update firmware for NRF devices. If your iPhone or iPad is USB-C it will work with your regular USB-C charging cable, for lightning devices you need the Apple Lightning to USB camera adaptor." : {
|
||||
|
||||
},
|
||||
"Each node is an available contact. Contacts with recent messages or marked as favorites show up at the top of the list. Select a contact to send or view messages. Long press to favorite or mute the contact or delete the conversation." : {
|
||||
|
||||
},
|
||||
"echo" : {
|
||||
"localizations" : {
|
||||
|
|
@ -6954,6 +6972,9 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Encrypted" : {
|
||||
|
||||
},
|
||||
"Encryption Enabled" : {
|
||||
|
||||
|
|
@ -7188,6 +7209,9 @@
|
|||
},
|
||||
"Favorites" : {
|
||||
|
||||
},
|
||||
"Favorites and nodes with recent messages show up at the top of the contact list." : {
|
||||
|
||||
},
|
||||
"Fifteen Minutes" : {
|
||||
|
||||
|
|
@ -8252,11 +8276,14 @@
|
|||
"Hops Away" : {
|
||||
|
||||
},
|
||||
"Hops Away %d) dB" : {
|
||||
"Hops Away %d" : {
|
||||
|
||||
},
|
||||
"Hops Away:" : {
|
||||
|
||||
},
|
||||
"Hops Away: %d" : {
|
||||
|
||||
},
|
||||
"Hour" : {
|
||||
|
||||
|
|
@ -10843,6 +10870,9 @@
|
|||
},
|
||||
"LED State" : {
|
||||
|
||||
},
|
||||
"Legacy Administration" : {
|
||||
|
||||
},
|
||||
"Licensed Operator" : {
|
||||
|
||||
|
|
@ -11248,6 +11278,9 @@
|
|||
},
|
||||
"Long Name: %@" : {
|
||||
|
||||
},
|
||||
"Long press to favorite or mute the contact or delete a conversation." : {
|
||||
|
||||
},
|
||||
"Longitude" : {
|
||||
|
||||
|
|
@ -14500,6 +14533,12 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Message" : {
|
||||
|
||||
},
|
||||
"Message Status Options" : {
|
||||
|
||||
},
|
||||
"message.details" : {
|
||||
"localizations" : {
|
||||
|
|
@ -15976,6 +16015,9 @@
|
|||
},
|
||||
"Other data sources" : {
|
||||
|
||||
},
|
||||
"Output live debug logging over serial." : {
|
||||
|
||||
},
|
||||
"Output pin buzzer GPIO " : {
|
||||
|
||||
|
|
@ -16626,9 +16668,21 @@
|
|||
},
|
||||
"Primary GPIO" : {
|
||||
|
||||
},
|
||||
"Private Key" : {
|
||||
|
||||
},
|
||||
"Project information" : {
|
||||
|
||||
},
|
||||
"Public Key" : {
|
||||
|
||||
},
|
||||
"Public Key Encryption" : {
|
||||
|
||||
},
|
||||
"Public Key Mismatch" : {
|
||||
|
||||
},
|
||||
"PWD" : {
|
||||
|
||||
|
|
@ -17207,7 +17261,10 @@
|
|||
"Remote administration for: %@" : {
|
||||
|
||||
},
|
||||
"Remote: %@" : {
|
||||
"Remote Legacy Admin: %@" : {
|
||||
|
||||
},
|
||||
"Remote PKI Admin: %@" : {
|
||||
|
||||
},
|
||||
"Remove" : {
|
||||
|
|
@ -17277,7 +17334,10 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"Request Admin: %@" : {
|
||||
"Request Legacy Admin: %@" : {
|
||||
|
||||
},
|
||||
"Request PKI Admin: %@" : {
|
||||
|
||||
},
|
||||
"Requires that there be an accelerometer on your device." : {
|
||||
|
|
@ -18407,6 +18467,28 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"routing.pkifailed" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Encrypted Send Failed"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"routing.pkiunknownpubkey" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Unknown Public Key"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"routing.timeout" : {
|
||||
"extractionState" : "migrated",
|
||||
"localizations" : {
|
||||
|
|
@ -18801,6 +18883,12 @@
|
|||
},
|
||||
"Secondary" : {
|
||||
|
||||
},
|
||||
"Security" : {
|
||||
|
||||
},
|
||||
"Security Config" : {
|
||||
|
||||
},
|
||||
"Select a channel" : {
|
||||
|
||||
|
|
@ -19016,6 +19104,9 @@
|
|||
},
|
||||
"Sensor Options" : {
|
||||
|
||||
},
|
||||
"Sent out to other nodes on the mesh to allow them to compute a shared secret key." : {
|
||||
|
||||
},
|
||||
"Sequence number" : {
|
||||
|
||||
|
|
@ -19083,6 +19174,12 @@
|
|||
},
|
||||
"Serial Console" : {
|
||||
|
||||
},
|
||||
"Serial Console over the Stream API." : {
|
||||
|
||||
},
|
||||
"Serial Debug Logs" : {
|
||||
|
||||
},
|
||||
"serial.config" : {
|
||||
"localizations" : {
|
||||
|
|
@ -19687,6 +19784,9 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Shared Key" : {
|
||||
|
||||
},
|
||||
"Short Name" : {
|
||||
|
||||
|
|
@ -20916,6 +21016,12 @@
|
|||
},
|
||||
"The minimum distance change in meters to be considered for a smart position broadcast." : {
|
||||
|
||||
},
|
||||
"The public key authorized to send admin messages to this node." : {
|
||||
|
||||
},
|
||||
"The public key does not match the recorded key. You may delete the node and let it exchange keys again, but this may indicate a more serious security problem. Contact the user through another trusted channel, to determine if the key change was due to a factory reset or other intentional action." : {
|
||||
|
||||
},
|
||||
"The region where you will be using your radios." : {
|
||||
|
||||
|
|
@ -22127,6 +22233,9 @@
|
|||
},
|
||||
"Use PWM Buzzer" : {
|
||||
|
||||
},
|
||||
"Used to create a shared key with a remote device." : {
|
||||
|
||||
},
|
||||
"user" : {
|
||||
"localizations" : {
|
||||
|
|
@ -22294,6 +22403,12 @@
|
|||
},
|
||||
"Via Mqtt" : {
|
||||
|
||||
},
|
||||
"View and export position-redacted device logs over Bluetooth" : {
|
||||
|
||||
},
|
||||
"View Logs" : {
|
||||
|
||||
},
|
||||
"voltage" : {
|
||||
"localizations" : {
|
||||
|
|
@ -22431,6 +22546,9 @@
|
|||
},
|
||||
"Website" : {
|
||||
|
||||
},
|
||||
"What does the lock mean?" : {
|
||||
|
||||
},
|
||||
"What is Meshtastic?" : {
|
||||
|
||||
|
|
@ -22449,6 +22567,12 @@
|
|||
},
|
||||
"WIND" : {
|
||||
|
||||
},
|
||||
"Wind Direction" : {
|
||||
|
||||
},
|
||||
"Wind Speed" : {
|
||||
|
||||
},
|
||||
"x" : {
|
||||
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@
|
|||
DD1B8F402B35E2F10022AABC /* GPSStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1B8F3F2B35E2F10022AABC /* GPSStatus.swift */; };
|
||||
DD1BD0EB2C601795008C0C70 /* CLLocation.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1BD0EA2C601795008C0C70 /* CLLocation.swift */; };
|
||||
DD1BD0EE2C603C91008C0C70 /* CustomFormatters.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1BD0ED2C603C91008C0C70 /* CustomFormatters.swift */; };
|
||||
DD1BD0F32C63C65E008C0C70 /* SecurityConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1BD0F22C63C65E008C0C70 /* SecurityConfig.swift */; };
|
||||
DD1BF2F92776FE2E008C8D2F /* UserMessageList.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1BF2F82776FE2E008C8D2F /* UserMessageList.swift */; };
|
||||
DD2160AF28C5552500C17253 /* MQTTConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2160AE28C5552500C17253 /* MQTTConfig.swift */; };
|
||||
DD23A50F26FD1B4400D9B90C /* PeripheralModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD23A50E26FD1B4400D9B90C /* PeripheralModel.swift */; };
|
||||
|
|
@ -91,6 +92,11 @@
|
|||
DD6193752862F6E600E59241 /* ExternalNotificationConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6193742862F6E600E59241 /* ExternalNotificationConfig.swift */; };
|
||||
DD6193772862F90F00E59241 /* CannedMessagesConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6193762862F90F00E59241 /* CannedMessagesConfig.swift */; };
|
||||
DD6193792863875F00E59241 /* SerialConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6193782863875F00E59241 /* SerialConfig.swift */; };
|
||||
DD6F65722C6AB8EC0053C113 /* SecureInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6F65712C6AB8EC0053C113 /* SecureInput.swift */; };
|
||||
DD6F65742C6CB80A0053C113 /* View.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6F65732C6CB80A0053C113 /* View.swift */; };
|
||||
DD6F65762C6EA5490053C113 /* AckErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6F65752C6EA5490053C113 /* AckErrors.swift */; };
|
||||
DD6F65792C6EADE60053C113 /* DirectMessagesHelp.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6F65782C6EADE60053C113 /* DirectMessagesHelp.swift */; };
|
||||
DD6F657B2C6EC2900053C113 /* LockLegend.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6F657A2C6EC2900053C113 /* LockLegend.swift */; };
|
||||
DD73FD1128750779000852D6 /* PositionLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD73FD1028750779000852D6 /* PositionLog.swift */; };
|
||||
DD769E0328D18BF1001A3F05 /* DeviceMetricsLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD769E0228D18BF0001A3F05 /* DeviceMetricsLog.swift */; };
|
||||
DD77093B2AA1ABB8007A8BF0 /* BluetoothTips.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD77093A2AA1ABB8007A8BF0 /* BluetoothTips.swift */; };
|
||||
|
|
@ -146,7 +152,6 @@
|
|||
DDB75A1A2A05EB67006ED576 /* alpha.png in Resources */ = {isa = PBXBuildFile; fileRef = DDB75A192A05EB67006ED576 /* alpha.png */; };
|
||||
DDB75A1E2A0B0CD0006ED576 /* LoRaSignalStrengthIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB75A1D2A0B0CD0006ED576 /* LoRaSignalStrengthIndicator.swift */; };
|
||||
DDB75A212A12B954006ED576 /* LoRaSignalStrength.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB75A202A12B954006ED576 /* LoRaSignalStrength.swift */; };
|
||||
DDB75A232A13CDA9006ED576 /* BatteryLevelCompact.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB75A222A13CDA9006ED576 /* BatteryLevelCompact.swift */; };
|
||||
DDB8F4102A9EE5B400230ECE /* Messages.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB8F40F2A9EE5B400230ECE /* Messages.swift */; };
|
||||
DDB8F4122A9EE5DD00230ECE /* UserList.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB8F4112A9EE5DD00230ECE /* UserList.swift */; };
|
||||
DDB8F4142A9EE5F000230ECE /* ChannelList.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB8F4132A9EE5F000230ECE /* ChannelList.swift */; };
|
||||
|
|
@ -292,6 +297,7 @@
|
|||
DD1BD0EA2C601795008C0C70 /* CLLocation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLLocation.swift; sourceTree = "<group>"; };
|
||||
DD1BD0ED2C603C91008C0C70 /* CustomFormatters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomFormatters.swift; sourceTree = "<group>"; };
|
||||
DD1BD0F12C61D3AD008C0C70 /* MeshtasticDataModelV 42.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 42.xcdatamodel"; sourceTree = "<group>"; };
|
||||
DD1BD0F22C63C65E008C0C70 /* SecurityConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecurityConfig.swift; sourceTree = "<group>"; };
|
||||
DD1BF2F82776FE2E008C8D2F /* UserMessageList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserMessageList.swift; sourceTree = "<group>"; };
|
||||
DD2160AE28C5552500C17253 /* MQTTConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MQTTConfig.swift; sourceTree = "<group>"; };
|
||||
DD23A50E26FD1B4400D9B90C /* PeripheralModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeripheralModel.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -343,6 +349,11 @@
|
|||
DD6193762862F90F00E59241 /* CannedMessagesConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CannedMessagesConfig.swift; sourceTree = "<group>"; };
|
||||
DD6193782863875F00E59241 /* SerialConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SerialConfig.swift; sourceTree = "<group>"; };
|
||||
DD68BAE72C417A74004C01A0 /* MeshtasticDataModelV 40.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 40.xcdatamodel"; sourceTree = "<group>"; };
|
||||
DD6F65712C6AB8EC0053C113 /* SecureInput.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureInput.swift; sourceTree = "<group>"; };
|
||||
DD6F65732C6CB80A0053C113 /* View.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = View.swift; sourceTree = "<group>"; };
|
||||
DD6F65752C6EA5490053C113 /* AckErrors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AckErrors.swift; sourceTree = "<group>"; };
|
||||
DD6F65782C6EADE60053C113 /* DirectMessagesHelp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DirectMessagesHelp.swift; sourceTree = "<group>"; };
|
||||
DD6F657A2C6EC2900053C113 /* LockLegend.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LockLegend.swift; sourceTree = "<group>"; };
|
||||
DD73FD1028750779000852D6 /* PositionLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PositionLog.swift; sourceTree = "<group>"; };
|
||||
DD769E0228D18BF0001A3F05 /* DeviceMetricsLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceMetricsLog.swift; sourceTree = "<group>"; };
|
||||
DD77093A2AA1ABB8007A8BF0 /* BluetoothTips.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BluetoothTips.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -406,7 +417,6 @@
|
|||
DDB75A1D2A0B0CD0006ED576 /* LoRaSignalStrengthIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoRaSignalStrengthIndicator.swift; sourceTree = "<group>"; };
|
||||
DDB75A1F2A10766D006ED576 /* MeshtasticDataModelV13.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV13.xcdatamodel; sourceTree = "<group>"; };
|
||||
DDB75A202A12B954006ED576 /* LoRaSignalStrength.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoRaSignalStrength.swift; sourceTree = "<group>"; };
|
||||
DDB75A222A13CDA9006ED576 /* BatteryLevelCompact.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryLevelCompact.swift; sourceTree = "<group>"; };
|
||||
DDB8F40F2A9EE5B400230ECE /* Messages.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Messages.swift; sourceTree = "<group>"; };
|
||||
DDB8F4112A9EE5DD00230ECE /* UserList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserList.swift; sourceTree = "<group>"; };
|
||||
DDB8F4132A9EE5F000230ECE /* ChannelList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelList.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -680,6 +690,7 @@
|
|||
DD2553582855B52700E55709 /* PositionConfig.swift */,
|
||||
DD8ED9C42898D51F00B3B0AB /* NetworkConfig.swift */,
|
||||
D93068DA2B81C85E0066FBC8 /* PowerConfig.swift */,
|
||||
DD1BD0F22C63C65E008C0C70 /* SecurityConfig.swift */,
|
||||
DD61937B2863877A00E59241 /* Module */,
|
||||
);
|
||||
path = Config;
|
||||
|
|
@ -703,6 +714,16 @@
|
|||
path = Module;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DD6F65772C6EAB860053C113 /* Help */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DD6F65752C6EA5490053C113 /* AckErrors.swift */,
|
||||
DD6F65782C6EADE60053C113 /* DirectMessagesHelp.swift */,
|
||||
DD6F657A2C6EC2900053C113 /* LockLegend.swift */,
|
||||
);
|
||||
path = Help;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DD7709392AA1ABA1007A8BF0 /* Tips */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
|
@ -895,9 +916,9 @@
|
|||
DDC2E18D26CE25CB0042C5E4 /* Helpers */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DD6F65772C6EAB860053C113 /* Help */,
|
||||
DD3CC6BD28E4CD9800FA9159 /* BatteryGauge.swift */,
|
||||
DD3CC24B2C498D6C001BD3A2 /* BatteryCompact.swift */,
|
||||
DDB75A222A13CDA9006ED576 /* BatteryLevelCompact.swift */,
|
||||
DD457187293C7E63000C49FB /* BLESignalStrengthIndicator.swift */,
|
||||
DD47E3D526F17ED900029299 /* CircleText.swift */,
|
||||
DDF924C926FBB953009FE055 /* ConnectedDevice.swift */,
|
||||
|
|
@ -909,6 +930,7 @@
|
|||
DD97E96528EFD9820056DDA4 /* MeshtasticLogo.swift */,
|
||||
DDF45C332BC1A48E005ED5F2 /* MQTTIcon.swift */,
|
||||
DD5E523D298F5A7D00D21B61 /* Weather */,
|
||||
DD6F65712C6AB8EC0053C113 /* SecureInput.swift */,
|
||||
);
|
||||
path = Helpers;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -995,6 +1017,7 @@
|
|||
DDD5BB172C2F9C36007E03CA /* OSLogEntryLog.swift */,
|
||||
DDF45C362BC46A5A005ED5F2 /* TimeZone.swift */,
|
||||
DDD5BB0C2C285F00007E03CA /* Logger.swift */,
|
||||
DD6F65732C6CB80A0053C113 /* View.swift */,
|
||||
);
|
||||
path = Extensions;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -1276,19 +1299,21 @@
|
|||
DDB8F4102A9EE5B400230ECE /* Messages.swift in Sources */,
|
||||
DDDB26482AACD6D1003AFCB7 /* NodeMapMapkit.swift in Sources */,
|
||||
DD4A911E2708C65400501B7E /* AppSettings.swift in Sources */,
|
||||
DD1BD0F32C63C65E008C0C70 /* SecurityConfig.swift in Sources */,
|
||||
DD2160AF28C5552500C17253 /* MQTTConfig.swift in Sources */,
|
||||
DD6F65722C6AB8EC0053C113 /* SecureInput.swift in Sources */,
|
||||
DD13AA492AB73BF400BA0C98 /* PositionPopover.swift in Sources */,
|
||||
6DEDA55C2A9592F900321D2E /* MessageEntityExtension.swift in Sources */,
|
||||
DDDB444229F8A88700EE2349 /* Double.swift in Sources */,
|
||||
DDF45C342BC1A48E005ED5F2 /* MQTTIcon.swift in Sources */,
|
||||
DDA9515A2BC6624100CEA535 /* TelemetryWeather.swift in Sources */,
|
||||
DDB75A232A13CDA9006ED576 /* BatteryLevelCompact.swift in Sources */,
|
||||
DDB75A162A0594AD006ED576 /* TileOverlay.swift in Sources */,
|
||||
DD1BD0EB2C601795008C0C70 /* CLLocation.swift in Sources */,
|
||||
DDF924CA26FBB953009FE055 /* ConnectedDevice.swift in Sources */,
|
||||
DD3CC6BE28E4CD9800FA9159 /* BatteryGauge.swift in Sources */,
|
||||
DD6193772862F90F00E59241 /* CannedMessagesConfig.swift in Sources */,
|
||||
DD3619152B1EF9F900C41C8C /* LocationsHandler.swift in Sources */,
|
||||
DD6F65792C6EADE60053C113 /* DirectMessagesHelp.swift in Sources */,
|
||||
25F5D5C02C3F6DA6008036E3 /* Router.swift in Sources */,
|
||||
DDDB444A29F8AA3A00EE2349 /* CLLocationCoordinate2D.swift in Sources */,
|
||||
25C49D902C471AEA0024FBD1 /* Constants.swift in Sources */,
|
||||
|
|
@ -1347,6 +1372,7 @@
|
|||
251926852C3BA97800249DF5 /* FavoriteNodeButton.swift in Sources */,
|
||||
D9C983A02B79D0E800BDBE6A /* AlertButton.swift in Sources */,
|
||||
DD86D4112881D16900BAEB7A /* WriteCsvFile.swift in Sources */,
|
||||
DD6F65762C6EA5490053C113 /* AckErrors.swift in Sources */,
|
||||
DDDB445029F8AC9C00EE2349 /* UIImage.swift in Sources */,
|
||||
DD86D40F2881BE4C00BAEB7A /* CsvDocument.swift in Sources */,
|
||||
DDB75A142A0593E2006ED576 /* OfflineTileManager.swift in Sources */,
|
||||
|
|
@ -1366,6 +1392,7 @@
|
|||
DDB6ABE228B13FB500384BA1 /* PositionConfigEnums.swift in Sources */,
|
||||
DD994B69295F88B60013760A /* IntervalEnums.swift in Sources */,
|
||||
DDDCD5702BB26F5C00BE6B60 /* NodeListFilter.swift in Sources */,
|
||||
DD6F65742C6CB80A0053C113 /* View.swift in Sources */,
|
||||
DD1933762B0835D500771CD5 /* PositionAltitudeChart.swift in Sources */,
|
||||
DD415828285859C4009B0E59 /* TelemetryConfig.swift in Sources */,
|
||||
DDB6CCFB2AAF805100945AF6 /* NodeMapSwiftUI.swift in Sources */,
|
||||
|
|
@ -1373,6 +1400,7 @@
|
|||
DD15E4F52B8BFC8E00654F61 /* PaxCounterLog.swift in Sources */,
|
||||
25F5D5C22C3F6E4B008036E3 /* AppState.swift in Sources */,
|
||||
DD3CC6C028E7A60700FA9159 /* MessagingEnums.swift in Sources */,
|
||||
DD6F657B2C6EC2900053C113 /* LockLegend.swift in Sources */,
|
||||
DD97E96628EFD9820056DDA4 /* MeshtasticLogo.swift in Sources */,
|
||||
DDAB580D2B0DAA9E00147258 /* Routes.swift in Sources */,
|
||||
D93068D52B812B700066FBC8 /* MessageDestination.swift in Sources */,
|
||||
|
|
@ -1617,7 +1645,7 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.4.2;
|
||||
MARKETING_VERSION = 2.5.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
|
|
@ -1652,7 +1680,7 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.4.2;
|
||||
MARKETING_VERSION = 2.5.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
|
|
@ -1684,7 +1712,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.4.2;
|
||||
MARKETING_VERSION = 2.5.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
|
|
@ -1717,7 +1745,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.4.2;
|
||||
MARKETING_VERSION = 2.5.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
|
|
|
|||
|
|
@ -210,6 +210,7 @@ enum ModemPresets: Int, CaseIterable, Identifiable {
|
|||
case medFast = 4
|
||||
case shortSlow = 5
|
||||
case shortFast = 6
|
||||
case shortTurbo = 8
|
||||
|
||||
var id: Int { self.rawValue }
|
||||
var description: String {
|
||||
|
|
@ -230,6 +231,8 @@ enum ModemPresets: Int, CaseIterable, Identifiable {
|
|||
return "Short Range - Slow"
|
||||
case .shortFast:
|
||||
return "Short Range - Fast"
|
||||
case .shortTurbo:
|
||||
return "Short Range - Turbo"
|
||||
}
|
||||
}
|
||||
var name: String {
|
||||
|
|
@ -250,6 +253,8 @@ enum ModemPresets: Int, CaseIterable, Identifiable {
|
|||
return "ShortSlow"
|
||||
case .shortFast:
|
||||
return "ShortFast"
|
||||
case .shortTurbo:
|
||||
return "ShortTurbo"
|
||||
}
|
||||
}
|
||||
func snrLimit() -> Float {
|
||||
|
|
@ -270,6 +275,8 @@ enum ModemPresets: Int, CaseIterable, Identifiable {
|
|||
return -10
|
||||
case .shortFast:
|
||||
return -7.5
|
||||
case .shortTurbo:
|
||||
return -7.5
|
||||
}
|
||||
}
|
||||
func protoEnumValue() -> Config.LoRaConfig.ModemPreset {
|
||||
|
|
@ -290,6 +297,8 @@ enum ModemPresets: Int, CaseIterable, Identifiable {
|
|||
return Config.LoRaConfig.ModemPreset.shortSlow
|
||||
case .shortFast:
|
||||
return Config.LoRaConfig.ModemPreset.shortFast
|
||||
case .shortTurbo:
|
||||
return Config.LoRaConfig.ModemPreset.shortTurbo
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
// Copyright(c) Garth Vander Houwen 8/4/22.
|
||||
//
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
import MeshtasticProtobufs
|
||||
|
||||
enum RoutingError: Int, CaseIterable, Identifiable {
|
||||
|
|
@ -21,6 +22,8 @@ enum RoutingError: Int, CaseIterable, Identifiable {
|
|||
case dutyCycleLimit = 9
|
||||
case badRequest = 32
|
||||
case notAuthorized = 33
|
||||
case pkiFailed = 34
|
||||
case pkiUnknownPubkey = 35
|
||||
|
||||
var id: Int { self.rawValue }
|
||||
var display: String {
|
||||
|
|
@ -50,6 +53,51 @@ enum RoutingError: Int, CaseIterable, Identifiable {
|
|||
return "routing.badRequest".localized
|
||||
case .notAuthorized:
|
||||
return "routing.notauthorized".localized
|
||||
case .pkiFailed:
|
||||
return "routing.pkifailed".localized
|
||||
case .pkiUnknownPubkey:
|
||||
return "routing.pkiunknownpubkey".localized
|
||||
}
|
||||
}
|
||||
var color: Color {
|
||||
if self == .none {
|
||||
return Color.secondary
|
||||
} else if self.canRetry {
|
||||
return Color.orange
|
||||
} else {
|
||||
return Color.red
|
||||
}
|
||||
}
|
||||
var canRetry: Bool {
|
||||
switch self {
|
||||
case .none:
|
||||
return false
|
||||
case .noRoute:
|
||||
return true
|
||||
case .gotNak:
|
||||
return true
|
||||
case .timeout:
|
||||
return true
|
||||
case .noInterface:
|
||||
return true
|
||||
case .maxRetransmit:
|
||||
return true
|
||||
case .noChannel:
|
||||
return true
|
||||
case .tooLarge:
|
||||
return false
|
||||
case .noResponse:
|
||||
return true
|
||||
case .dutyCycleLimit:
|
||||
return true
|
||||
case .badRequest:
|
||||
return true
|
||||
case .notAuthorized:
|
||||
return true
|
||||
case .pkiFailed:
|
||||
return false
|
||||
case .pkiUnknownPubkey:
|
||||
return false
|
||||
}
|
||||
}
|
||||
func protoEnumValue() -> Routing.Error {
|
||||
|
|
@ -80,7 +128,10 @@ enum RoutingError: Int, CaseIterable, Identifiable {
|
|||
return Routing.Error.badRequest
|
||||
case .notAuthorized:
|
||||
return Routing.Error.notAuthorized
|
||||
|
||||
case .pkiFailed:
|
||||
return Routing.Error.pkiFailed
|
||||
case .pkiUnknownPubkey:
|
||||
return Routing.Error.pkiUnknownPubkey
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,8 @@ extension MessageEntity {
|
|||
}
|
||||
|
||||
var canRetry: Bool {
|
||||
return ackError == 9 || ackError == 5 || ackError == 3
|
||||
let re = RoutingError(rawValue: Int(ackError))
|
||||
return re?.canRetry ?? false
|
||||
}
|
||||
|
||||
var tapbacks: [MessageEntity] {
|
||||
|
|
|
|||
|
|
@ -14,6 +14,10 @@ extension NodeInfoEntity {
|
|||
return self.positions?.lastObject as? PositionEntity
|
||||
}
|
||||
|
||||
var latestDeviceMetrics: TelemetryEntity? {
|
||||
return self.telemetries?.filtered(using: NSPredicate(format: "metricsType == 0")).lastObject as? TelemetryEntity
|
||||
}
|
||||
|
||||
var latestEnvironmentMetrics: TelemetryEntity? {
|
||||
return self.telemetries?.filtered(using: NSPredicate(format: "metricsType == 1")).lastObject as? TelemetryEntity
|
||||
}
|
||||
|
|
@ -54,6 +58,15 @@ extension NodeInfoEntity {
|
|||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var canRemoteAdmin: Bool {
|
||||
if UserDefaults.enableAdministration {
|
||||
return true
|
||||
} else {
|
||||
let adminChannel = myInfo?.channels?.filter { ($0 as AnyObject).name?.lowercased() == "admin" }
|
||||
return adminChannel?.count ?? 0 > 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func createNodeInfo(num: Int64, context: NSManagedObjectContext) -> NodeInfoEntity {
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@ extension UserDefaults {
|
|||
case modemPreset
|
||||
case firmwareVersion
|
||||
case environmentEnableWeatherKit
|
||||
case enableAdministration
|
||||
case testIntEnum
|
||||
}
|
||||
|
||||
|
|
@ -162,6 +163,9 @@ extension UserDefaults {
|
|||
@UserDefault(.environmentEnableWeatherKit, defaultValue: true)
|
||||
static var environmentEnableWeatherKit: Bool
|
||||
|
||||
@UserDefault(.enableAdministration, defaultValue: false)
|
||||
static var enableAdministration: Bool
|
||||
|
||||
@UserDefault(.testIntEnum, defaultValue: .one)
|
||||
static var testIntEnum: TestIntEnum
|
||||
}
|
||||
|
|
|
|||
30
Meshtastic/Extensions/View.swift
Normal file
30
Meshtastic/Extensions/View.swift
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
//
|
||||
// View.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Copyright(c) Garth Vander Houwen 8/14/24.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
public extension View {
|
||||
func onFirstAppear(_ action: @escaping () -> ()) -> some View {
|
||||
modifier(FirstAppear(action: action))
|
||||
}
|
||||
}
|
||||
|
||||
private struct FirstAppear: ViewModifier {
|
||||
let action: () -> ()
|
||||
|
||||
// Use this to only fire your block one time
|
||||
@State private var hasAppeared = false
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
// And then, track it here
|
||||
content.onAppear {
|
||||
guard !hasAppeared else { return }
|
||||
hasAppeared = true
|
||||
action()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -893,6 +893,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
lastConnectionError = ""
|
||||
isSubscribed = true
|
||||
Logger.mesh.info("🤜 [BLE] Want Config Complete. ID:\(decodedInfo.configCompleteID)")
|
||||
sendTime()
|
||||
peripherals.removeAll(where: { $0.peripheral.state == CBPeripheralState.disconnected })
|
||||
// Config conplete returns so we don't read the characteristic again
|
||||
|
||||
|
|
@ -1000,6 +1001,10 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
if toUserNum > 0 {
|
||||
newMessage.toUser = fetchedUsers.first(where: { $0.num == toUserNum })
|
||||
newMessage.toUser?.lastMessage = Date()
|
||||
if newMessage.toUser?.pkiEncrypted ?? false {
|
||||
newMessage.publicKey = newMessage.toUser?.publicKey
|
||||
newMessage.pkiEncrypted = true
|
||||
}
|
||||
}
|
||||
newMessage.fromUser = fetchedUsers.first(where: { $0.num == fromUserNum })
|
||||
newMessage.isEmoji = isEmoji
|
||||
|
|
@ -1022,6 +1027,10 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
dataMessage.portnum = dataType
|
||||
|
||||
var meshPacket = MeshPacket()
|
||||
if newMessage.toUser?.pkiEncrypted ?? false {
|
||||
meshPacket.pkiEncrypted = true
|
||||
meshPacket.publicKey = newMessage.toUser?.publicKey ?? Data()
|
||||
}
|
||||
meshPacket.id = UInt32(newMessage.messageId)
|
||||
if toUserNum > 0 {
|
||||
meshPacket.to = UInt32(toUserNum)
|
||||
|
|
@ -1140,6 +1149,11 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
guard let lastLocation = LocationsHandler.shared.locationsArray.last else {
|
||||
return nil
|
||||
}
|
||||
|
||||
if lastLocation == CLLocation(latitude: 0, longitude: 0) {
|
||||
return nil
|
||||
}
|
||||
|
||||
positionPacket.latitudeI = Int32(lastLocation.coordinate.latitude * 1e7)
|
||||
positionPacket.longitudeI = Int32(lastLocation.coordinate.longitude * 1e7)
|
||||
let timestamp = lastLocation.timestamp
|
||||
|
|
@ -1285,9 +1299,36 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
}
|
||||
}
|
||||
|
||||
public func sendTime() -> Bool {
|
||||
var adminPacket = AdminMessage()
|
||||
adminPacket.setTimeOnly = UInt32(Date().timeIntervalSince1970)
|
||||
var meshPacket: MeshPacket = MeshPacket()
|
||||
meshPacket.to = 0
|
||||
meshPacket.id = UInt32.random(in: UInt32(UInt8.max)..<UInt32.max)
|
||||
meshPacket.priority = MeshPacket.Priority.reliable
|
||||
meshPacket.wantAck = true
|
||||
meshPacket.channel = 0
|
||||
var dataMessage = DataMessage()
|
||||
if let serializedData: Data = try? adminPacket.serializedData() {
|
||||
dataMessage.payload = serializedData
|
||||
dataMessage.portnum = PortNum.adminApp
|
||||
meshPacket.decoded = dataMessage
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
let messageDescription = "🕛 Sent Set Time Admin Message to the connectecd node."
|
||||
if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
public func sendShutdown(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Bool {
|
||||
var adminPacket = AdminMessage()
|
||||
adminPacket.shutdownSeconds = 5
|
||||
if fromUser != toUser {
|
||||
adminPacket.sessionPasskey = toUser.userNode?.sessionPasskey ?? Data()
|
||||
}
|
||||
var meshPacket: MeshPacket = MeshPacket()
|
||||
meshPacket.to = UInt32(toUser.num)
|
||||
meshPacket.from = UInt32(fromUser.num)
|
||||
|
|
@ -1313,6 +1354,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
public func sendReboot(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Bool {
|
||||
var adminPacket = AdminMessage()
|
||||
adminPacket.rebootSeconds = 5
|
||||
if fromUser != toUser {
|
||||
adminPacket.sessionPasskey = toUser.userNode?.sessionPasskey ?? Data()
|
||||
}
|
||||
var meshPacket: MeshPacket = MeshPacket()
|
||||
meshPacket.to = UInt32(toUser.num)
|
||||
meshPacket.from = UInt32(fromUser.num)
|
||||
|
|
@ -1338,6 +1382,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
public func sendRebootOta(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Bool {
|
||||
var adminPacket = AdminMessage()
|
||||
adminPacket.rebootOtaSeconds = 5
|
||||
if fromUser != toUser {
|
||||
adminPacket.sessionPasskey = toUser.userNode?.sessionPasskey ?? Data()
|
||||
}
|
||||
var meshPacket: MeshPacket = MeshPacket()
|
||||
meshPacket.to = UInt32(toUser.num)
|
||||
meshPacket.from = UInt32(fromUser.num)
|
||||
|
|
@ -1363,6 +1410,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
public func sendEnterDfuMode(fromUser: UserEntity, toUser: UserEntity) -> Bool {
|
||||
var adminPacket = AdminMessage()
|
||||
adminPacket.enterDfuModeRequest = true
|
||||
if fromUser != toUser {
|
||||
adminPacket.sessionPasskey = toUser.userNode?.sessionPasskey ?? Data()
|
||||
}
|
||||
var meshPacket: MeshPacket = MeshPacket()
|
||||
meshPacket.to = UInt32(toUser.num)
|
||||
meshPacket.from = UInt32(fromUser.num)
|
||||
|
|
@ -1388,7 +1438,10 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
|
||||
public func sendFactoryReset(fromUser: UserEntity, toUser: UserEntity) -> Bool {
|
||||
var adminPacket = AdminMessage()
|
||||
adminPacket.factoryReset = 5
|
||||
adminPacket.factoryResetConfig = 5
|
||||
if fromUser != toUser {
|
||||
adminPacket.sessionPasskey = toUser.userNode?.sessionPasskey ?? Data()
|
||||
}
|
||||
var meshPacket: MeshPacket = MeshPacket()
|
||||
meshPacket.to = UInt32(toUser.num)
|
||||
meshPacket.from = UInt32(fromUser.num)
|
||||
|
|
@ -1414,6 +1467,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
public func sendNodeDBReset(fromUser: UserEntity, toUser: UserEntity) -> Bool {
|
||||
var adminPacket = AdminMessage()
|
||||
adminPacket.nodedbReset = 5
|
||||
if fromUser != toUser {
|
||||
adminPacket.sessionPasskey = toUser.userNode?.sessionPasskey ?? Data()
|
||||
}
|
||||
var meshPacket: MeshPacket = MeshPacket()
|
||||
meshPacket.to = UInt32(toUser.num)
|
||||
meshPacket.from = 0 // UInt32(fromUser.num)
|
||||
|
|
@ -1626,6 +1682,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
public func saveUser(config: User, fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Int64 {
|
||||
var adminPacket = AdminMessage()
|
||||
adminPacket.setOwner = config
|
||||
if fromUser != toUser {
|
||||
adminPacket.sessionPasskey = toUser.userNode?.sessionPasskey ?? Data()
|
||||
}
|
||||
var meshPacket: MeshPacket = MeshPacket()
|
||||
meshPacket.to = UInt32(toUser.num)
|
||||
meshPacket.from = UInt32(fromUser.num)
|
||||
|
|
@ -1748,6 +1807,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
public func saveLicensedUser(ham: HamParameters, fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Int64 {
|
||||
var adminPacket = AdminMessage()
|
||||
adminPacket.setHamMode = ham
|
||||
if fromUser != toUser {
|
||||
adminPacket.sessionPasskey = toUser.userNode?.sessionPasskey ?? Data()
|
||||
}
|
||||
var meshPacket: MeshPacket = MeshPacket()
|
||||
meshPacket.to = UInt32(toUser.num)
|
||||
meshPacket.from = UInt32(fromUser.num)
|
||||
|
|
@ -1771,6 +1833,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
public func saveBluetoothConfig(config: Config.BluetoothConfig, fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Int64 {
|
||||
var adminPacket = AdminMessage()
|
||||
adminPacket.setConfig.bluetooth = config
|
||||
if fromUser != toUser {
|
||||
adminPacket.sessionPasskey = toUser.userNode?.sessionPasskey ?? Data()
|
||||
}
|
||||
var meshPacket: MeshPacket = MeshPacket()
|
||||
meshPacket.to = UInt32(toUser.num)
|
||||
meshPacket.from = UInt32(fromUser.num)
|
||||
|
|
@ -1788,7 +1853,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
let messageDescription = "🛟 Saved Bluetooth Config for \(toUser.longName ?? "unknown".localized)"
|
||||
|
||||
if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription) {
|
||||
upsertBluetoothConfigPacket(config: config, nodeNum: toUser.num, context: context)
|
||||
upsertBluetoothConfigPacket(config: config, nodeNum: toUser.num, sessionPasskey: toUser.userNode?.sessionPasskey, context: context)
|
||||
return Int64(meshPacket.id)
|
||||
}
|
||||
|
||||
|
|
@ -1799,7 +1864,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
|
||||
var adminPacket = AdminMessage()
|
||||
adminPacket.setConfig.device = config
|
||||
|
||||
if fromUser != toUser {
|
||||
adminPacket.sessionPasskey = toUser.userNode?.sessionPasskey ?? Data()
|
||||
}
|
||||
var meshPacket: MeshPacket = MeshPacket()
|
||||
meshPacket.to = UInt32(toUser.num)
|
||||
meshPacket.from = UInt32(fromUser.num)
|
||||
|
|
@ -1816,7 +1883,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
meshPacket.decoded = dataMessage
|
||||
let messageDescription = "🛟 Saved Device Config for \(toUser.longName ?? "unknown".localized)"
|
||||
if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription) {
|
||||
upsertDeviceConfigPacket(config: config, nodeNum: toUser.num, context: context)
|
||||
upsertDeviceConfigPacket(config: config, nodeNum: toUser.num, sessionPasskey: toUser.userNode?.sessionPasskey, context: context)
|
||||
return Int64(meshPacket.id)
|
||||
}
|
||||
return 0
|
||||
|
|
@ -1825,6 +1892,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
public func saveDisplayConfig(config: Config.DisplayConfig, fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Int64 {
|
||||
var adminPacket = AdminMessage()
|
||||
adminPacket.setConfig.display = config
|
||||
if fromUser != toUser {
|
||||
adminPacket.sessionPasskey = toUser.userNode?.sessionPasskey ?? Data()
|
||||
}
|
||||
var meshPacket: MeshPacket = MeshPacket()
|
||||
meshPacket.to = UInt32(toUser.num)
|
||||
meshPacket.from = UInt32(fromUser.num)
|
||||
|
|
@ -1843,7 +1913,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
meshPacket.decoded = dataMessage
|
||||
let messageDescription = "🛟 Saved Display Config for \(toUser.longName ?? "unknown".localized)"
|
||||
if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription) {
|
||||
upsertDisplayConfigPacket(config: config, nodeNum: toUser.num, context: context)
|
||||
upsertDisplayConfigPacket(config: config, nodeNum: toUser.num, sessionPasskey: toUser.userNode?.sessionPasskey, context: context)
|
||||
return Int64(meshPacket.id)
|
||||
}
|
||||
return 0
|
||||
|
|
@ -1853,6 +1923,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
|
||||
var adminPacket = AdminMessage()
|
||||
adminPacket.setConfig.lora = config
|
||||
if fromUser != toUser {
|
||||
adminPacket.sessionPasskey = toUser.userNode?.sessionPasskey ?? Data()
|
||||
}
|
||||
var meshPacket: MeshPacket = MeshPacket()
|
||||
meshPacket.to = UInt32(toUser.num)
|
||||
meshPacket.from = UInt32(fromUser.num)
|
||||
|
|
@ -1870,7 +1943,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
let messageDescription = "🛟 Saved LoRa Config for \(toUser.longName ?? "unknown".localized)"
|
||||
|
||||
if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription) {
|
||||
upsertLoRaConfigPacket(config: config, nodeNum: toUser.num, context: context)
|
||||
upsertLoRaConfigPacket(config: config, nodeNum: toUser.num, sessionPasskey: toUser.userNode?.sessionPasskey,context: context)
|
||||
return Int64(meshPacket.id)
|
||||
}
|
||||
|
||||
|
|
@ -1881,7 +1954,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
|
||||
var adminPacket = AdminMessage()
|
||||
adminPacket.setConfig.position = config
|
||||
|
||||
if fromUser != toUser {
|
||||
adminPacket.sessionPasskey = toUser.userNode?.sessionPasskey ?? Data()
|
||||
}
|
||||
var meshPacket: MeshPacket = MeshPacket()
|
||||
meshPacket.to = UInt32(toUser.num)
|
||||
meshPacket.from = UInt32(fromUser.num)
|
||||
|
|
@ -1943,7 +2018,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
|
||||
var adminPacket = AdminMessage()
|
||||
adminPacket.setConfig.network = config
|
||||
|
||||
if fromUser != toUser {
|
||||
adminPacket.sessionPasskey = toUser.userNode?.sessionPasskey ?? Data()
|
||||
}
|
||||
var meshPacket: MeshPacket = MeshPacket()
|
||||
meshPacket.to = UInt32(toUser.num)
|
||||
meshPacket.from = UInt32(fromUser.num)
|
||||
|
|
@ -1970,11 +2047,46 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
return 0
|
||||
}
|
||||
|
||||
public func saveSecurityConfig(config: Config.SecurityConfig, fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Int64 {
|
||||
|
||||
var adminPacket = AdminMessage()
|
||||
adminPacket.setConfig.security = config
|
||||
if fromUser != toUser {
|
||||
adminPacket.sessionPasskey = toUser.userNode?.sessionPasskey ?? Data()
|
||||
}
|
||||
var meshPacket: MeshPacket = MeshPacket()
|
||||
meshPacket.to = UInt32(toUser.num)
|
||||
meshPacket.from = UInt32(fromUser.num)
|
||||
meshPacket.channel = UInt32(adminIndex)
|
||||
meshPacket.id = UInt32.random(in: UInt32(UInt8.max)..<UInt32.max)
|
||||
meshPacket.priority = MeshPacket.Priority.reliable
|
||||
meshPacket.wantAck = true
|
||||
var dataMessage = DataMessage()
|
||||
guard let adminData: Data = try? adminPacket.serializedData() else {
|
||||
return 0
|
||||
}
|
||||
dataMessage.payload = adminData
|
||||
dataMessage.portnum = PortNum.adminApp
|
||||
|
||||
meshPacket.decoded = dataMessage
|
||||
|
||||
let messageDescription = "🛟 Saved Security Config for \(toUser.longName ?? "unknown".localized)"
|
||||
|
||||
if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription) {
|
||||
upsertSecurityConfigPacket(config: config, nodeNum: toUser.num, context: context)
|
||||
return Int64(meshPacket.id)
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
public func saveAmbientLightingModuleConfig(config: ModuleConfig.AmbientLightingConfig, fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Int64 {
|
||||
|
||||
var adminPacket = AdminMessage()
|
||||
adminPacket.setModuleConfig.ambientLighting = config
|
||||
|
||||
if fromUser != toUser {
|
||||
adminPacket.sessionPasskey = toUser.userNode?.sessionPasskey ?? Data()
|
||||
}
|
||||
var meshPacket: MeshPacket = MeshPacket()
|
||||
meshPacket.to = UInt32(toUser.num)
|
||||
meshPacket.from = UInt32(fromUser.num)
|
||||
|
|
@ -2004,7 +2116,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
|
||||
var adminPacket = AdminMessage()
|
||||
adminPacket.setModuleConfig.cannedMessage = config
|
||||
|
||||
if fromUser != toUser {
|
||||
adminPacket.sessionPasskey = toUser.userNode?.sessionPasskey ?? Data()
|
||||
}
|
||||
var meshPacket: MeshPacket = MeshPacket()
|
||||
meshPacket.to = UInt32(toUser.num)
|
||||
meshPacket.from = UInt32(fromUser.num)
|
||||
|
|
@ -2034,7 +2148,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
|
||||
var adminPacket = AdminMessage()
|
||||
adminPacket.setCannedMessageModuleMessages = messages
|
||||
|
||||
if fromUser != toUser {
|
||||
adminPacket.sessionPasskey = toUser.userNode?.sessionPasskey ?? Data()
|
||||
}
|
||||
var meshPacket: MeshPacket = MeshPacket()
|
||||
meshPacket.to = UInt32(toUser.num)
|
||||
meshPacket.from = UInt32(fromUser.num)
|
||||
|
|
@ -2065,7 +2181,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
|
||||
var adminPacket = AdminMessage()
|
||||
adminPacket.setModuleConfig.detectionSensor = config
|
||||
|
||||
if fromUser != toUser {
|
||||
adminPacket.sessionPasskey = toUser.userNode?.sessionPasskey ?? Data()
|
||||
}
|
||||
var meshPacket: MeshPacket = MeshPacket()
|
||||
meshPacket.id = UInt32.random(in: UInt32(UInt8.max)..<UInt32.max)
|
||||
meshPacket.to = UInt32(toUser.num)
|
||||
|
|
@ -2094,7 +2212,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
|
||||
var adminPacket = AdminMessage()
|
||||
adminPacket.setModuleConfig.externalNotification = config
|
||||
|
||||
if fromUser != toUser {
|
||||
adminPacket.sessionPasskey = toUser.userNode?.sessionPasskey ?? Data()
|
||||
}
|
||||
var meshPacket: MeshPacket = MeshPacket()
|
||||
meshPacket.to = UInt32(toUser.num)
|
||||
meshPacket.from = UInt32(fromUser.num)
|
||||
|
|
@ -2123,7 +2243,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
|
||||
var adminPacket = AdminMessage()
|
||||
adminPacket.setModuleConfig.paxcounter = config
|
||||
|
||||
if fromUser != toUser {
|
||||
adminPacket.sessionPasskey = toUser.userNode?.sessionPasskey ?? Data()
|
||||
}
|
||||
var meshPacket: MeshPacket = MeshPacket()
|
||||
meshPacket.to = UInt32(toUser.num)
|
||||
meshPacket.from = UInt32(fromUser.num)
|
||||
|
|
@ -2153,7 +2275,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
|
||||
var adminPacket = AdminMessage()
|
||||
adminPacket.setRingtoneMessage = ringtone
|
||||
|
||||
if fromUser != toUser {
|
||||
adminPacket.sessionPasskey = toUser.userNode?.sessionPasskey ?? Data()
|
||||
}
|
||||
var meshPacket: MeshPacket = MeshPacket()
|
||||
meshPacket.id = UInt32.random(in: UInt32(UInt8.max)..<UInt32.max)
|
||||
meshPacket.to = UInt32(toUser.num)
|
||||
|
|
@ -2183,7 +2307,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
|
||||
var adminPacket = AdminMessage()
|
||||
adminPacket.setModuleConfig.mqtt = config
|
||||
|
||||
if fromUser != toUser {
|
||||
adminPacket.sessionPasskey = toUser.userNode?.sessionPasskey ?? Data()
|
||||
}
|
||||
var meshPacket: MeshPacket = MeshPacket()
|
||||
meshPacket.id = UInt32.random(in: UInt32(UInt8.max)..<UInt32.max)
|
||||
meshPacket.to = UInt32(toUser.num)
|
||||
|
|
@ -2213,7 +2339,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
|
||||
var adminPacket = AdminMessage()
|
||||
adminPacket.setModuleConfig.rangeTest = config
|
||||
|
||||
if fromUser != toUser {
|
||||
adminPacket.sessionPasskey = toUser.userNode?.sessionPasskey ?? Data()
|
||||
}
|
||||
var meshPacket: MeshPacket = MeshPacket()
|
||||
meshPacket.id = UInt32.random(in: UInt32(UInt8.max)..<UInt32.max)
|
||||
meshPacket.to = UInt32(toUser.num)
|
||||
|
|
@ -2243,7 +2371,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
|
||||
var adminPacket = AdminMessage()
|
||||
adminPacket.setModuleConfig.serial = config
|
||||
|
||||
if fromUser != toUser {
|
||||
adminPacket.sessionPasskey = toUser.userNode?.sessionPasskey ?? Data()
|
||||
}
|
||||
var meshPacket: MeshPacket = MeshPacket()
|
||||
meshPacket.id = UInt32.random(in: UInt32(UInt8.max)..<UInt32.max)
|
||||
meshPacket.to = UInt32(toUser.num)
|
||||
|
|
@ -2272,7 +2402,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
|
||||
var adminPacket = AdminMessage()
|
||||
adminPacket.setModuleConfig.storeForward = config
|
||||
|
||||
if fromUser != toUser {
|
||||
adminPacket.sessionPasskey = toUser.userNode?.sessionPasskey ?? Data()
|
||||
}
|
||||
var meshPacket: MeshPacket = MeshPacket()
|
||||
meshPacket.id = UInt32.random(in: UInt32(UInt8.max)..<UInt32.max)
|
||||
meshPacket.to = UInt32(toUser.num)
|
||||
|
|
@ -2301,7 +2433,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
|
||||
var adminPacket = AdminMessage()
|
||||
adminPacket.setModuleConfig.telemetry = config
|
||||
|
||||
if fromUser != toUser {
|
||||
adminPacket.sessionPasskey = toUser.userNode?.sessionPasskey ?? Data()
|
||||
}
|
||||
var meshPacket: MeshPacket = MeshPacket()
|
||||
meshPacket.id = UInt32.random(in: UInt32(UInt8.max)..<UInt32.max)
|
||||
meshPacket.to = UInt32(toUser.num)
|
||||
|
|
@ -2403,7 +2537,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
|
||||
var adminPacket = AdminMessage()
|
||||
adminPacket.getConfigRequest = AdminMessage.ConfigType.bluetoothConfig
|
||||
|
||||
var meshPacket: MeshPacket = MeshPacket()
|
||||
meshPacket.to = UInt32(toUser.num)
|
||||
meshPacket.from = UInt32(fromUser.num)
|
||||
|
|
@ -2434,7 +2567,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
|
||||
var adminPacket = AdminMessage()
|
||||
adminPacket.getConfigRequest = AdminMessage.ConfigType.deviceConfig
|
||||
|
||||
var meshPacket: MeshPacket = MeshPacket()
|
||||
meshPacket.to = UInt32(toUser.num)
|
||||
meshPacket.from = UInt32(fromUser.num)
|
||||
|
|
@ -2465,7 +2597,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
|
||||
var adminPacket = AdminMessage()
|
||||
adminPacket.getConfigRequest = AdminMessage.ConfigType.displayConfig
|
||||
|
||||
var meshPacket: MeshPacket = MeshPacket()
|
||||
meshPacket.to = UInt32(toUser.num)
|
||||
meshPacket.from = UInt32(fromUser.num)
|
||||
|
|
@ -2496,7 +2627,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
|
||||
var adminPacket = AdminMessage()
|
||||
adminPacket.getConfigRequest = AdminMessage.ConfigType.loraConfig
|
||||
|
||||
var meshPacket: MeshPacket = MeshPacket()
|
||||
meshPacket.to = UInt32(toUser.num)
|
||||
meshPacket.from = UInt32(fromUser.num)
|
||||
|
|
@ -2529,7 +2659,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
|
||||
var adminPacket = AdminMessage()
|
||||
adminPacket.getConfigRequest = AdminMessage.ConfigType.networkConfig
|
||||
|
||||
var meshPacket: MeshPacket = MeshPacket()
|
||||
meshPacket.to = UInt32(toUser.num)
|
||||
meshPacket.from = UInt32(fromUser.num)
|
||||
|
|
@ -2559,7 +2688,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
|
||||
var adminPacket = AdminMessage()
|
||||
adminPacket.getConfigRequest = AdminMessage.ConfigType.positionConfig
|
||||
|
||||
var meshPacket: MeshPacket = MeshPacket()
|
||||
meshPacket.to = UInt32(toUser.num)
|
||||
meshPacket.from = UInt32(fromUser.num)
|
||||
|
|
@ -2589,7 +2717,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
|
||||
var adminPacket = AdminMessage()
|
||||
adminPacket.getConfigRequest = AdminMessage.ConfigType.powerConfig
|
||||
|
||||
var meshPacket: MeshPacket = MeshPacket()
|
||||
meshPacket.to = UInt32(toUser.num)
|
||||
meshPacket.from = UInt32(fromUser.num)
|
||||
|
|
@ -2615,11 +2742,39 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
return false
|
||||
}
|
||||
|
||||
public func requestSecurityConfig(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Bool {
|
||||
|
||||
var adminPacket = AdminMessage()
|
||||
adminPacket.getConfigRequest = AdminMessage.ConfigType.securityConfig
|
||||
var meshPacket: MeshPacket = MeshPacket()
|
||||
meshPacket.to = UInt32(toUser.num)
|
||||
meshPacket.from = UInt32(fromUser.num)
|
||||
meshPacket.id = UInt32.random(in: UInt32(UInt8.max)..<UInt32.max)
|
||||
meshPacket.priority = MeshPacket.Priority.reliable
|
||||
meshPacket.channel = UInt32(adminIndex)
|
||||
meshPacket.wantAck = true
|
||||
|
||||
var dataMessage = DataMessage()
|
||||
guard let adminData: Data = try? adminPacket.serializedData() else {
|
||||
return false
|
||||
}
|
||||
dataMessage.payload = adminData
|
||||
dataMessage.portnum = PortNum.adminApp
|
||||
dataMessage.wantResponse = true
|
||||
|
||||
meshPacket.decoded = dataMessage
|
||||
|
||||
let messageDescription = "🛎️ Requested Security Config on admin channel \(adminIndex) for node: \(toUser.longName ?? "unknown".localized)"
|
||||
if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
public func requestAmbientLightingConfig(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Bool {
|
||||
|
||||
var adminPacket = AdminMessage()
|
||||
adminPacket.getModuleConfigRequest = AdminMessage.ModuleConfigType.ambientlightingConfig
|
||||
|
||||
var meshPacket: MeshPacket = MeshPacket()
|
||||
meshPacket.to = UInt32(toUser.num)
|
||||
meshPacket.from = UInt32(fromUser.num)
|
||||
|
|
@ -2649,7 +2804,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
|
||||
var adminPacket = AdminMessage()
|
||||
adminPacket.getModuleConfigRequest = AdminMessage.ModuleConfigType.cannedmsgConfig
|
||||
|
||||
var meshPacket: MeshPacket = MeshPacket()
|
||||
meshPacket.to = UInt32(toUser.num)
|
||||
meshPacket.from = UInt32(fromUser.num)
|
||||
|
|
@ -2679,7 +2833,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
|
||||
var adminPacket = AdminMessage()
|
||||
adminPacket.getModuleConfigRequest = AdminMessage.ModuleConfigType.extnotifConfig
|
||||
|
||||
var meshPacket: MeshPacket = MeshPacket()
|
||||
meshPacket.to = UInt32(toUser.num)
|
||||
meshPacket.from = UInt32(fromUser.num)
|
||||
|
|
@ -2709,7 +2862,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
|
||||
var adminPacket = AdminMessage()
|
||||
adminPacket.getModuleConfigRequest = AdminMessage.ModuleConfigType.paxcounterConfig
|
||||
|
||||
var meshPacket: MeshPacket = MeshPacket()
|
||||
meshPacket.to = UInt32(toUser.num)
|
||||
meshPacket.from = UInt32(fromUser.num)
|
||||
|
|
@ -2739,7 +2891,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
|
||||
var adminPacket = AdminMessage()
|
||||
adminPacket.getRingtoneRequest = true
|
||||
|
||||
var meshPacket: MeshPacket = MeshPacket()
|
||||
meshPacket.to = UInt32(toUser.num)
|
||||
meshPacket.from = UInt32(fromUser.num)
|
||||
|
|
@ -2769,7 +2920,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
|
||||
var adminPacket = AdminMessage()
|
||||
adminPacket.getModuleConfigRequest = AdminMessage.ModuleConfigType.rangetestConfig
|
||||
|
||||
var meshPacket: MeshPacket = MeshPacket()
|
||||
meshPacket.to = UInt32(toUser.num)
|
||||
meshPacket.from = UInt32(fromUser.num)
|
||||
|
|
@ -2799,7 +2949,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
|
||||
var adminPacket = AdminMessage()
|
||||
adminPacket.getModuleConfigRequest = AdminMessage.ModuleConfigType.mqttConfig
|
||||
|
||||
var meshPacket: MeshPacket = MeshPacket()
|
||||
meshPacket.to = UInt32(toUser.num)
|
||||
meshPacket.from = UInt32(fromUser.num)
|
||||
|
|
@ -2829,7 +2978,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
|
||||
var adminPacket = AdminMessage()
|
||||
adminPacket.getModuleConfigRequest = AdminMessage.ModuleConfigType.detectionsensorConfig
|
||||
|
||||
var meshPacket: MeshPacket = MeshPacket()
|
||||
meshPacket.to = UInt32(toUser.num)
|
||||
meshPacket.from = UInt32(fromUser.num)
|
||||
|
|
@ -2859,7 +3007,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
|
||||
var adminPacket = AdminMessage()
|
||||
adminPacket.getModuleConfigRequest = AdminMessage.ModuleConfigType.serialConfig
|
||||
|
||||
var meshPacket: MeshPacket = MeshPacket()
|
||||
meshPacket.to = UInt32(toUser.num)
|
||||
meshPacket.from = UInt32(fromUser.num)
|
||||
|
|
@ -2889,7 +3036,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
|
||||
var adminPacket = AdminMessage()
|
||||
adminPacket.getModuleConfigRequest = AdminMessage.ModuleConfigType.storeforwardConfig
|
||||
|
||||
var meshPacket: MeshPacket = MeshPacket()
|
||||
meshPacket.to = UInt32(toUser.num)
|
||||
meshPacket.from = UInt32(fromUser.num)
|
||||
|
|
@ -2919,7 +3065,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
|
||||
var adminPacket = AdminMessage()
|
||||
adminPacket.getModuleConfigRequest = AdminMessage.ModuleConfigType.telemetryConfig
|
||||
|
||||
adminPacket.sessionPasskey = toUser.userNode?.sessionPasskey ?? Data()
|
||||
var meshPacket: MeshPacket = MeshPacket()
|
||||
meshPacket.to = UInt32(toUser.num)
|
||||
meshPacket.from = UInt32(fromUser.num)
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ func generateMessageMarkdown (message: String) -> String {
|
|||
}
|
||||
|
||||
func localConfig (config: Config, context: NSManagedObjectContext, nodeNum: Int64, nodeLongName: String) {
|
||||
// We don't care about any of the Power settings, config is available for everything else
|
||||
|
||||
if config.payloadVariant == Config.OneOf_PayloadVariant.bluetooth(config.bluetooth) {
|
||||
upsertBluetoothConfigPacket(config: config.bluetooth, nodeNum: nodeNum, context: context)
|
||||
} else if config.payloadVariant == Config.OneOf_PayloadVariant.device(config.device) {
|
||||
|
|
@ -64,6 +64,8 @@ func localConfig (config: Config, context: NSManagedObjectContext, nodeNum: Int6
|
|||
upsertPositionConfigPacket(config: config.position, nodeNum: nodeNum, context: context)
|
||||
} else if config.payloadVariant == Config.OneOf_PayloadVariant.power(config.power) {
|
||||
upsertPowerConfigPacket(config: config.power, nodeNum: nodeNum, context: context)
|
||||
} else if config.payloadVariant == Config.OneOf_PayloadVariant.security(config.security) {
|
||||
upsertSecurityConfigPacket(config: config.security, nodeNum: nodeNum, context: context)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -196,7 +198,7 @@ func channelPacket (channel: Channel, fromNum: Int64, context: NSManagedObjectCo
|
|||
}
|
||||
}
|
||||
|
||||
func deviceMetadataPacket (metadata: DeviceMetadata, fromNum: Int64, context: NSManagedObjectContext) {
|
||||
func deviceMetadataPacket (metadata: DeviceMetadata, fromNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) {
|
||||
|
||||
if metadata.isInitialized {
|
||||
let logString = String.localizedStringWithFormat("mesh.log.device.metadata.received %@".localized, fromNum.toHex())
|
||||
|
|
@ -230,6 +232,10 @@ func deviceMetadataPacket (metadata: DeviceMetadata, fromNum: Int64, context: NS
|
|||
newNode.metadata = newMetadata
|
||||
}
|
||||
}
|
||||
if sessionPasskey?.count != 0 {
|
||||
fetchedNode[0].sessionPasskey = sessionPasskey
|
||||
fetchedNode[0].sessionExpiration = Date().addingTimeInterval(300)
|
||||
}
|
||||
do {
|
||||
try context.save()
|
||||
} catch {
|
||||
|
|
@ -276,6 +282,7 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje
|
|||
newTelemetries.append(telemetry)
|
||||
newNode.telemetries? = NSOrderedSet(array: newTelemetries)
|
||||
}
|
||||
|
||||
newNode.firstHeard = Date(timeIntervalSince1970: TimeInterval(Int64(nodeInfo.lastHeard)))
|
||||
newNode.lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(nodeInfo.lastHeard)))
|
||||
newNode.snr = nodeInfo.snr
|
||||
|
|
@ -296,6 +303,10 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje
|
|||
}
|
||||
newUser.isLicensed = nodeInfo.user.isLicensed
|
||||
newUser.role = Int32(nodeInfo.user.role.rawValue)
|
||||
if !nodeInfo.user.publicKey.isEmpty {
|
||||
newUser.pkiEncrypted = true
|
||||
newUser.publicKey = nodeInfo.user.publicKey
|
||||
}
|
||||
newNode.user = newUser
|
||||
} else if nodeInfo.num > Constants.minimumNodeNum {
|
||||
let newUser = createUser(num: Int64(nodeInfo.num), context: context)
|
||||
|
|
@ -353,6 +364,11 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje
|
|||
if fetchedNode[0].user == nil {
|
||||
fetchedNode[0].user = UserEntity(context: context)
|
||||
}
|
||||
// Set the public key for a user if it is empty, don't update
|
||||
if fetchedNode[0].user?.publicKey?.isEmpty == nil && !nodeInfo.user.publicKey.isEmpty {
|
||||
fetchedNode[0].user?.pkiEncrypted = true
|
||||
fetchedNode[0].user?.publicKey = nodeInfo.user.publicKey
|
||||
}
|
||||
fetchedNode[0].user!.userId = nodeInfo.user.id
|
||||
fetchedNode[0].user!.num = Int64(nodeInfo.num)
|
||||
fetchedNode[0].user!.numString = String(nodeInfo.num)
|
||||
|
|
@ -476,7 +492,7 @@ func adminAppPacket (packet: MeshPacket, context: NSManagedObjectContext) {
|
|||
} else if adminMessage.payloadVariant == AdminMessage.OneOf_PayloadVariant.getChannelResponse(adminMessage.getChannelResponse) {
|
||||
channelPacket(channel: adminMessage.getChannelResponse, fromNum: Int64(packet.from), context: context)
|
||||
} else if adminMessage.payloadVariant == AdminMessage.OneOf_PayloadVariant.getDeviceMetadataResponse(adminMessage.getDeviceMetadataResponse) {
|
||||
deviceMetadataPacket(metadata: adminMessage.getDeviceMetadataResponse, fromNum: Int64(packet.from), context: context)
|
||||
deviceMetadataPacket(metadata: adminMessage.getDeviceMetadataResponse, fromNum: Int64(packet.from), sessionPasskey: adminMessage.sessionPasskey, context: context)
|
||||
} else if adminMessage.payloadVariant == AdminMessage.OneOf_PayloadVariant.getConfigResponse(adminMessage.getConfigResponse) {
|
||||
let config = adminMessage.getConfigResponse
|
||||
if config.payloadVariant == Config.OneOf_PayloadVariant.bluetooth(config.bluetooth) {
|
||||
|
|
@ -493,6 +509,8 @@ func adminAppPacket (packet: MeshPacket, context: NSManagedObjectContext) {
|
|||
upsertPositionConfigPacket(config: config.position, nodeNum: Int64(packet.from), context: context)
|
||||
} else if config.payloadVariant == Config.OneOf_PayloadVariant.power(config.power) {
|
||||
upsertPowerConfigPacket(config: config.power, nodeNum: Int64(packet.from), context: context)
|
||||
} else if config.payloadVariant == Config.OneOf_PayloadVariant.security(config.security) {
|
||||
upsertSecurityConfigPacket(config: config.security, nodeNum: Int64(packet.from), sessionPasskey: adminMessage.sessionPasskey, context: context)
|
||||
}
|
||||
} else if adminMessage.payloadVariant == AdminMessage.OneOf_PayloadVariant.getModuleConfigResponse(adminMessage.getModuleConfigResponse) {
|
||||
let moduleConfig = adminMessage.getModuleConfigResponse
|
||||
|
|
@ -614,13 +632,21 @@ func routingPacket (packet: MeshPacket, connectedNodeNum: Int64, context: NSMana
|
|||
}
|
||||
}
|
||||
fetchedMessage[0].ackError = Int32(routingMessage.errorReason.rawValue)
|
||||
if routingError == RoutingError.pkiFailed {
|
||||
fetchedMessage[0].toUser?.keyMatch = false
|
||||
fetchedMessage[0].toUser?.newPublicKey = fetchedMessage[0].publicKey
|
||||
}
|
||||
|
||||
if routingMessage.errorReason == Routing.Error.none {
|
||||
|
||||
fetchedMessage[0].receivedACK = true
|
||||
}
|
||||
fetchedMessage[0].ackSNR = packet.rxSnr
|
||||
fetchedMessage[0].ackTimestamp = Int32(truncatingIfNeeded: packet.rxTime)
|
||||
if packet.rxTime > 0 {
|
||||
fetchedMessage[0].ackTimestamp = Int32(truncatingIfNeeded: packet.rxTime)
|
||||
} else {
|
||||
fetchedMessage[0].ackTimestamp = Int32(Date().timeIntervalSince1970)
|
||||
}
|
||||
|
||||
if fetchedMessage[0].toUser != nil {
|
||||
fetchedMessage[0].toUser!.objectWillChange.send()
|
||||
|
|
@ -709,7 +735,11 @@ func telemetryPacket(packet: MeshPacket, connectedNode: Int64, context: NSManage
|
|||
return
|
||||
}
|
||||
mutableTelemetries.add(telemetry)
|
||||
fetchedNode[0].lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(truncatingIfNeeded: packet.rxTime)))
|
||||
if packet.rxTime > 0 {
|
||||
fetchedNode[0].lastHeard = Date(timeIntervalSince1970: TimeInterval(packet.rxTime))
|
||||
} else {
|
||||
fetchedNode[0].lastHeard = Date()
|
||||
}
|
||||
fetchedNode[0].telemetries = mutableTelemetries.copy() as? NSOrderedSet
|
||||
}
|
||||
try context.save()
|
||||
|
|
@ -808,10 +838,10 @@ func textMessageAppPacket(
|
|||
let fetchedUsers = try context.fetch(messageUsers)
|
||||
let newMessage = MessageEntity(context: context)
|
||||
newMessage.messageId = Int64(packet.id)
|
||||
if packet.rxTime == 0 {
|
||||
newMessage.messageTimestamp = Int32(Date().timeIntervalSince1970)
|
||||
if packet.rxTime > 0 {
|
||||
newMessage.messageTimestamp = Int32(bitPattern: packet.rxTime)
|
||||
} else {
|
||||
newMessage.messageTimestamp = Int32(packet.rxTime)
|
||||
newMessage.messageTimestamp = Int32(Date().timeIntervalSince1970)
|
||||
}
|
||||
newMessage.receivedACK = false
|
||||
newMessage.snr = packet.rxSnr
|
||||
|
|
@ -819,6 +849,8 @@ func textMessageAppPacket(
|
|||
newMessage.isEmoji = packet.decoded.emoji == 1
|
||||
newMessage.channel = Int32(packet.channel)
|
||||
newMessage.portNum = Int32(packet.decoded.portnum.rawValue)
|
||||
newMessage.publicKey = packet.publicKey
|
||||
newMessage.pkiEncrypted = packet.pkiEncrypted
|
||||
if packet.decoded.portnum == PortNum.detectionSensorApp {
|
||||
if !UserDefaults.enableDetectionNotifications {
|
||||
newMessage.read = true
|
||||
|
|
@ -835,8 +867,21 @@ func textMessageAppPacket(
|
|||
}
|
||||
if fetchedUsers.first(where: { $0.num == packet.from }) != nil {
|
||||
newMessage.fromUser = fetchedUsers.first(where: { $0.num == packet.from })
|
||||
if !(newMessage.fromUser?.publicKey?.isEmpty ?? true) {
|
||||
/// We have a key, check if it matches
|
||||
if newMessage.fromUser?.publicKey != newMessage.publicKey {
|
||||
newMessage.fromUser?.keyMatch = false
|
||||
newMessage.fromUser?.newPublicKey = newMessage.publicKey
|
||||
}
|
||||
} else {
|
||||
/// We have no key, set it
|
||||
newMessage.fromUser?.publicKey = packet.publicKey
|
||||
newMessage.fromUser?.pkiEncrypted = packet.pkiEncrypted
|
||||
}
|
||||
if packet.rxTime > 0 {
|
||||
newMessage.fromUser?.userNode?.lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(packet.rxTime)))
|
||||
} else {
|
||||
newMessage.fromUser?.userNode?.lastHeard = Date()
|
||||
}
|
||||
}
|
||||
newMessage.messagePayload = messageText
|
||||
|
|
|
|||
|
|
@ -155,7 +155,9 @@
|
|||
<attribute name="messagePayload" optional="YES" attributeType="String" defaultValueString=""/>
|
||||
<attribute name="messagePayloadMarkdown" optional="YES" attributeType="String"/>
|
||||
<attribute name="messageTimestamp" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="pkiEncrypted" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="portNum" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="publicKey" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="read" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="realACK" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="receivedACK" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
|
|
@ -224,6 +226,8 @@
|
|||
<attribute name="num" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="peripheralId" optional="YES" attributeType="String"/>
|
||||
<attribute name="rssi" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="sessionExpiration" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="sessionPasskey" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="viaMqtt" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<relationship name="ambientLightingConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="AmbientLightingConfigEntity" inverseName="ambientLightingConfigNode" inverseEntity="AmbientLightingConfigEntity"/>
|
||||
|
|
@ -245,6 +249,7 @@
|
|||
<relationship name="powerConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="PowerConfigEntity" inverseName="powerConfigNode" inverseEntity="PowerConfigEntity"/>
|
||||
<relationship name="rangeTestConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="RangeTestConfigEntity" inverseName="rangeTestConfigNode" inverseEntity="RangeTestConfigEntity"/>
|
||||
<relationship name="rtttlConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="RTTTLConfigEntity" inverseName="rtttlConfigNode" inverseEntity="RTTTLConfigEntity"/>
|
||||
<relationship name="securityConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="SecurityConfigEntity" inverseName="securityConfigNode" inverseEntity="SecurityConfigEntity"/>
|
||||
<relationship name="serialConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="SerialConfigEntity" inverseName="serialConfigNode" inverseEntity="SerialConfigEntity"/>
|
||||
<relationship name="storeForwardConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="StoreForwardConfigEntity" inverseName="storeForwardConfigNode" inverseEntity="StoreForwardConfigEntity"/>
|
||||
<relationship name="telemetries" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="TelemetryEntity" inverseName="nodeTelemetry" inverseEntity="TelemetryEntity"/>
|
||||
|
|
@ -334,6 +339,17 @@
|
|||
<attribute name="ringtone" optional="YES" attributeType="String" maxValueString="228" defaultValueString=""/>
|
||||
<relationship name="rtttlConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="rtttlConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="SecurityConfigEntity" representedClassName="SecurityConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="adminChannelEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="adminKey" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="bluetoothLoggingEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="debugLogApiEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="isManaged" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="privateKey" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="publicKey" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="serialEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<relationship name="securityConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="securityConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="SerialConfigEntity" representedClassName="SerialConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="baudRate" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="echo" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
|
|
@ -418,11 +434,15 @@
|
|||
<attribute name="hwModel" attributeType="String"/>
|
||||
<attribute name="hwModelId" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="isLicensed" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="keyMatch" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<attribute name="lastMessage" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="longName" attributeType="String"/>
|
||||
<attribute name="mute" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="newPublicKey" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="num" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="numString" optional="YES" attributeType="String"/>
|
||||
<attribute name="pkiEncrypted" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="publicKey" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="role" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="shortName" attributeType="String"/>
|
||||
<attribute name="userId" attributeType="String"/>
|
||||
|
|
|
|||
|
|
@ -104,8 +104,8 @@ extension NSPersistentContainer {
|
|||
}
|
||||
|
||||
/// Restore backup persistent stores located in the directory referenced by `backupURL`.
|
||||
///
|
||||
/// **Be very careful with this**. To restore a persistent store, the current persistent store must be removed from the container. When that happens, **all currently loaded Core Data objects** will become invalid. Using them after restoring will cause your app to crash. When calling this method you **must** ensure that you do not continue to use any previously fetched managed objects or existing fetched results controllers. **If this method does not throw, that does not mean your app is safe.** You need to take extra steps to prevent crashes. The details vary depending on the nature of your app.
|
||||
/// **Be very careful with this**. To restore a persistent store, the current persistent store must be removed from the container. When that happens, **all currently loaded Core Data objects** will become invalid. Using them after restoring will cause your app to crash.
|
||||
/// When calling this method you **must** ensure that you do not continue to use any previously fetched managed objects or existing fetched results controllers. **If this method does not throw, that does not mean your app is safe.** You need to take extra steps to prevent crashes. The details vary depending on the nature of your app.
|
||||
/// - Parameter backupURL: A file URL containing backup copies of all currently loaded persistent stores.
|
||||
/// - Throws: `CopyPersistentStoreError` in various situations.
|
||||
/// - Returns: Nothing. If no errors are thrown, the restore is complete.
|
||||
|
|
|
|||
|
|
@ -148,6 +148,9 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext)
|
|||
if packet.rxTime > 0 {
|
||||
newNode.firstHeard = Date(timeIntervalSince1970: TimeInterval(Int64(packet.rxTime)))
|
||||
newNode.lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(packet.rxTime)))
|
||||
} else {
|
||||
newNode.firstHeard = Date()
|
||||
newNode.lastHeard = Date()
|
||||
}
|
||||
newNode.snr = packet.rxSnr
|
||||
newNode.rssi = packet.rxRssi
|
||||
|
|
@ -178,6 +181,8 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext)
|
|||
newUser.role = Int32(newUserMessage.role.rawValue)
|
||||
newUser.hwModel = String(describing: newUserMessage.hwModel).uppercased()
|
||||
newUser.hwModelId = Int32(newUserMessage.hwModel.rawValue)
|
||||
newUser.pkiEncrypted = packet.pkiEncrypted
|
||||
newUser.publicKey = packet.publicKey
|
||||
Task {
|
||||
Api().loadDeviceHardwareData { (hw) in
|
||||
let dh = hw.first(where: { $0.hwModel == newUser.hwModelId })
|
||||
|
|
@ -231,9 +236,8 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext)
|
|||
fetchedNode[0].num = Int64(packet.from)
|
||||
if packet.rxTime > 0 {
|
||||
fetchedNode[0].lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(packet.rxTime)))
|
||||
if fetchedNode[0].firstHeard == nil {
|
||||
fetchedNode[0].lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(packet.rxTime)))
|
||||
}
|
||||
} else {
|
||||
fetchedNode[0].lastHeard = Date()
|
||||
}
|
||||
fetchedNode[0].snr = packet.rxSnr
|
||||
fetchedNode[0].rssi = packet.rxRssi
|
||||
|
|
@ -361,6 +365,8 @@ func upsertPositionPacket (packet: MeshPacket, context: NSManagedObjectContext)
|
|||
fetchedNode[0].lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(positionMessage.time)))
|
||||
} else if packet.rxTime > 0 {
|
||||
fetchedNode[0].lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(packet.rxTime)))
|
||||
} else {
|
||||
fetchedNode[0].lastHeard = Date()
|
||||
}
|
||||
fetchedNode[0].snr = packet.rxSnr
|
||||
fetchedNode[0].rssi = packet.rxRssi
|
||||
|
|
@ -391,7 +397,7 @@ func upsertPositionPacket (packet: MeshPacket, context: NSManagedObjectContext)
|
|||
}
|
||||
}
|
||||
|
||||
func upsertBluetoothConfigPacket(config: Config.BluetoothConfig, nodeNum: Int64, context: NSManagedObjectContext) {
|
||||
func upsertBluetoothConfigPacket(config: Config.BluetoothConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.bluetooth.config %@".localized, String(nodeNum))
|
||||
MeshLogger.log("📶 \(logString)")
|
||||
|
|
@ -416,6 +422,10 @@ func upsertBluetoothConfigPacket(config: Config.BluetoothConfig, nodeNum: Int64,
|
|||
fetchedNode[0].bluetoothConfig?.fixedPin = Int32(config.fixedPin)
|
||||
fetchedNode[0].bluetoothConfig?.deviceLoggingEnabled = config.deviceLoggingEnabled
|
||||
}
|
||||
if sessionPasskey != nil {
|
||||
fetchedNode[0].sessionPasskey = sessionPasskey
|
||||
fetchedNode[0].sessionExpiration = Date().addingTimeInterval(300)
|
||||
}
|
||||
do {
|
||||
try context.save()
|
||||
Logger.data.info("💾 [BluetoothConfigEntity] Updated for node: \(nodeNum.toHex(), privacy: .public)")
|
||||
|
|
@ -433,7 +443,7 @@ func upsertBluetoothConfigPacket(config: Config.BluetoothConfig, nodeNum: Int64,
|
|||
}
|
||||
}
|
||||
|
||||
func upsertDeviceConfigPacket(config: Config.DeviceConfig, nodeNum: Int64, context: NSManagedObjectContext) {
|
||||
func upsertDeviceConfigPacket(config: Config.DeviceConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.device.config %@".localized, String(nodeNum))
|
||||
MeshLogger.log("📟 \(logString)")
|
||||
|
|
@ -471,6 +481,10 @@ func upsertDeviceConfigPacket(config: Config.DeviceConfig, nodeNum: Int64, conte
|
|||
fetchedNode[0].deviceConfig?.isManaged = config.isManaged
|
||||
fetchedNode[0].deviceConfig?.tzdef = config.tzdef
|
||||
}
|
||||
if sessionPasskey != nil {
|
||||
fetchedNode[0].sessionPasskey = sessionPasskey
|
||||
fetchedNode[0].sessionExpiration = Date().addingTimeInterval(300)
|
||||
}
|
||||
do {
|
||||
try context.save()
|
||||
Logger.data.info("💾 [DeviceConfigEntity] Updated Device Config for node number: \(nodeNum.toHex(), privacy: .public)")
|
||||
|
|
@ -486,7 +500,7 @@ func upsertDeviceConfigPacket(config: Config.DeviceConfig, nodeNum: Int64, conte
|
|||
}
|
||||
}
|
||||
|
||||
func upsertDisplayConfigPacket(config: Config.DisplayConfig, nodeNum: Int64, context: NSManagedObjectContext) {
|
||||
func upsertDisplayConfigPacket(config: Config.DisplayConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.display.config %@".localized, nodeNum.toHex())
|
||||
MeshLogger.log("🖥️ \(logString)")
|
||||
|
|
@ -512,7 +526,6 @@ func upsertDisplayConfigPacket(config: Config.DisplayConfig, nodeNum: Int64, con
|
|||
newDisplayConfig.units = Int32(config.units.rawValue)
|
||||
newDisplayConfig.headingBold = config.headingBold
|
||||
fetchedNode[0].displayConfig = newDisplayConfig
|
||||
|
||||
} else {
|
||||
|
||||
fetchedNode[0].displayConfig?.gpsFormat = Int32(config.gpsFormat.rawValue)
|
||||
|
|
@ -525,7 +538,10 @@ func upsertDisplayConfigPacket(config: Config.DisplayConfig, nodeNum: Int64, con
|
|||
fetchedNode[0].displayConfig?.units = Int32(config.units.rawValue)
|
||||
fetchedNode[0].displayConfig?.headingBold = config.headingBold
|
||||
}
|
||||
|
||||
if sessionPasskey != nil {
|
||||
fetchedNode[0].sessionPasskey = sessionPasskey
|
||||
fetchedNode[0].sessionExpiration = Date().addingTimeInterval(300)
|
||||
}
|
||||
do {
|
||||
|
||||
try context.save()
|
||||
|
|
@ -550,7 +566,7 @@ func upsertDisplayConfigPacket(config: Config.DisplayConfig, nodeNum: Int64, con
|
|||
}
|
||||
}
|
||||
|
||||
func upsertLoRaConfigPacket(config: Config.LoRaConfig, nodeNum: Int64, context: NSManagedObjectContext) {
|
||||
func upsertLoRaConfigPacket(config: Config.LoRaConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.lora.config %@".localized, nodeNum.toHex())
|
||||
MeshLogger.log("📻 \(logString)")
|
||||
|
|
@ -598,6 +614,10 @@ func upsertLoRaConfigPacket(config: Config.LoRaConfig, nodeNum: Int64, context:
|
|||
fetchedNode[0].loRaConfig?.ignoreMqtt = config.ignoreMqtt
|
||||
fetchedNode[0].loRaConfig?.sx126xRxBoostedGain = config.sx126XRxBoostedGain
|
||||
}
|
||||
if sessionPasskey != nil {
|
||||
fetchedNode[0].sessionPasskey = sessionPasskey
|
||||
fetchedNode[0].sessionExpiration = Date().addingTimeInterval(300)
|
||||
}
|
||||
do {
|
||||
try context.save()
|
||||
Logger.data.info("💾 [LoRaConfigEntity] Updated for node: \(nodeNum.toHex(), privacy: .public)")
|
||||
|
|
@ -615,7 +635,7 @@ func upsertLoRaConfigPacket(config: Config.LoRaConfig, nodeNum: Int64, context:
|
|||
}
|
||||
}
|
||||
|
||||
func upsertNetworkConfigPacket(config: Config.NetworkConfig, nodeNum: Int64, context: NSManagedObjectContext) {
|
||||
func upsertNetworkConfigPacket(config: Config.NetworkConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.network.config %@".localized, String(nodeNum))
|
||||
MeshLogger.log("🌐 \(logString)")
|
||||
|
|
@ -640,7 +660,10 @@ func upsertNetworkConfigPacket(config: Config.NetworkConfig, nodeNum: Int64, con
|
|||
fetchedNode[0].networkConfig?.wifiSsid = config.wifiSsid
|
||||
fetchedNode[0].networkConfig?.wifiPsk = config.wifiPsk
|
||||
}
|
||||
|
||||
if sessionPasskey != nil {
|
||||
fetchedNode[0].sessionPasskey = sessionPasskey
|
||||
fetchedNode[0].sessionExpiration = Date().addingTimeInterval(300)
|
||||
}
|
||||
do {
|
||||
try context.save()
|
||||
Logger.data.info("💾 [NetworkConfigEntity] Updated Network Config for node: \(nodeNum.toHex(), privacy: .public)")
|
||||
|
|
@ -659,7 +682,7 @@ func upsertNetworkConfigPacket(config: Config.NetworkConfig, nodeNum: Int64, con
|
|||
}
|
||||
}
|
||||
|
||||
func upsertPositionConfigPacket(config: Config.PositionConfig, nodeNum: Int64, context: NSManagedObjectContext) {
|
||||
func upsertPositionConfigPacket(config: Config.PositionConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.position.config %@".localized, String(nodeNum))
|
||||
MeshLogger.log("🗺️ \(logString)")
|
||||
|
|
@ -702,6 +725,10 @@ func upsertPositionConfigPacket(config: Config.PositionConfig, nodeNum: Int64, c
|
|||
fetchedNode[0].positionConfig?.gpsUpdateInterval = Int32(config.gpsUpdateInterval)
|
||||
fetchedNode[0].positionConfig?.positionFlags = Int32(config.positionFlags)
|
||||
}
|
||||
if sessionPasskey != nil {
|
||||
fetchedNode[0].sessionPasskey = sessionPasskey
|
||||
fetchedNode[0].sessionExpiration = Date().addingTimeInterval(300)
|
||||
}
|
||||
do {
|
||||
try context.save()
|
||||
Logger.data.info("💾 [PositionConfigEntity] Updated for node: \(nodeNum.toHex(), privacy: .public)")
|
||||
|
|
@ -719,7 +746,7 @@ func upsertPositionConfigPacket(config: Config.PositionConfig, nodeNum: Int64, c
|
|||
}
|
||||
}
|
||||
|
||||
func upsertPowerConfigPacket(config: Config.PowerConfig, nodeNum: Int64, context: NSManagedObjectContext) {
|
||||
func upsertPowerConfigPacket(config: Config.PowerConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) {
|
||||
let logString = String.localizedStringWithFormat("mesh.log.power.config %@".localized, String(nodeNum))
|
||||
MeshLogger.log("🗺️ \(logString)")
|
||||
|
||||
|
|
@ -749,6 +776,10 @@ func upsertPowerConfigPacket(config: Config.PowerConfig, nodeNum: Int64, context
|
|||
fetchedNode[0].powerConfig?.onBatteryShutdownAfterSecs = Int32(truncatingIfNeeded: config.onBatteryShutdownAfterSecs)
|
||||
fetchedNode[0].powerConfig?.waitBluetoothSecs = Int32(truncatingIfNeeded: config.waitBluetoothSecs)
|
||||
}
|
||||
if sessionPasskey != nil {
|
||||
fetchedNode[0].sessionPasskey = sessionPasskey
|
||||
fetchedNode[0].sessionExpiration = Date().addingTimeInterval(300)
|
||||
}
|
||||
do {
|
||||
try context.save()
|
||||
Logger.data.info("💾 [PowerConfigEntity] Updated Power Config for node: \(nodeNum.toHex(), privacy: .public)")
|
||||
|
|
@ -766,7 +797,60 @@ func upsertPowerConfigPacket(config: Config.PowerConfig, nodeNum: Int64, context
|
|||
}
|
||||
}
|
||||
|
||||
func upsertAmbientLightingModuleConfigPacket(config: ModuleConfig.AmbientLightingConfig, nodeNum: Int64, context: NSManagedObjectContext) {
|
||||
func upsertSecurityConfigPacket(config: Config.SecurityConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.security.config %@".localized, String(nodeNum))
|
||||
MeshLogger.log("🛡️ \(logString)")
|
||||
|
||||
let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest()
|
||||
fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum))
|
||||
|
||||
do {
|
||||
let fetchedNode = try context.fetch(fetchNodeInfoRequest)
|
||||
// Found a node, save Security Config
|
||||
if !fetchedNode.isEmpty {
|
||||
if fetchedNode[0].securityConfig == nil {
|
||||
let newSecurityConfig = SecurityConfigEntity(context: context)
|
||||
newSecurityConfig.publicKey = config.publicKey
|
||||
newSecurityConfig.privateKey = config.privateKey
|
||||
newSecurityConfig.adminKey = config.adminKey
|
||||
newSecurityConfig.isManaged = config.isManaged
|
||||
newSecurityConfig.serialEnabled = config.serialEnabled
|
||||
newSecurityConfig.debugLogApiEnabled = config.debugLogApiEnabled
|
||||
newSecurityConfig.bluetoothLoggingEnabled = config.bluetoothLoggingEnabled
|
||||
fetchedNode[0].securityConfig = newSecurityConfig
|
||||
} else {
|
||||
fetchedNode[0].securityConfig?.publicKey = config.publicKey
|
||||
fetchedNode[0].securityConfig?.privateKey = config.privateKey
|
||||
fetchedNode[0].securityConfig?.adminKey = config.adminKey
|
||||
fetchedNode[0].securityConfig?.isManaged = config.isManaged
|
||||
fetchedNode[0].securityConfig?.serialEnabled = config.serialEnabled
|
||||
fetchedNode[0].securityConfig?.debugLogApiEnabled = config.debugLogApiEnabled
|
||||
fetchedNode[0].securityConfig?.bluetoothLoggingEnabled = config.bluetoothLoggingEnabled
|
||||
}
|
||||
if sessionPasskey?.count != 0 {
|
||||
fetchedNode[0].sessionPasskey = sessionPasskey
|
||||
fetchedNode[0].sessionExpiration = Date().addingTimeInterval(300)
|
||||
}
|
||||
do {
|
||||
try context.save()
|
||||
Logger.data.info("💾 [SecurityConfigEntity] Updated Security Config for node: \(nodeNum.toHex(), privacy: .public)")
|
||||
|
||||
} catch {
|
||||
context.rollback()
|
||||
let nsError = error as NSError
|
||||
Logger.data.error("💥 [SecurityConfigEntity] Error Updating Core Data: \(nsError, privacy: .public)")
|
||||
}
|
||||
} else {
|
||||
Logger.data.error("💥 [SecurityConfigEntity] No Nodes found in local database matching node \(nodeNum.toHex(), privacy: .public) unable to save Security Config")
|
||||
}
|
||||
} catch {
|
||||
let nsError = error as NSError
|
||||
Logger.data.error("💥 [SecurityConfigEntity] Fetching node for core data failed: \(nsError, privacy: .public)")
|
||||
}
|
||||
}
|
||||
|
||||
func upsertAmbientLightingModuleConfigPacket(config: ModuleConfig.AmbientLightingConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.ambientlighting.config %@".localized, String(nodeNum))
|
||||
MeshLogger.log("🏮 \(logString)")
|
||||
|
|
@ -780,16 +864,13 @@ func upsertAmbientLightingModuleConfigPacket(config: ModuleConfig.AmbientLightin
|
|||
if !fetchedNode.isEmpty {
|
||||
|
||||
if fetchedNode[0].cannedMessageConfig == nil {
|
||||
|
||||
let newAmbientLightingConfig = AmbientLightingConfigEntity(context: context)
|
||||
|
||||
newAmbientLightingConfig.ledState = config.ledState
|
||||
newAmbientLightingConfig.current = Int32(config.current)
|
||||
newAmbientLightingConfig.red = Int32(config.red)
|
||||
newAmbientLightingConfig.green = Int32(config.green)
|
||||
newAmbientLightingConfig.blue = Int32(config.blue)
|
||||
fetchedNode[0].ambientLightingConfig = newAmbientLightingConfig
|
||||
|
||||
} else {
|
||||
|
||||
if fetchedNode[0].ambientLightingConfig == nil {
|
||||
|
|
@ -801,7 +882,10 @@ func upsertAmbientLightingModuleConfigPacket(config: ModuleConfig.AmbientLightin
|
|||
fetchedNode[0].ambientLightingConfig?.green = Int32(config.green)
|
||||
fetchedNode[0].ambientLightingConfig?.blue = Int32(config.blue)
|
||||
}
|
||||
|
||||
if sessionPasskey != nil {
|
||||
fetchedNode[0].sessionPasskey = sessionPasskey
|
||||
fetchedNode[0].sessionExpiration = Date().addingTimeInterval(300)
|
||||
}
|
||||
do {
|
||||
try context.save()
|
||||
Logger.data.info("💾 [AmbientLightingConfigEntity] Updated for node: \(nodeNum.toHex(), privacy: .public)")
|
||||
|
|
@ -819,7 +903,7 @@ func upsertAmbientLightingModuleConfigPacket(config: ModuleConfig.AmbientLightin
|
|||
}
|
||||
}
|
||||
|
||||
func upsertCannedMessagesModuleConfigPacket(config: ModuleConfig.CannedMessageConfig, nodeNum: Int64, context: NSManagedObjectContext) {
|
||||
func upsertCannedMessagesModuleConfigPacket(config: ModuleConfig.CannedMessageConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.cannedmessage.config %@".localized, String(nodeNum))
|
||||
MeshLogger.log("🥫 \(logString)")
|
||||
|
|
@ -833,9 +917,7 @@ func upsertCannedMessagesModuleConfigPacket(config: ModuleConfig.CannedMessageCo
|
|||
if !fetchedNode.isEmpty {
|
||||
|
||||
if fetchedNode[0].cannedMessageConfig == nil {
|
||||
|
||||
let newCannedMessageConfig = CannedMessageConfigEntity(context: context)
|
||||
|
||||
newCannedMessageConfig.enabled = config.enabled
|
||||
newCannedMessageConfig.sendBell = config.sendBell
|
||||
newCannedMessageConfig.rotary1Enabled = config.rotary1Enabled
|
||||
|
|
@ -846,11 +928,8 @@ func upsertCannedMessagesModuleConfigPacket(config: ModuleConfig.CannedMessageCo
|
|||
newCannedMessageConfig.inputbrokerEventCw = Int32(config.inputbrokerEventCw.rawValue)
|
||||
newCannedMessageConfig.inputbrokerEventCcw = Int32(config.inputbrokerEventCcw.rawValue)
|
||||
newCannedMessageConfig.inputbrokerEventPress = Int32(config.inputbrokerEventPress.rawValue)
|
||||
|
||||
fetchedNode[0].cannedMessageConfig = newCannedMessageConfig
|
||||
|
||||
} else {
|
||||
|
||||
fetchedNode[0].cannedMessageConfig?.enabled = config.enabled
|
||||
fetchedNode[0].cannedMessageConfig?.sendBell = config.sendBell
|
||||
fetchedNode[0].cannedMessageConfig?.rotary1Enabled = config.rotary1Enabled
|
||||
|
|
@ -862,7 +941,10 @@ func upsertCannedMessagesModuleConfigPacket(config: ModuleConfig.CannedMessageCo
|
|||
fetchedNode[0].cannedMessageConfig?.inputbrokerEventCcw = Int32(config.inputbrokerEventCcw.rawValue)
|
||||
fetchedNode[0].cannedMessageConfig?.inputbrokerEventPress = Int32(config.inputbrokerEventPress.rawValue)
|
||||
}
|
||||
|
||||
if sessionPasskey != nil {
|
||||
fetchedNode[0].sessionPasskey = sessionPasskey
|
||||
fetchedNode[0].sessionExpiration = Date().addingTimeInterval(300)
|
||||
}
|
||||
do {
|
||||
try context.save()
|
||||
Logger.data.info("💾 [CannedMessageConfigEntity] Updated for node: \(nodeNum.toHex(), privacy: .public)")
|
||||
|
|
@ -880,7 +962,7 @@ func upsertCannedMessagesModuleConfigPacket(config: ModuleConfig.CannedMessageCo
|
|||
}
|
||||
}
|
||||
|
||||
func upsertDetectionSensorModuleConfigPacket(config: ModuleConfig.DetectionSensorConfig, nodeNum: Int64, context: NSManagedObjectContext) {
|
||||
func upsertDetectionSensorModuleConfigPacket(config: ModuleConfig.DetectionSensorConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.detectionsensor.config %@".localized, String(nodeNum))
|
||||
MeshLogger.log("🕵️ \(logString)")
|
||||
|
|
@ -892,21 +974,17 @@ func upsertDetectionSensorModuleConfigPacket(config: ModuleConfig.DetectionSenso
|
|||
let fetchedNode = try context.fetch(fetchNodeInfoRequest)
|
||||
// Found a node, save Detection Sensor Config
|
||||
if !fetchedNode.isEmpty {
|
||||
|
||||
if fetchedNode[0].detectionSensorConfig == nil {
|
||||
|
||||
let newConfig = DetectionSensorConfigEntity(context: context)
|
||||
newConfig.enabled = config.enabled
|
||||
newConfig.sendBell = config.sendBell
|
||||
newConfig.name = config.name
|
||||
|
||||
newConfig.monitorPin = Int32(config.monitorPin)
|
||||
newConfig.detectionTriggeredHigh = config.detectionTriggeredHigh
|
||||
newConfig.usePullup = config.usePullup
|
||||
newConfig.minimumBroadcastSecs = Int32(truncatingIfNeeded: config.minimumBroadcastSecs)
|
||||
newConfig.stateBroadcastSecs = Int32(truncatingIfNeeded: config.stateBroadcastSecs)
|
||||
fetchedNode[0].detectionSensorConfig = newConfig
|
||||
|
||||
} else {
|
||||
fetchedNode[0].detectionSensorConfig?.enabled = config.enabled
|
||||
fetchedNode[0].detectionSensorConfig?.sendBell = config.sendBell
|
||||
|
|
@ -917,7 +995,10 @@ func upsertDetectionSensorModuleConfigPacket(config: ModuleConfig.DetectionSenso
|
|||
fetchedNode[0].detectionSensorConfig?.minimumBroadcastSecs = Int32(truncatingIfNeeded: config.minimumBroadcastSecs)
|
||||
fetchedNode[0].detectionSensorConfig?.stateBroadcastSecs = Int32(truncatingIfNeeded: config.stateBroadcastSecs)
|
||||
}
|
||||
|
||||
if sessionPasskey != nil {
|
||||
fetchedNode[0].sessionPasskey = sessionPasskey
|
||||
fetchedNode[0].sessionExpiration = Date().addingTimeInterval(300)
|
||||
}
|
||||
do {
|
||||
try context.save()
|
||||
Logger.data.info("💾 [DetectionSensorConfigEntity] Updated for node: \(nodeNum.toHex(), privacy: .public)")
|
||||
|
|
@ -938,7 +1019,7 @@ func upsertDetectionSensorModuleConfigPacket(config: ModuleConfig.DetectionSenso
|
|||
}
|
||||
}
|
||||
|
||||
func upsertExternalNotificationModuleConfigPacket(config: ModuleConfig.ExternalNotificationConfig, nodeNum: Int64, context: NSManagedObjectContext) {
|
||||
func upsertExternalNotificationModuleConfigPacket(config: ModuleConfig.ExternalNotificationConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.externalnotification.config %@".localized, String(nodeNum))
|
||||
MeshLogger.log("📣 \(logString)")
|
||||
|
|
@ -969,7 +1050,6 @@ func upsertExternalNotificationModuleConfigPacket(config: ModuleConfig.ExternalN
|
|||
newExternalNotificationConfig.nagTimeout = Int32(config.nagTimeout)
|
||||
newExternalNotificationConfig.useI2SAsBuzzer = config.useI2SAsBuzzer
|
||||
fetchedNode[0].externalNotificationConfig = newExternalNotificationConfig
|
||||
|
||||
} else {
|
||||
fetchedNode[0].externalNotificationConfig?.enabled = config.enabled
|
||||
fetchedNode[0].externalNotificationConfig?.usePWM = config.usePwm
|
||||
|
|
@ -987,7 +1067,10 @@ func upsertExternalNotificationModuleConfigPacket(config: ModuleConfig.ExternalN
|
|||
fetchedNode[0].externalNotificationConfig?.nagTimeout = Int32(config.nagTimeout)
|
||||
fetchedNode[0].externalNotificationConfig?.useI2SAsBuzzer = config.useI2SAsBuzzer
|
||||
}
|
||||
|
||||
if sessionPasskey != nil {
|
||||
fetchedNode[0].sessionPasskey = sessionPasskey
|
||||
fetchedNode[0].sessionExpiration = Date().addingTimeInterval(300)
|
||||
}
|
||||
do {
|
||||
try context.save()
|
||||
Logger.data.info("💾 [ExternalNotificationConfigEntity] Updated for node: \(nodeNum.toHex(), privacy: .public)")
|
||||
|
|
@ -1005,7 +1088,7 @@ func upsertExternalNotificationModuleConfigPacket(config: ModuleConfig.ExternalN
|
|||
}
|
||||
}
|
||||
|
||||
func upsertPaxCounterModuleConfigPacket(config: ModuleConfig.PaxcounterConfig, nodeNum: Int64, context: NSManagedObjectContext) {
|
||||
func upsertPaxCounterModuleConfigPacket(config: ModuleConfig.PaxcounterConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.paxcounter.config %@".localized, String(nodeNum))
|
||||
MeshLogger.log("🧑🤝🧑 \(logString)")
|
||||
|
|
@ -1017,19 +1100,19 @@ func upsertPaxCounterModuleConfigPacket(config: ModuleConfig.PaxcounterConfig, n
|
|||
let fetchedNode = try context.fetch(fetchNodeInfoRequest)
|
||||
// Found a node, save PAX Counter Config
|
||||
if !fetchedNode.isEmpty {
|
||||
|
||||
if fetchedNode[0].paxCounterConfig == nil {
|
||||
let newPaxCounterConfig = PaxCounterConfigEntity(context: context)
|
||||
newPaxCounterConfig.enabled = config.enabled
|
||||
newPaxCounterConfig.updateInterval = Int32(config.paxcounterUpdateInterval)
|
||||
|
||||
fetchedNode[0].paxCounterConfig = newPaxCounterConfig
|
||||
|
||||
} else {
|
||||
fetchedNode[0].paxCounterConfig?.enabled = config.enabled
|
||||
fetchedNode[0].paxCounterConfig?.updateInterval = Int32(config.paxcounterUpdateInterval)
|
||||
}
|
||||
|
||||
if sessionPasskey != nil {
|
||||
fetchedNode[0].sessionPasskey = sessionPasskey
|
||||
fetchedNode[0].sessionExpiration = Date().addingTimeInterval(300)
|
||||
}
|
||||
do {
|
||||
try context.save()
|
||||
Logger.data.info("💾 [PaxCounterConfigEntity] Updated for node number: \(nodeNum.toHex(), privacy: .public)")
|
||||
|
|
@ -1047,7 +1130,7 @@ func upsertPaxCounterModuleConfigPacket(config: ModuleConfig.PaxcounterConfig, n
|
|||
}
|
||||
}
|
||||
|
||||
func upsertRtttlConfigPacket(ringtone: String, nodeNum: Int64, context: NSManagedObjectContext) {
|
||||
func upsertRtttlConfigPacket(ringtone: String, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.ringtone.config %@".localized, String(nodeNum))
|
||||
MeshLogger.log("⛰️ \(logString)")
|
||||
|
|
@ -1066,6 +1149,10 @@ func upsertRtttlConfigPacket(ringtone: String, nodeNum: Int64, context: NSManage
|
|||
} else {
|
||||
fetchedNode[0].rtttlConfig?.ringtone = ringtone
|
||||
}
|
||||
if sessionPasskey != nil {
|
||||
fetchedNode[0].sessionPasskey = sessionPasskey
|
||||
fetchedNode[0].sessionExpiration = Date().addingTimeInterval(300)
|
||||
}
|
||||
do {
|
||||
try context.save()
|
||||
Logger.data.info("💾 [RtttlConfigEntity] Updated for node: \(nodeNum.toHex(), privacy: .public)")
|
||||
|
|
@ -1083,7 +1170,7 @@ func upsertRtttlConfigPacket(ringtone: String, nodeNum: Int64, context: NSManage
|
|||
}
|
||||
}
|
||||
|
||||
func upsertMqttModuleConfigPacket(config: ModuleConfig.MQTTConfig, nodeNum: Int64, context: NSManagedObjectContext) {
|
||||
func upsertMqttModuleConfigPacket(config: ModuleConfig.MQTTConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.mqtt.config %@".localized, String(nodeNum))
|
||||
MeshLogger.log("🌉 \(logString)")
|
||||
|
|
@ -1095,7 +1182,6 @@ func upsertMqttModuleConfigPacket(config: ModuleConfig.MQTTConfig, nodeNum: Int6
|
|||
let fetchedNode = try context.fetch(fetchNodeInfoRequest)
|
||||
// Found a node, save MQTT Config
|
||||
if !fetchedNode.isEmpty {
|
||||
|
||||
if fetchedNode[0].mqttConfig == nil {
|
||||
let newMQTTConfig = MQTTConfigEntity(context: context)
|
||||
newMQTTConfig.enabled = config.enabled
|
||||
|
|
@ -1125,6 +1211,10 @@ func upsertMqttModuleConfigPacket(config: ModuleConfig.MQTTConfig, nodeNum: Int6
|
|||
fetchedNode[0].mqttConfig?.mapPositionPrecision = Int32(config.mapReportSettings.positionPrecision)
|
||||
fetchedNode[0].mqttConfig?.mapPublishIntervalSecs = Int32(config.mapReportSettings.publishIntervalSecs)
|
||||
}
|
||||
if sessionPasskey != nil {
|
||||
fetchedNode[0].sessionPasskey = sessionPasskey
|
||||
fetchedNode[0].sessionExpiration = Date().addingTimeInterval(300)
|
||||
}
|
||||
do {
|
||||
try context.save()
|
||||
Logger.data.info("💾 [MQTTConfigEntity] Updated for node number: \(nodeNum.toHex(), privacy: .public)")
|
||||
|
|
@ -1142,7 +1232,7 @@ func upsertMqttModuleConfigPacket(config: ModuleConfig.MQTTConfig, nodeNum: Int6
|
|||
}
|
||||
}
|
||||
|
||||
func upsertRangeTestModuleConfigPacket(config: ModuleConfig.RangeTestConfig, nodeNum: Int64, context: NSManagedObjectContext) {
|
||||
func upsertRangeTestModuleConfigPacket(config: ModuleConfig.RangeTestConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.rangetest.config %@".localized, String(nodeNum))
|
||||
MeshLogger.log("⛰️ \(logString)")
|
||||
|
|
@ -1165,6 +1255,10 @@ func upsertRangeTestModuleConfigPacket(config: ModuleConfig.RangeTestConfig, nod
|
|||
fetchedNode[0].rangeTestConfig?.enabled = config.enabled
|
||||
fetchedNode[0].rangeTestConfig?.save = config.save
|
||||
}
|
||||
if sessionPasskey != nil {
|
||||
fetchedNode[0].sessionPasskey = sessionPasskey
|
||||
fetchedNode[0].sessionExpiration = Date().addingTimeInterval(300)
|
||||
}
|
||||
do {
|
||||
try context.save()
|
||||
Logger.data.info("💾 [RangeTestConfigEntity] Updated for node: \(nodeNum.toHex(), privacy: .public)")
|
||||
|
|
@ -1182,7 +1276,7 @@ func upsertRangeTestModuleConfigPacket(config: ModuleConfig.RangeTestConfig, nod
|
|||
}
|
||||
}
|
||||
|
||||
func upsertSerialModuleConfigPacket(config: ModuleConfig.SerialConfig, nodeNum: Int64, context: NSManagedObjectContext) {
|
||||
func upsertSerialModuleConfigPacket(config: ModuleConfig.SerialConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.serial.config %@".localized, String(nodeNum))
|
||||
MeshLogger.log("🤖 \(logString)")
|
||||
|
|
@ -1194,9 +1288,7 @@ func upsertSerialModuleConfigPacket(config: ModuleConfig.SerialConfig, nodeNum:
|
|||
let fetchedNode = try context.fetch(fetchNodeInfoRequest)
|
||||
// Found a node, save Device Config
|
||||
if !fetchedNode.isEmpty {
|
||||
|
||||
if fetchedNode[0].serialConfig == nil {
|
||||
|
||||
let newSerialConfig = SerialConfigEntity(context: context)
|
||||
newSerialConfig.enabled = config.enabled
|
||||
newSerialConfig.echo = config.echo
|
||||
|
|
@ -1206,7 +1298,6 @@ func upsertSerialModuleConfigPacket(config: ModuleConfig.SerialConfig, nodeNum:
|
|||
newSerialConfig.timeout = Int32(config.timeout)
|
||||
newSerialConfig.mode = Int32(config.mode.rawValue)
|
||||
fetchedNode[0].serialConfig = newSerialConfig
|
||||
|
||||
} else {
|
||||
fetchedNode[0].serialConfig?.enabled = config.enabled
|
||||
fetchedNode[0].serialConfig?.echo = config.echo
|
||||
|
|
@ -1216,7 +1307,10 @@ func upsertSerialModuleConfigPacket(config: ModuleConfig.SerialConfig, nodeNum:
|
|||
fetchedNode[0].serialConfig?.timeout = Int32(config.timeout)
|
||||
fetchedNode[0].serialConfig?.mode = Int32(config.mode.rawValue)
|
||||
}
|
||||
|
||||
if sessionPasskey != nil {
|
||||
fetchedNode[0].sessionPasskey = sessionPasskey
|
||||
fetchedNode[0].sessionExpiration = Date().addingTimeInterval(300)
|
||||
}
|
||||
do {
|
||||
try context.save()
|
||||
Logger.data.info("💾 [SerialConfigEntity]Updated Serial Module Config for node: \(nodeNum.toHex(), privacy: .public)")
|
||||
|
|
@ -1237,7 +1331,7 @@ func upsertSerialModuleConfigPacket(config: ModuleConfig.SerialConfig, nodeNum:
|
|||
}
|
||||
}
|
||||
|
||||
func upsertStoreForwardModuleConfigPacket(config: ModuleConfig.StoreForwardConfig, nodeNum: Int64, context: NSManagedObjectContext) {
|
||||
func upsertStoreForwardModuleConfigPacket(config: ModuleConfig.StoreForwardConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.storeforward.config %@".localized, String(nodeNum))
|
||||
MeshLogger.log("📬 \(logString)")
|
||||
|
|
@ -1249,9 +1343,7 @@ func upsertStoreForwardModuleConfigPacket(config: ModuleConfig.StoreForwardConfi
|
|||
let fetchedNode = try context.fetch(fetchNodeInfoRequest)
|
||||
// Found a node, save Store & Forward Sensor Config
|
||||
if !fetchedNode.isEmpty {
|
||||
|
||||
if fetchedNode[0].storeForwardConfig == nil {
|
||||
|
||||
let newConfig = StoreForwardConfigEntity(context: context)
|
||||
newConfig.enabled = config.enabled
|
||||
newConfig.heartbeat = config.heartbeat
|
||||
|
|
@ -1259,7 +1351,6 @@ func upsertStoreForwardModuleConfigPacket(config: ModuleConfig.StoreForwardConfi
|
|||
newConfig.historyReturnMax = Int32(config.historyReturnMax)
|
||||
newConfig.historyReturnWindow = Int32(config.historyReturnWindow)
|
||||
fetchedNode[0].storeForwardConfig = newConfig
|
||||
|
||||
} else {
|
||||
fetchedNode[0].storeForwardConfig?.enabled = config.enabled
|
||||
fetchedNode[0].storeForwardConfig?.heartbeat = config.heartbeat
|
||||
|
|
@ -1267,6 +1358,10 @@ func upsertStoreForwardModuleConfigPacket(config: ModuleConfig.StoreForwardConfi
|
|||
fetchedNode[0].storeForwardConfig?.historyReturnMax = Int32(config.historyReturnMax)
|
||||
fetchedNode[0].storeForwardConfig?.historyReturnWindow = Int32(config.historyReturnWindow)
|
||||
}
|
||||
if sessionPasskey != nil {
|
||||
fetchedNode[0].sessionPasskey = sessionPasskey
|
||||
fetchedNode[0].sessionExpiration = Date().addingTimeInterval(300)
|
||||
}
|
||||
do {
|
||||
try context.save()
|
||||
Logger.data.info("💾 [StoreForwardConfigEntity] Updated for node: \(nodeNum.toHex(), privacy: .public)")
|
||||
|
|
@ -1284,21 +1379,18 @@ func upsertStoreForwardModuleConfigPacket(config: ModuleConfig.StoreForwardConfi
|
|||
}
|
||||
}
|
||||
|
||||
func upsertTelemetryModuleConfigPacket(config: ModuleConfig.TelemetryConfig, nodeNum: Int64, context: NSManagedObjectContext) {
|
||||
func upsertTelemetryModuleConfigPacket(config: ModuleConfig.TelemetryConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.telemetry.config %@".localized, String(nodeNum))
|
||||
MeshLogger.log("📈 \(logString)")
|
||||
|
||||
let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest()
|
||||
fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum))
|
||||
|
||||
do {
|
||||
let fetchedNode = try context.fetch(fetchNodeInfoRequest)
|
||||
// Found a node, save Telemetry Config
|
||||
if !fetchedNode.isEmpty {
|
||||
|
||||
if fetchedNode[0].telemetryConfig == nil {
|
||||
|
||||
let newTelemetryConfig = TelemetryConfigEntity(context: context)
|
||||
newTelemetryConfig.deviceUpdateInterval = Int32(config.deviceUpdateInterval)
|
||||
newTelemetryConfig.environmentUpdateInterval = Int32(config.environmentUpdateInterval)
|
||||
|
|
@ -1309,7 +1401,6 @@ func upsertTelemetryModuleConfigPacket(config: ModuleConfig.TelemetryConfig, nod
|
|||
newTelemetryConfig.powerUpdateInterval = Int32(config.powerUpdateInterval)
|
||||
newTelemetryConfig.powerScreenEnabled = config.powerScreenEnabled
|
||||
fetchedNode[0].telemetryConfig = newTelemetryConfig
|
||||
|
||||
} else {
|
||||
fetchedNode[0].telemetryConfig?.deviceUpdateInterval = Int32(config.deviceUpdateInterval)
|
||||
fetchedNode[0].telemetryConfig?.environmentUpdateInterval = Int32(config.environmentUpdateInterval)
|
||||
|
|
@ -1320,7 +1411,10 @@ func upsertTelemetryModuleConfigPacket(config: ModuleConfig.TelemetryConfig, nod
|
|||
fetchedNode[0].telemetryConfig?.powerUpdateInterval = Int32(config.powerUpdateInterval)
|
||||
fetchedNode[0].telemetryConfig?.powerScreenEnabled = config.powerScreenEnabled
|
||||
}
|
||||
|
||||
if sessionPasskey != nil {
|
||||
fetchedNode[0].sessionPasskey = sessionPasskey
|
||||
fetchedNode[0].sessionExpiration = Date().addingTimeInterval(300)
|
||||
}
|
||||
do {
|
||||
try context.save()
|
||||
Logger.data.info("💾 [TelemetryConfigEntity] Updated Telemetry Module Config for node: \(nodeNum.toHex(), privacy: .public)")
|
||||
|
|
|
|||
|
|
@ -370,7 +370,7 @@
|
|||
{
|
||||
"hwModel": 66,
|
||||
"hwModelSlug": "HELTEC_VISION_MASTER_T190",
|
||||
"platformioTarget": "heltec-vision-master-T190",
|
||||
"platformioTarget": "heltec-vision-master-t190",
|
||||
"architecture": "esp32-s3",
|
||||
"activelySupported": true,
|
||||
"displayName": "Heltec Vision Master T190"
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ enum SettingsNavigationState: String {
|
|||
case paxCounter
|
||||
case ringtone
|
||||
case serial
|
||||
case security
|
||||
case storeAndForward
|
||||
case telemetry
|
||||
case meshLog
|
||||
|
|
|
|||
|
|
@ -25,22 +25,3 @@ struct MessagesTip: Tip {
|
|||
Image(systemName: "bubble.left.and.bubble.right")
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 17.0, macOS 14.0, *)
|
||||
struct ContactsTip: Tip {
|
||||
|
||||
var id: String {
|
||||
return "tip.messages.contacts"
|
||||
}
|
||||
var title: Text {
|
||||
// Text("tip.messages.contacts.title")
|
||||
Text("Contacts")
|
||||
}
|
||||
var message: Text? {
|
||||
// Text("tip.messages.contacts.message")
|
||||
Text("Each node is an available contact. Contacts with recent messages or marked as favorites show up at the top of the list. Select a contact to send or view messages. Long press to favorite or mute the contact or delete the conversation.")
|
||||
}
|
||||
var image: Image? {
|
||||
Image(systemName: "person.circle")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
import SwiftUI
|
||||
|
||||
struct BatteryCompact: View {
|
||||
@State var batteryLevel: Int32
|
||||
var batteryLevel: Int32
|
||||
var font: Font
|
||||
var iconFont: Font
|
||||
var color: Color
|
||||
|
|
|
|||
|
|
@ -1,25 +0,0 @@
|
|||
//
|
||||
// BatteryIcon.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Copyright Garth Vander Houwen 3/24/23.
|
||||
//
|
||||
import SwiftUI
|
||||
|
||||
struct BatteryLevelCompact: View {
|
||||
|
||||
@ObservedObject var node: NodeInfoEntity
|
||||
|
||||
var font: Font
|
||||
var iconFont: Font
|
||||
var color: Color
|
||||
|
||||
var body: some View {
|
||||
let deviceMetrics = node.telemetries?.filtered(using: NSPredicate(format: "metricsType == 0"))
|
||||
let mostRecent = deviceMetrics?.lastObject as? TelemetryEntity
|
||||
let batteryLevel = mostRecent?.batteryLevel ?? 0
|
||||
if deviceMetrics?.count ?? 0 > 0 {
|
||||
BatteryCompact(batteryLevel: batteryLevel, font: font, iconFont: iconFont, color: color)
|
||||
}
|
||||
}
|
||||
}
|
||||
44
Meshtastic/Views/Helpers/Help/AckErrors.swift
Normal file
44
Meshtastic/Views/Helpers/Help/AckErrors.swift
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
//
|
||||
// IAQScale.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Copyright Garth Vander Houwen 4/24/24.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct AckErrors: View {
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
Text("Message Status Options")
|
||||
.font(.title2)
|
||||
HStack {
|
||||
RoundedRectangle(cornerRadius: 5)
|
||||
.fill(.orange)
|
||||
.frame(width: 20, height: 12)
|
||||
Text("Acknowledged by another node")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.orange)
|
||||
}
|
||||
ForEach(RoutingError.allCases) { re in
|
||||
HStack {
|
||||
RoundedRectangle(cornerRadius: 5)
|
||||
.fill(re.color)
|
||||
.frame(width: 20, height: 12)
|
||||
Text(re.display)
|
||||
.font(.caption)
|
||||
.foregroundStyle(re.color)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct AckErrorsPreviews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
VStack {
|
||||
AckErrors()
|
||||
}
|
||||
}
|
||||
}
|
||||
76
Meshtastic/Views/Helpers/Help/DirectMessagesHelp.swift
Normal file
76
Meshtastic/Views/Helpers/Help/DirectMessagesHelp.swift
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
//
|
||||
// DirectMessagesHelp.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Copyright Garth Vander Houwen on 8/15/24.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct DirectMessagesHelp: View {
|
||||
private var idiom: UIUserInterfaceIdiom { UIDevice.current.userInterfaceIdiom }
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
Label("Direct Message Help", systemImage: "questionmark.circle")
|
||||
.font(.title)
|
||||
.padding(.vertical)
|
||||
VStack(alignment: .leading) {
|
||||
HStack {
|
||||
Image(systemName: "star.fill")
|
||||
.foregroundColor(.yellow)
|
||||
.padding(.bottom)
|
||||
Text("Favorites and nodes with recent messages show up at the top of the contact list.")
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.padding(.bottom)
|
||||
}
|
||||
HStack {
|
||||
Image(systemName: "hand.tap")
|
||||
.padding(.bottom)
|
||||
Text("Long press to favorite or mute the contact or delete a conversation.")
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.padding(.bottom)
|
||||
}
|
||||
}
|
||||
if idiom == .phone {
|
||||
VStack(alignment: .leading) {
|
||||
LockLegend()
|
||||
AckErrors()
|
||||
}
|
||||
} else {
|
||||
HStack(alignment: .top) {
|
||||
LockLegend()
|
||||
AckErrors()
|
||||
.padding(.trailing)
|
||||
}
|
||||
}
|
||||
#if targetEnvironment(macCatalyst)
|
||||
Spacer()
|
||||
Button {
|
||||
dismiss()
|
||||
} label: {
|
||||
Label("close", systemImage: "xmark")
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
.buttonBorderShape(.capsule)
|
||||
.controlSize(.large)
|
||||
.padding(.bottom)
|
||||
#endif
|
||||
}
|
||||
.frame(minHeight: 0, maxHeight: .infinity, alignment: .leading)
|
||||
.padding()
|
||||
.presentationDetents([.large])
|
||||
.presentationContentInteraction(.scrolls)
|
||||
.presentationDragIndicator(.visible)
|
||||
.presentationBackgroundInteraction(.enabled(upThrough: .large))
|
||||
}
|
||||
}
|
||||
|
||||
struct DirectMessagesHelpPreviews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
VStack {
|
||||
AckErrors()
|
||||
}
|
||||
}
|
||||
}
|
||||
66
Meshtastic/Views/Helpers/Help/LockLegend.swift
Normal file
66
Meshtastic/Views/Helpers/Help/LockLegend.swift
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
//
|
||||
// LockLegend.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Copyright Garth Vander Houwen 8/15/24.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct LockLegend: View {
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
Text("What does the lock mean?")
|
||||
.font(.title2)
|
||||
.padding(.bottom, 5)
|
||||
VStack(alignment: .leading) {
|
||||
HStack {
|
||||
Image(systemName: "lock.open.fill")
|
||||
.foregroundColor(.yellow)
|
||||
Text("Shared Key")
|
||||
.fontWeight(.semibold)
|
||||
}
|
||||
Text("Direct messages are using the shared key for the channel.")
|
||||
.allowsTightening(/*@START_MENU_TOKEN@*/true/*@END_MENU_TOKEN@*/)
|
||||
.font(.callout)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
}
|
||||
.padding(.bottom)
|
||||
VStack(alignment: .leading) {
|
||||
HStack {
|
||||
Image(systemName: "lock.fill")
|
||||
.foregroundColor(.green)
|
||||
Text("Public Key Encryption")
|
||||
.fontWeight(.semibold)
|
||||
}
|
||||
Text("Direct messages are using the new public key infrastructure for encryption. Reguires firmware version 2.5 or greater.")
|
||||
.allowsTightening(/*@START_MENU_TOKEN@*/true/*@END_MENU_TOKEN@*/)
|
||||
.font(.callout)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
}
|
||||
.padding(.bottom)
|
||||
VStack(alignment: .leading) {
|
||||
HStack {
|
||||
Image(systemName: "key.slash")
|
||||
.foregroundColor(.red)
|
||||
Text("Public Key Mismatch")
|
||||
.fontWeight(.semibold)
|
||||
}
|
||||
Text("The public key does not match the recorded key. You may delete the node and let it exchange keys again, but this may indicate a more serious security problem. Contact the user through another trusted channel, to determine if the key change was due to a factory reset or other intentional action.")
|
||||
.allowsTightening(/*@START_MENU_TOKEN@*/true/*@END_MENU_TOKEN@*/)
|
||||
.font(.callout)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
}
|
||||
.padding(.bottom)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct LockLegendPreviews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
VStack {
|
||||
LockLegend()
|
||||
}
|
||||
}
|
||||
}
|
||||
57
Meshtastic/Views/Helpers/SecureInput.swift
Normal file
57
Meshtastic/Views/Helpers/SecureInput.swift
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
//
|
||||
// SecureInput.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Copyright(c) Garth Vander Houwen 8/12/24.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct SecureInput: View {
|
||||
|
||||
private var idiom: UIUserInterfaceIdiom { UIDevice.current.userInterfaceIdiom }
|
||||
@Binding private var text: String
|
||||
@State private var isSecure: Bool = true
|
||||
private var title: String
|
||||
|
||||
init(_ title: String, text: Binding<String>) {
|
||||
self.title = title
|
||||
self._text = text
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ZStack(alignment: .trailing) {
|
||||
Group {
|
||||
if isSecure {
|
||||
SecureField(title, text: $text)
|
||||
.font(idiom == .phone ? .caption : .callout)
|
||||
.allowsTightening(true)
|
||||
.monospaced()
|
||||
.keyboardType(.alphabet)
|
||||
.foregroundStyle(.tertiary)
|
||||
.disableAutocorrection(true)
|
||||
.textSelection(.enabled)
|
||||
} else {
|
||||
TextField(title, text: $text, axis: .vertical)
|
||||
.font(idiom == .phone ? .caption : .callout)
|
||||
.allowsTightening(true)
|
||||
.monospaced()
|
||||
.keyboardType(.alphabet)
|
||||
.foregroundStyle(.tertiary)
|
||||
.disableAutocorrection(true)
|
||||
.textSelection(.enabled)
|
||||
.lineLimit(...3)
|
||||
}
|
||||
}.padding(.trailing, 36)
|
||||
|
||||
if !text.isEmpty {
|
||||
Button(action: {
|
||||
isSecure.toggle()
|
||||
}) {
|
||||
Image(systemName: self.isSecure ? "eye.slash" : "eye")
|
||||
.accentColor(.secondary)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -101,7 +101,7 @@ struct WeatherConditionsCompactWidget: View {
|
|||
.font(temperature.length < 4 ? .system(size: 90) : .system(size: 60) )
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.frame(height: 175)
|
||||
.frame(height: 150)
|
||||
.background(.tertiary, in: RoundedRectangle(cornerRadius: 20, style: .continuous))
|
||||
}
|
||||
}
|
||||
|
|
@ -123,7 +123,7 @@ struct HumidityCompactWidget: View {
|
|||
}
|
||||
.padding(.horizontal)
|
||||
.frame(maxWidth: .infinity)
|
||||
.frame(height: 175)
|
||||
.frame(height: 150)
|
||||
.background(.tertiary, in: RoundedRectangle(cornerRadius: 20, style: .continuous))
|
||||
}
|
||||
}
|
||||
|
|
@ -135,16 +135,15 @@ struct PressureCompactWidget: View {
|
|||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
Label { Text("PRESSURE") } icon: { Image(systemName: "gauge").symbolRenderingMode(.multicolor) }
|
||||
.font(.caption2)
|
||||
.font(.callout)
|
||||
Text(pressure)
|
||||
.font(pressure.length < 7 ? .system(size: 35) : .system(size: 30) )
|
||||
Text(low ? "LOW" : "HIGH")
|
||||
.padding(.bottom)
|
||||
Text(unit)
|
||||
}
|
||||
.padding(.horizontal)
|
||||
.frame(maxWidth: .infinity)
|
||||
.frame(height: 175)
|
||||
.padding(.horizontal, 5)
|
||||
.frame(width: 175, height: 175)
|
||||
.background(.tertiary, in: RoundedRectangle(cornerRadius: 20, style: .continuous))
|
||||
}
|
||||
}
|
||||
|
|
@ -156,17 +155,14 @@ struct WindCompactWidget: View {
|
|||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
Label { Text("WIND") } icon: { Image(systemName: "wind").foregroundColor(.accentColor) }
|
||||
.font(.caption)
|
||||
Text("\(direction)")
|
||||
.font(.caption)
|
||||
.padding(.bottom, 10)
|
||||
Text(speed)
|
||||
.font(.system(size: 35))
|
||||
Text("Gusts \(gust)")
|
||||
}
|
||||
.padding(.horizontal)
|
||||
.frame(maxWidth: .infinity)
|
||||
.frame(height: 175)
|
||||
//.padding(.horizontal)
|
||||
.frame(width: 175, height: 175)
|
||||
.background(.tertiary, in: RoundedRectangle(cornerRadius: 20, style: .continuous))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -84,16 +84,14 @@ struct ChannelMessageList: View {
|
|||
}
|
||||
|
||||
HStack {
|
||||
if currentUser && message.receivedACK {
|
||||
// Ack Received
|
||||
Text("Acknowledged").font(.caption2).foregroundColor(.gray)
|
||||
} else if currentUser && message.ackError == 0 {
|
||||
if currentUser && message.ackError == 0 {
|
||||
// Empty Error
|
||||
Text("Waiting to be acknowledged. . .").font(.caption2).foregroundColor(.orange)
|
||||
} else if currentUser && message.ackError > 0 {
|
||||
let ackErrorVal = RoutingError(rawValue: Int(message.ackError))
|
||||
Text("\(ackErrorVal?.display ?? "Empty Ack Error")").fixedSize(horizontal: false, vertical: true)
|
||||
.font(.caption2).foregroundColor(.red)
|
||||
.foregroundStyle(ackErrorVal?.color ?? .red)
|
||||
.font(.caption2)
|
||||
} else if isDetectionSensorMessage {
|
||||
let messageDate = message.timestamp
|
||||
Text(" \(messageDate.formattedDate(format: MessageText.dateFormatString))").font(.caption2).foregroundColor(.gray)
|
||||
|
|
|
|||
|
|
@ -13,6 +13,9 @@ struct MessageContextMenuItems: View {
|
|||
|
||||
var body: some View {
|
||||
VStack {
|
||||
if message.pkiEncrypted {
|
||||
Label("Encrypted", systemImage: "lock")
|
||||
}
|
||||
Text("channel") + Text(": \(message.channel)")
|
||||
}
|
||||
|
||||
|
|
@ -53,6 +56,7 @@ struct MessageContextMenuItems: View {
|
|||
let messageDate = Date(timeIntervalSince1970: TimeInterval(message.messageTimestamp))
|
||||
Text("\(messageDate.formattedDate(format: MessageText.dateFormatString))").foregroundColor(.gray)
|
||||
}
|
||||
|
||||
if !isCurrentUser && !(message.fromUser?.userNode?.viaMqtt ?? false) && message.fromUser?.userNode?.hopsAway ?? -1 == 0 {
|
||||
VStack {
|
||||
Text("SNR \(String(format: "%.2f", message.snr)) dB")
|
||||
|
|
@ -60,7 +64,7 @@ struct MessageContextMenuItems: View {
|
|||
}
|
||||
} else if !isCurrentUser && !(message.fromUser?.userNode?.viaMqtt ?? false) {
|
||||
VStack {
|
||||
Text("Hops Away \(message.fromUser?.userNode?.hopsAway ?? 0)) dB")
|
||||
Text("Hops Away \(message.fromUser?.userNode?.hopsAway ?? 0)")
|
||||
}
|
||||
}
|
||||
if isCurrentUser && message.receivedACK {
|
||||
|
|
|
|||
|
|
@ -24,11 +24,25 @@ struct MessageText: View {
|
|||
let markdownText = LocalizedStringKey(message.messagePayloadMarkdown ?? (message.messagePayload ?? "EMPTY MESSAGE"))
|
||||
return Text(markdownText)
|
||||
.tint(Self.linkBlue)
|
||||
.padding(10)
|
||||
.padding(.vertical, 10)
|
||||
.padding(.horizontal, 8)
|
||||
.foregroundColor(.white)
|
||||
.background(isCurrentUser ? .accentColor : Color(.gray))
|
||||
.cornerRadius(15)
|
||||
.overlay {
|
||||
if message.pkiEncrypted {
|
||||
VStack(alignment: .trailing) {
|
||||
Spacer()
|
||||
HStack {
|
||||
Spacer()
|
||||
Image(systemName: "lock.circle.fill")
|
||||
.symbolRenderingMode(.palette)
|
||||
.foregroundStyle(.white, .green)
|
||||
.font(.system(size: 20))
|
||||
.offset(x: 8, y: 8)
|
||||
}
|
||||
}
|
||||
}
|
||||
let isDetectionSensorMessage = message.portNum == Int32(PortNum.detectionSensorApp.rawValue)
|
||||
if tapBackDestination.overlaySensorMessage {
|
||||
VStack {
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ struct UserList: View {
|
|||
@State private var viaLora = true
|
||||
@State private var viaMqtt = true
|
||||
@State private var isOnline = false
|
||||
@State private var isPkiEncrypted = false
|
||||
@State private var isFavorite = false
|
||||
@State private var isEnvironment = false
|
||||
@State private var distanceFilter = false
|
||||
|
|
@ -27,11 +28,23 @@ struct UserList: View {
|
|||
@State private var hopsAway: Double = -1.0
|
||||
@State private var roleFilter = false
|
||||
@State private var deviceRoles: Set<Int> = []
|
||||
@State var isEditingFilters = false
|
||||
@State private var editingFilters = false
|
||||
@State private var showingHelp = false
|
||||
@State private var showingTrustConfirm: Bool = false
|
||||
|
||||
var boolFilters: [Bool] {[
|
||||
isFavorite,
|
||||
isOnline,
|
||||
isPkiEncrypted,
|
||||
isEnvironment,
|
||||
distanceFilter,
|
||||
roleFilter
|
||||
]}
|
||||
|
||||
@FetchRequest(
|
||||
sortDescriptors: [NSSortDescriptor(key: "lastMessage", ascending: false),
|
||||
NSSortDescriptor(key: "userNode.favorite", ascending: false),
|
||||
NSSortDescriptor(key: "pkiEncrypted", ascending: false),
|
||||
NSSortDescriptor(key: "longName", ascending: true)],
|
||||
animation: .default
|
||||
)
|
||||
|
|
@ -47,9 +60,6 @@ struct UserList: View {
|
|||
let dateFormatString = (localeDateFormat ?? "MM/dd/YY")
|
||||
VStack {
|
||||
List(selection: $userSelection) {
|
||||
if #available(iOS 17.0, macOS 14.0, *) {
|
||||
TipView(ContactsTip(), arrowEdge: .bottom)
|
||||
}
|
||||
ForEach(users) { (user: UserEntity) in
|
||||
let mostRecent = user.messageList.last
|
||||
let lastMessageTime = Date(timeIntervalSince1970: TimeInterval(Int64((mostRecent?.messageTimestamp ?? 0 ))))
|
||||
|
|
@ -69,8 +79,22 @@ struct UserList: View {
|
|||
|
||||
VStack(alignment: .leading) {
|
||||
HStack {
|
||||
if user.pkiEncrypted {
|
||||
if !user.keyMatch {
|
||||
/// Public Key on the User and the Public Key on the Last Message don't match
|
||||
Image(systemName: "key.slash")
|
||||
.foregroundColor(.red)
|
||||
} else {
|
||||
Image(systemName: "lock.fill")
|
||||
.foregroundColor(.green)
|
||||
}
|
||||
} else {
|
||||
Image(systemName: "lock.open.fill")
|
||||
.foregroundColor(.yellow)
|
||||
}
|
||||
Text(user.longName ?? "unknown".localized)
|
||||
.font(.headline)
|
||||
.allowsTightening(true)
|
||||
Spacer()
|
||||
if user.userNode?.favorite ?? false {
|
||||
Image(systemName: "star.fill")
|
||||
|
|
@ -169,9 +193,12 @@ struct UserList: View {
|
|||
}
|
||||
}
|
||||
.listStyle(.plain)
|
||||
.navigationTitle(String.localizedStringWithFormat("contacts %@".localized, String(users.count == 0 ? 0 : users.count - 1)))
|
||||
.sheet(isPresented: $isEditingFilters) {
|
||||
NodeListFilter(filterTitle: "Contact Filters", viaLora: $viaLora, viaMqtt: $viaMqtt, isOnline: $isOnline, isFavorite: $isFavorite, isEnvironment: $isEnvironment, distanceFilter: $distanceFilter, maximumDistance: $maxDistance, hopsAway: $hopsAway, roleFilter: $roleFilter, deviceRoles: $deviceRoles)
|
||||
.navigationTitle(String.localizedStringWithFormat("contacts %@".localized, String(users.count == 0 ? 0 : users.count)))
|
||||
.sheet(isPresented: $editingFilters) {
|
||||
NodeListFilter(filterTitle: "Contact Filters", viaLora: $viaLora, viaMqtt: $viaMqtt, isOnline: $isOnline, isPkiEncrypted: $isPkiEncrypted, isFavorite: $isFavorite, isEnvironment: $isEnvironment, distanceFilter: $distanceFilter, maximumDistance: $maxDistance, hopsAway: $hopsAway, roleFilter: $roleFilter, deviceRoles: $deviceRoles)
|
||||
}
|
||||
.sheet(isPresented: $showingHelp) {
|
||||
DirectMessagesHelp()
|
||||
}
|
||||
.onChange(of: searchText) { _ in
|
||||
searchUserList()
|
||||
|
|
@ -194,40 +221,46 @@ struct UserList: View {
|
|||
.onChange(of: hopsAway) { _ in
|
||||
searchUserList()
|
||||
}
|
||||
.onChange(of: isOnline) { _ in
|
||||
searchUserList()
|
||||
}
|
||||
.onChange(of: isFavorite) { _ in
|
||||
.onChange(of: [boolFilters]) { _ in
|
||||
searchUserList()
|
||||
}
|
||||
.onChange(of: maxDistance) { _ in
|
||||
searchUserList()
|
||||
}
|
||||
.onChange(of: distanceFilter) { _ in
|
||||
.onFirstAppear {
|
||||
searchUserList()
|
||||
}
|
||||
.onAppear {
|
||||
searchUserList()
|
||||
}
|
||||
.safeAreaInset(edge: .bottom, alignment: .trailing) {
|
||||
.safeAreaInset(edge: .bottom, alignment: .leading) {
|
||||
HStack {
|
||||
Button(action: {
|
||||
withAnimation {
|
||||
isEditingFilters = !isEditingFilters
|
||||
showingHelp = !showingHelp
|
||||
}
|
||||
}) {
|
||||
Image(systemName: !isEditingFilters ? "line.3.horizontal.decrease.circle" : "line.3.horizontal.decrease.circle.fill")
|
||||
Image(systemName: !editingFilters ? "questionmark.circle" : "questionmark.circle.fill")
|
||||
.padding(.vertical, 5)
|
||||
}
|
||||
.tint(Color(UIColor.secondarySystemBackground))
|
||||
.foregroundColor(.accentColor)
|
||||
.buttonStyle(.borderedProminent)
|
||||
Spacer()
|
||||
Button(action: {
|
||||
withAnimation {
|
||||
editingFilters = !editingFilters
|
||||
}
|
||||
}) {
|
||||
Image(systemName: !editingFilters ? "line.3.horizontal.decrease.circle" : "line.3.horizontal.decrease.circle.fill")
|
||||
.padding(.vertical, 5)
|
||||
}
|
||||
.tint(Color(UIColor.secondarySystemBackground))
|
||||
.foregroundColor(.accentColor)
|
||||
.buttonStyle(.borderedProminent)
|
||||
|
||||
}
|
||||
.controlSize(.regular)
|
||||
.padding(5)
|
||||
}
|
||||
.padding(.bottom, 5)
|
||||
.padding(.bottom, 5)
|
||||
.searchable(text: $searchText, placement: users.count > 10 ? .navigationBarDrawer(displayMode: .always) : .automatic, prompt: "Find a contact")
|
||||
.disableAutocorrection(true)
|
||||
.scrollDismissesKeyboard(.immediately)
|
||||
|
|
@ -277,6 +310,11 @@ struct UserList: View {
|
|||
let isOnlinePredicate = NSPredicate(format: "userNode.lastHeard >= %@", Calendar.current.date(byAdding: .minute, value: -15, to: Date())! as NSDate)
|
||||
predicates.append(isOnlinePredicate)
|
||||
}
|
||||
/// Encrypted
|
||||
if isPkiEncrypted {
|
||||
let isPkiEncryptedPredicate = NSPredicate(format: "pkiEncrypted == YES")
|
||||
predicates.append(isPkiEncryptedPredicate)
|
||||
}
|
||||
/// Favorites
|
||||
if isFavorite {
|
||||
let isFavoritePredicate = NSPredicate(format: "userNode.favorite == YES")
|
||||
|
|
|
|||
|
|
@ -72,7 +72,9 @@ struct UserMessageList: View {
|
|||
if currentUser && message.receivedACK {
|
||||
// Ack Received
|
||||
if message.realACK {
|
||||
Text("\(ackErrorVal?.display ?? "Empty Ack Error")").font(.caption2).foregroundColor(.gray)
|
||||
Text("\(ackErrorVal?.display ?? "Empty Ack Error")")
|
||||
.font(.caption2)
|
||||
.foregroundStyle(ackErrorVal?.color ?? Color.secondary)
|
||||
} else {
|
||||
Text("Acknowledged by another node").font(.caption2).foregroundColor(.orange)
|
||||
}
|
||||
|
|
@ -81,7 +83,7 @@ struct UserMessageList: View {
|
|||
Text("Waiting to be acknowledged. . .").font(.caption2).foregroundColor(.yellow)
|
||||
} else if currentUser && message.ackError > 0 {
|
||||
Text("\(ackErrorVal?.display ?? "Empty Ack Error")").fixedSize(horizontal: false, vertical: true)
|
||||
.font(.caption2).foregroundColor(.red)
|
||||
.foregroundStyle(ackErrorVal?.color ?? Color.red)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -93,6 +93,12 @@ struct EnvironmentMetricsLog: View {
|
|||
IndoorAirQuality(iaq: Int(em.iaq), displayMode: IaqDisplayMode.dot )
|
||||
}
|
||||
}
|
||||
TableColumn("Wind Speed") { em in
|
||||
Text("\(String(format: "%.1f", em.windSpeed)) hPa")
|
||||
}
|
||||
TableColumn("Wind Direction") { em in
|
||||
Text("\(String(format: "%.1f", em.windDirection)) hPa")
|
||||
}
|
||||
TableColumn("timestamp") { em in
|
||||
Text(em.time?.formattedDate(format: dateFormatString) ?? "unknown.age".localized)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ struct PositionPopover: View {
|
|||
@ObservedObject var locationsHandler = LocationsHandler.shared
|
||||
@Environment(\.managedObjectContext) var context
|
||||
@EnvironmentObject var bleManager: BLEManager
|
||||
private var idiom: UIUserInterfaceIdiom { UIDevice.current.userInterfaceIdiom }
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
var position: PositionEntity
|
||||
var popover: Bool = true
|
||||
|
|
@ -52,9 +53,13 @@ struct PositionPopover: View {
|
|||
VStack(alignment: .leading) {
|
||||
/// Time
|
||||
Label {
|
||||
Text("heard".localized + ":")
|
||||
if idiom != .phone {
|
||||
Text("heard".localized + ":")
|
||||
}
|
||||
LastHeardText(lastHeard: position.time)
|
||||
.foregroundColor(.primary)
|
||||
.font(idiom == .phone ? .callout : .body)
|
||||
.allowsTightening(/*@START_MENU_TOKEN@*/true/*@END_MENU_TOKEN@*/)
|
||||
} icon: {
|
||||
Image(systemName: position.nodePosition?.isOnline ?? false ? "checkmark.circle.fill" : "moon.circle.fill")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
|
|
@ -67,12 +72,28 @@ struct PositionPopover: View {
|
|||
Text("\(String(format: "%.6f", position.coordinate.latitude)), \(String(format: "%.6f", position.coordinate.longitude))")
|
||||
.textSelection(.enabled)
|
||||
.foregroundColor(.primary)
|
||||
.font(idiom == .phone ? .callout : .body)
|
||||
.allowsTightening(/*@START_MENU_TOKEN@*/true/*@END_MENU_TOKEN@*/)
|
||||
} icon: {
|
||||
Image(systemName: "mappin.and.ellipse")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.frame(width: 35)
|
||||
}
|
||||
.padding(.bottom, 5)
|
||||
/// Hops Away
|
||||
if position.nodePosition?.hopsAway ?? 0 > 0 {
|
||||
Label {
|
||||
Text("Hops Away: \(position.nodePosition?.hopsAway ?? 0)")
|
||||
.textSelection(.enabled)
|
||||
.foregroundColor(.primary)
|
||||
.font(idiom == .phone ? .callout : .body)
|
||||
} icon: {
|
||||
Image(systemName: "hare")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.frame(width: 35)
|
||||
}
|
||||
.padding(.bottom, 5)
|
||||
}
|
||||
/// Altitude
|
||||
Label {
|
||||
let formatter = MeasurementFormatter()
|
||||
|
|
@ -81,9 +102,11 @@ struct PositionPopover: View {
|
|||
if Locale.current.measurementSystem == .metric {
|
||||
Text(altitudeFormatter.string(from: distanceInMeters))
|
||||
.foregroundColor(.primary)
|
||||
.font(idiom == .phone ? .callout : .body)
|
||||
} else {
|
||||
Text(altitudeFormatter.string(from: distanceInFeet))
|
||||
.foregroundColor(.primary)
|
||||
.font(idiom == .phone ? .callout : .body)
|
||||
}
|
||||
|
||||
} icon: {
|
||||
|
|
@ -98,6 +121,7 @@ struct PositionPopover: View {
|
|||
Label {
|
||||
Text("Sats in view: \(String(position.satsInView))")
|
||||
.foregroundColor(.primary)
|
||||
.font(idiom == .phone ? .callout : .body)
|
||||
} icon: {
|
||||
Image(systemName: "sparkles")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
|
|
@ -110,6 +134,7 @@ struct PositionPopover: View {
|
|||
Label {
|
||||
Text("Sequence: \(String(position.seqNo))")
|
||||
.foregroundColor(.primary)
|
||||
.font(idiom == .phone ? .callout : .body)
|
||||
} icon: {
|
||||
Image(systemName: "number")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
|
|
@ -129,11 +154,28 @@ struct PositionPopover: View {
|
|||
.rotationEffect(degrees)
|
||||
}
|
||||
.padding(.bottom, 5)
|
||||
/// Distance
|
||||
if let lastLocation = locationsHandler.locationsArray.last {
|
||||
/// Distance
|
||||
if lastLocation.distance(from: CLLocation(latitude: LocationsHandler.DefaultLocation.latitude, longitude: LocationsHandler.DefaultLocation.longitude)) > 0.0 {
|
||||
let metersAway = position.coordinate.distance(from: CLLocationCoordinate2D(latitude: lastLocation.coordinate.latitude, longitude: lastLocation.coordinate.longitude))
|
||||
Label {
|
||||
Text("distance".localized + ": \(distanceFormatter.string(fromDistance: Double(metersAway)))")
|
||||
.foregroundColor(.primary)
|
||||
.font(idiom == .phone ? .callout : .body)
|
||||
} icon: {
|
||||
Image(systemName: "lines.measurement.horizontal")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.frame(width: 35)
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Speed
|
||||
let speed = Measurement(value: Double(position.speed), unit: UnitSpeed.kilometersPerHour)
|
||||
Label {
|
||||
Text("Speed: \(speed.formatted(.measurement(width: .abbreviated, numberFormatStyle: .number.precision(.fractionLength(0)))))")
|
||||
.foregroundColor(.primary)
|
||||
.font(idiom == .phone ? .callout : .body)
|
||||
} icon: {
|
||||
Image(systemName: "gauge.with.dots.needle.33percent")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
|
|
@ -144,6 +186,7 @@ struct PositionPopover: View {
|
|||
|
||||
Label {
|
||||
Text("MQTT")
|
||||
.font(idiom == .phone ? .callout : .body)
|
||||
} icon: {
|
||||
Image(systemName: "network")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
|
|
@ -152,20 +195,6 @@ struct PositionPopover: View {
|
|||
}
|
||||
.padding(.bottom, 5)
|
||||
}
|
||||
if let lastLocation = locationsHandler.locationsArray.last {
|
||||
/// Distance
|
||||
if lastLocation.distance(from: CLLocation(latitude: LocationsHandler.DefaultLocation.latitude, longitude: LocationsHandler.DefaultLocation.longitude)) > 0.0 {
|
||||
let metersAway = position.coordinate.distance(from: CLLocationCoordinate2D(latitude: lastLocation.coordinate.latitude, longitude: lastLocation.coordinate.longitude))
|
||||
Label {
|
||||
Text("distance".localized + ": \(distanceFormatter.string(fromDistance: Double(metersAway)))")
|
||||
.foregroundColor(.primary)
|
||||
} icon: {
|
||||
Image(systemName: "lines.measurement.horizontal")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.frame(width: 35)
|
||||
}
|
||||
}
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
Spacer()
|
||||
|
|
@ -223,9 +252,9 @@ struct PositionPopover: View {
|
|||
#endif
|
||||
}
|
||||
}
|
||||
.presentationDetents([.medium, .large])
|
||||
.presentationDetents([.fraction(0.65), .large])
|
||||
.presentationContentInteraction(.scrolls)
|
||||
.presentationDragIndicator(.visible)
|
||||
.presentationBackgroundInteraction(.enabled(upThrough: .medium))
|
||||
.presentationBackgroundInteraction(.enabled(upThrough: .large))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,6 +44,24 @@ struct NodeDetail: View {
|
|||
NodeInfoItem(node: node)
|
||||
}
|
||||
Section("Node") {
|
||||
if let user = node.user {
|
||||
if !user.keyMatch {
|
||||
Label {
|
||||
VStack(alignment: .leading) {
|
||||
Text("Public Key Mismatch")
|
||||
.font(.title3)
|
||||
.foregroundStyle(.red)
|
||||
Text("The public key does not match the recorded key. You may delete the node and let it exchange keys again, but this may indicate a more serious security problem. Contact the user through another trusted channel, to determine if the key change was due to a factory reset or other intentional action.")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.red)
|
||||
}
|
||||
} icon: {
|
||||
Image(systemName: "key.slash.fill")
|
||||
.symbolRenderingMode(.multicolor)
|
||||
.foregroundStyle(.red)
|
||||
}
|
||||
}
|
||||
}
|
||||
HStack {
|
||||
Label {
|
||||
Text("Node Number")
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ struct NodeListFilter: View {
|
|||
@Binding var viaLora: Bool
|
||||
@Binding var viaMqtt: Bool
|
||||
@Binding var isOnline: Bool
|
||||
@Binding var isPkiEncrypted: Bool
|
||||
@Binding var isFavorite: Bool
|
||||
@Binding var isEnvironment: Bool
|
||||
@Binding var distanceFilter: Bool
|
||||
|
|
@ -64,6 +65,19 @@ struct NodeListFilter: View {
|
|||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
.listRowSeparator(.visible)
|
||||
|
||||
Toggle(isOn: $isPkiEncrypted) {
|
||||
|
||||
Label {
|
||||
Text("Encrypted")
|
||||
} icon: {
|
||||
Image(systemName: "lock.fill")
|
||||
.foregroundColor(.green)
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
}
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
.listRowSeparator(.visible)
|
||||
|
||||
Toggle(isOn: $isFavorite) {
|
||||
|
||||
Label {
|
||||
|
|
@ -173,9 +187,9 @@ struct NodeListFilter: View {
|
|||
.padding(.bottom)
|
||||
#endif
|
||||
}
|
||||
.presentationDetents([.medium, .large])
|
||||
.presentationDetents([.fraction(0.75), .large])
|
||||
.presentationContentInteraction(.scrolls)
|
||||
.presentationDragIndicator(.visible)
|
||||
.presentationBackgroundInteraction(.enabled(upThrough: .medium))
|
||||
.presentationBackgroundInteraction(.enabled(upThrough: .large))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,14 +23,16 @@ struct NodeListItem: View {
|
|||
VStack(alignment: .leading) {
|
||||
CircleText(text: node.user?.shortName ?? "?", color: Color(UIColor(hex: UInt32(node.num))), circleSize: 70)
|
||||
.padding(.trailing, 5)
|
||||
BatteryLevelCompact(node: node, font: .caption, iconFont: .callout, color: .accentColor)
|
||||
.padding(.trailing, 5)
|
||||
if node.latestDeviceMetrics != nil {
|
||||
BatteryCompact(batteryLevel: node.latestDeviceMetrics?.batteryLevel ?? 0, font: .caption, iconFont: .callout, color: .accentColor)
|
||||
.padding(.trailing, 5)
|
||||
}
|
||||
}
|
||||
VStack(alignment: .leading) {
|
||||
HStack {
|
||||
Text(node.user?.longName ?? "unknown".localized)
|
||||
.fontWeight(.medium)
|
||||
.font(.headline)
|
||||
.allowsTightening(true)
|
||||
if node.favorite {
|
||||
Spacer()
|
||||
Image(systemName: "star.fill")
|
||||
|
|
@ -98,7 +100,7 @@ struct NodeListItem: View {
|
|||
DistanceText(meters: metersAway)
|
||||
.font(UIDevice.current.userInterfaceIdiom == .phone ? .callout : .caption)
|
||||
.foregroundColor(.gray)
|
||||
let trueBearing = getBearingBetweenTwoPoints(point1: myCoord, point2: CLLocation(latitude: lastPostion.coordinate.latitude, longitude: lastPostion.coordinate.longitude))
|
||||
let trueBearing = getBearingBetweenTwoPoints(point1: myCoord, point2: nodeCoord)
|
||||
let headingDegrees = Angle.degrees(trueBearing)
|
||||
Image(systemName: "location.north")
|
||||
.font(.callout)
|
||||
|
|
@ -124,7 +126,7 @@ struct NodeListItem: View {
|
|||
DistanceText(meters: metersAway)
|
||||
.font(UIDevice.current.userInterfaceIdiom == .phone ? .callout : .caption)
|
||||
.foregroundColor(.secondary)
|
||||
let trueBearing = getBearingBetweenTwoPoints(point1: myCoord, point2: CLLocation(latitude: lastPostion.coordinate.latitude, longitude: lastPostion.coordinate.longitude))
|
||||
let trueBearing = getBearingBetweenTwoPoints(point1: myCoord, point2: nodeCoord)
|
||||
let headingDegrees = Angle.degrees(trueBearing)
|
||||
Image(systemName: "location.north")
|
||||
.font(.callout)
|
||||
|
|
@ -145,7 +147,6 @@ struct NodeListItem: View {
|
|||
HStack {
|
||||
Image(systemName: "\(node.channel).circle.fill")
|
||||
.font(.title2)
|
||||
.symbolRenderingMode(.multicolor)
|
||||
.frame(width: 30)
|
||||
Text("Channel")
|
||||
.foregroundColor(.secondary)
|
||||
|
|
@ -216,7 +217,6 @@ struct NodeListItem: View {
|
|||
.font(UIDevice.current.userInterfaceIdiom == .phone ? .callout : .caption)
|
||||
Image(systemName: "\(node.hopsAway).square")
|
||||
.font(.title2)
|
||||
.symbolRenderingMode(.multicolor)
|
||||
}
|
||||
} else {
|
||||
if node.snr != 0 && !node.viaMqtt {
|
||||
|
|
|
|||
|
|
@ -33,13 +33,27 @@ struct MeshMap: View {
|
|||
@Namespace var mapScope
|
||||
@State var mapStyle: MapStyle = MapStyle.standard(elevation: .flat, emphasis: MapStyle.StandardEmphasis.muted, pointsOfInterest: .excludingAll, showsTraffic: false)
|
||||
@State var position = MapCameraPosition.automatic
|
||||
@State var isEditingSettings = false
|
||||
@State private var editingSettings = false
|
||||
@State private var editingFilters = false
|
||||
@State var selectedPosition: PositionEntity?
|
||||
@State var editingWaypoint: WaypointEntity?
|
||||
@State var selectedWaypoint: WaypointEntity?
|
||||
@State var selectedWaypointId: String?
|
||||
@State var newWaypointCoord: CLLocationCoordinate2D?
|
||||
@State var isMeshMap = true
|
||||
/// Filter
|
||||
@State private var searchText = ""
|
||||
@State private var viaLora = true
|
||||
@State private var viaMqtt = true
|
||||
@State private var isOnline = false
|
||||
@State private var isPkiEncrypted = false
|
||||
@State private var isFavorite = false
|
||||
@State private var isEnvironment = false
|
||||
@State private var distanceFilter = false
|
||||
@State private var maxDistance: Double = 800000
|
||||
@State private var hopsAway: Double = -1.0
|
||||
@State private var roleFilter = false
|
||||
@State private var deviceRoles: Set<Int> = []
|
||||
|
||||
var body: some View {
|
||||
|
||||
|
|
@ -48,7 +62,6 @@ struct MeshMap: View {
|
|||
MapReader { reader in
|
||||
Map(position: $position, bounds: MapCameraBounds(minimumDistance: 1, maximumDistance: .infinity), scope: mapScope) {
|
||||
MeshMapContent(showUserLocation: $showUserLocation, showTraffic: $showTraffic, showPointsOfInterest: $showPointsOfInterest, selectedMapLayer: $selectedMapLayer, selectedPosition: $selectedPosition, selectedWaypoint: $selectedWaypoint)
|
||||
|
||||
}
|
||||
.mapScope(mapScope)
|
||||
.mapStyle(mapStyle)
|
||||
|
|
@ -106,7 +119,7 @@ struct MeshMap: View {
|
|||
WaypointForm(waypoint: selection, editMode: true)
|
||||
.padding()
|
||||
}
|
||||
.sheet(isPresented: $isEditingSettings) {
|
||||
.sheet(isPresented: $editingSettings) {
|
||||
MapSettingsForm(traffic: $showTraffic, pointsOfInterest: $showPointsOfInterest, mapLayer: $selectedMapLayer, meshMap: $isMeshMap)
|
||||
}
|
||||
.onChange(of: router.navigationState) {
|
||||
|
|
@ -128,19 +141,46 @@ struct MeshMap: View {
|
|||
return
|
||||
}
|
||||
}
|
||||
.sheet(isPresented: $editingFilters) {
|
||||
NodeListFilter(
|
||||
viaLora: $viaLora,
|
||||
viaMqtt: $viaMqtt,
|
||||
isOnline: $isOnline,
|
||||
isPkiEncrypted: $isPkiEncrypted,
|
||||
isFavorite: $isFavorite,
|
||||
isEnvironment: $isEnvironment,
|
||||
distanceFilter: $distanceFilter,
|
||||
maximumDistance: $maxDistance,
|
||||
hopsAway: $hopsAway,
|
||||
roleFilter: $roleFilter,
|
||||
deviceRoles: $deviceRoles
|
||||
)
|
||||
}
|
||||
.safeAreaInset(edge: .bottom, alignment: .trailing) {
|
||||
HStack {
|
||||
Spacer()
|
||||
Button(action: {
|
||||
withAnimation {
|
||||
isEditingSettings = !isEditingSettings
|
||||
editingSettings = !editingSettings
|
||||
}
|
||||
}) {
|
||||
Image(systemName: isEditingSettings ? "info.circle.fill" : "info.circle")
|
||||
Image(systemName: editingSettings ? "info.circle.fill" : "info.circle")
|
||||
.padding(.vertical, 5)
|
||||
}
|
||||
.tint(Color(UIColor.secondarySystemBackground))
|
||||
.foregroundColor(.accentColor)
|
||||
.buttonStyle(.borderedProminent)
|
||||
// Button(action: {
|
||||
// withAnimation {
|
||||
// editingFilters = !editingFilters
|
||||
// }
|
||||
// }) {
|
||||
// Image(systemName: !editingFilters ? "line.3.horizontal.decrease.circle" : "line.3.horizontal.decrease.circle.fill")
|
||||
// .padding(.vertical, 5)
|
||||
// }
|
||||
// .tint(Color(UIColor.secondarySystemBackground))
|
||||
// .foregroundColor(.accentColor)
|
||||
// .buttonStyle(.borderedProminent)
|
||||
}
|
||||
.controlSize(.regular)
|
||||
.padding(5)
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ struct NodeList: View {
|
|||
@State private var viaLora = true
|
||||
@State private var viaMqtt = true
|
||||
@State private var isOnline = false
|
||||
@State private var isPkiEncrypted = false
|
||||
@State private var isFavorite = false
|
||||
@State private var isEnvironment = false
|
||||
@State private var distanceFilter = false
|
||||
|
|
@ -38,8 +39,9 @@ struct NodeList: View {
|
|||
@State private var deleteNodeId: Int64 = 0
|
||||
|
||||
var boolFilters: [Bool] {[
|
||||
isOnline,
|
||||
isFavorite,
|
||||
isOnline,
|
||||
isPkiEncrypted,
|
||||
isEnvironment,
|
||||
distanceFilter,
|
||||
roleFilter
|
||||
|
|
@ -86,8 +88,15 @@ struct NodeList: View {
|
|||
context: context,
|
||||
node: node
|
||||
)
|
||||
/// Don't show trace route, position exchange or delete context menu items for the connected node
|
||||
/// Don't show message, trace route, position exchange or delete context menu items for the connected node
|
||||
if connectedNode.num != node.num {
|
||||
Button(action: {
|
||||
if let url = URL(string: "meshtastic:///messages?userNum=\(node.num)") {
|
||||
UIApplication.shared.open(url)
|
||||
}
|
||||
}) {
|
||||
Label("Message", systemImage: "message")
|
||||
}
|
||||
Button {
|
||||
let traceRouteSent = bleManager.sendTraceRouteRequest(
|
||||
destNum: node.num,
|
||||
|
|
@ -153,6 +162,7 @@ struct NodeList: View {
|
|||
viaLora: $viaLora,
|
||||
viaMqtt: $viaMqtt,
|
||||
isOnline: $isOnline,
|
||||
isPkiEncrypted: $isPkiEncrypted,
|
||||
isFavorite: $isFavorite,
|
||||
isEnvironment: $isEnvironment,
|
||||
distanceFilter: $distanceFilter,
|
||||
|
|
@ -299,7 +309,7 @@ struct NodeList: View {
|
|||
await searchNodeList()
|
||||
}
|
||||
}
|
||||
.onChange(of: boolFilters) { _ in
|
||||
.onChange(of: [boolFilters]) { _ in
|
||||
Task {
|
||||
await searchNodeList()
|
||||
}
|
||||
|
|
@ -334,7 +344,7 @@ struct NodeList: View {
|
|||
self.selectedNode = nil
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
.onFirstAppear {
|
||||
Task {
|
||||
await searchNodeList()
|
||||
}
|
||||
|
|
@ -383,6 +393,11 @@ struct NodeList: View {
|
|||
let isOnlinePredicate = NSPredicate(format: "lastHeard >= %@", Calendar.current.date(byAdding: .minute, value: -15, to: Date())! as NSDate)
|
||||
predicates.append(isOnlinePredicate)
|
||||
}
|
||||
/// Encrypted
|
||||
if isPkiEncrypted {
|
||||
let isPkiEncryptedPredicate = NSPredicate(format: "user.pkiEncrypted == YES")
|
||||
predicates.append(isPkiEncryptedPredicate)
|
||||
}
|
||||
/// Favorites
|
||||
if isFavorite {
|
||||
let isFavoritePredicate = NSPredicate(format: "favorite == YES")
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ struct AppSettings: View {
|
|||
@State private var isPresentingCoreDataResetConfirm = false
|
||||
@State private var isPresentingDeleteMapTilesConfirm = false
|
||||
@AppStorage("environmentEnableWeatherKit") private var environmentEnableWeatherKit: Bool = true
|
||||
@AppStorage("enableAdministration") private var enableAdministration: Bool = false
|
||||
var body: some View {
|
||||
VStack {
|
||||
Form {
|
||||
|
|
@ -23,6 +24,10 @@ struct AppSettings: View {
|
|||
UIApplication.shared.open(url)
|
||||
}
|
||||
}
|
||||
Toggle(isOn: $enableAdministration) {
|
||||
Label("Administration", systemImage: "gearshape.2")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
}
|
||||
Section(header: Text("environment")) {
|
||||
VStack(alignment: .leading) {
|
||||
|
|
|
|||
|
|
@ -107,7 +107,6 @@ struct BluetoothConfig: View {
|
|||
}
|
||||
)
|
||||
.onAppear {
|
||||
setBluetoothValues()
|
||||
// Need to request a BluetoothConfig from the remote node before allowing changes
|
||||
if let connectedPeripheral = bleManager.connectedPeripheral, let node, node.bluetoothConfig == nil {
|
||||
Logger.mesh.info("empty bluetooth config")
|
||||
|
|
@ -117,25 +116,17 @@ struct BluetoothConfig: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
.onChange(of: enabled) { newEnabled in
|
||||
if node != nil && node!.bluetoothConfig != nil {
|
||||
if newEnabled != node!.bluetoothConfig!.enabled { hasChanges = true }
|
||||
}
|
||||
.onChange(of: enabled) {
|
||||
if $0 != node?.bluetoothConfig?.enabled { hasChanges = true }
|
||||
}
|
||||
.onChange(of: mode) { newMode in
|
||||
if node != nil && node!.bluetoothConfig != nil {
|
||||
if newMode != node!.bluetoothConfig!.mode { hasChanges = true }
|
||||
}
|
||||
.onChange(of: mode) {
|
||||
if $0 != node?.bluetoothConfig?.mode ?? -1 { hasChanges = true }
|
||||
}
|
||||
.onChange(of: fixedPin) { newFixedPin in
|
||||
if node != nil && node!.bluetoothConfig != nil {
|
||||
if newFixedPin != String(node!.bluetoothConfig!.fixedPin) { hasChanges = true }
|
||||
}
|
||||
if newFixedPin != String(node?.bluetoothConfig?.fixedPin ?? -1) { hasChanges = true }
|
||||
}
|
||||
.onChange(of: deviceLoggingEnabled) { newDeviceLogging in
|
||||
if node != nil && node!.bluetoothConfig != nil {
|
||||
if newDeviceLogging != node!.bluetoothConfig!.deviceLoggingEnabled { hasChanges = true }
|
||||
}
|
||||
.onChange(of: deviceLoggingEnabled) {
|
||||
if $0 != node?.bluetoothConfig?.deviceLoggingEnabled { hasChanges = true }
|
||||
}
|
||||
}
|
||||
func setBluetoothValues() {
|
||||
|
|
|
|||
|
|
@ -23,11 +23,12 @@ struct ConfigHeader<T>: View {
|
|||
.foregroundColor(.orange)
|
||||
} else {
|
||||
Text("Remote administration for: \(node?.user?.longName ?? "Unknown")")
|
||||
.onFirstAppear(onAppear)
|
||||
.font(.title3)
|
||||
.onAppear(perform: onAppear)
|
||||
}
|
||||
} else if node != nil && node?.num ?? 0 == bleManager.connectedPeripheral?.num ?? -1 {
|
||||
Text("Configuration for: \(node?.user?.longName ?? "Unknown")")
|
||||
.onFirstAppear(onAppear)
|
||||
} else {
|
||||
Text("Please connect to a radio to configure settings.")
|
||||
.font(.callout)
|
||||
|
|
|
|||
|
|
@ -155,7 +155,7 @@ struct DeviceConfig: View {
|
|||
.disabled(node?.user == nil)
|
||||
.buttonStyle(.bordered)
|
||||
.buttonBorderShape(.capsule)
|
||||
.controlSize(.large)
|
||||
.controlSize(.regular)
|
||||
.padding(.leading)
|
||||
.confirmationDialog(
|
||||
"are.you.sure",
|
||||
|
|
@ -180,10 +180,10 @@ struct DeviceConfig: View {
|
|||
.disabled(node?.user == nil)
|
||||
.buttonStyle(.bordered)
|
||||
.buttonBorderShape(.capsule)
|
||||
.controlSize(.large)
|
||||
.controlSize(.regular)
|
||||
.padding(.trailing)
|
||||
.confirmationDialog(
|
||||
"All device and app data will be deleted. You will also need to forget your devices under Settings > Bluetooth.",
|
||||
"All device and app data will be deleted.",
|
||||
isPresented: $isPresentingFactoryResetConfirm,
|
||||
titleVisibility: .visible
|
||||
) {
|
||||
|
|
@ -233,13 +233,17 @@ struct DeviceConfig: View {
|
|||
Spacer()
|
||||
}
|
||||
.navigationTitle("device.config")
|
||||
.navigationBarItems(trailing:
|
||||
ZStack {
|
||||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
|
||||
})
|
||||
.navigationBarItems(
|
||||
trailing: ZStack {
|
||||
ConnectedDevice(
|
||||
bluetoothOn: bleManager.isSwitchedOn,
|
||||
deviceConnected: bleManager.connectedPeripheral != nil,
|
||||
name: bleManager.connectedPeripheral?.shortName ?? "?"
|
||||
)
|
||||
}
|
||||
)
|
||||
.onAppear {
|
||||
setDeviceValues()
|
||||
// Need to request a LoRaConfig from the remote node before allowing changes
|
||||
// Need to request a DeviceConfig from the remote node before allowing changes
|
||||
if bleManager.connectedPeripheral != nil && node?.deviceConfig == nil {
|
||||
Logger.mesh.info("empty device config")
|
||||
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1, context: context)
|
||||
|
|
@ -248,55 +252,35 @@ struct DeviceConfig: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
.onChange(of: deviceRole) { newRole in
|
||||
if node != nil && node?.deviceConfig != nil {
|
||||
if newRole != node!.deviceConfig!.role { hasChanges = true }
|
||||
}
|
||||
.onChange(of: deviceRole) {
|
||||
if $0 != node?.deviceConfig?.role ?? -1 { hasChanges = true }
|
||||
}
|
||||
.onChange(of: serialEnabled) { newSerial in
|
||||
if node != nil && node?.deviceConfig != nil {
|
||||
if newSerial != node!.deviceConfig!.serialEnabled { hasChanges = true }
|
||||
}
|
||||
.onChange(of: serialEnabled) {
|
||||
if $0 != node?.deviceConfig?.serialEnabled { hasChanges = true }
|
||||
}
|
||||
.onChange(of: debugLogEnabled) { newDebugLog in
|
||||
if node != nil && node?.deviceConfig != nil {
|
||||
if newDebugLog != node!.deviceConfig!.debugLogEnabled { hasChanges = true }
|
||||
}
|
||||
.onChange(of: debugLogEnabled) {
|
||||
if $0 != node?.deviceConfig?.debugLogEnabled { hasChanges = true }
|
||||
}
|
||||
.onChange(of: buttonGPIO) { newButtonGPIO in
|
||||
if node != nil && node?.deviceConfig != nil {
|
||||
if newButtonGPIO != node!.deviceConfig!.buttonGpio { hasChanges = true }
|
||||
}
|
||||
if newButtonGPIO != node?.deviceConfig?.buttonGpio ?? -1 { hasChanges = true }
|
||||
}
|
||||
.onChange(of: buzzerGPIO) { newBuzzerGPIO in
|
||||
if node != nil && node?.deviceConfig != nil {
|
||||
if newBuzzerGPIO != node!.deviceConfig!.buttonGpio { hasChanges = true }
|
||||
}
|
||||
if newBuzzerGPIO != node?.deviceConfig?.buttonGpio ?? -1 { hasChanges = true }
|
||||
}
|
||||
.onChange(of: rebroadcastMode) { newRebroadcastMode in
|
||||
if node != nil && node?.deviceConfig != nil {
|
||||
if newRebroadcastMode != node!.deviceConfig!.rebroadcastMode { hasChanges = true }
|
||||
}
|
||||
if newRebroadcastMode != node?.deviceConfig?.rebroadcastMode ?? -1 { hasChanges = true }
|
||||
}
|
||||
.onChange(of: nodeInfoBroadcastSecs) { newNodeInfoBroadcastSecs in
|
||||
if node != nil && node?.deviceConfig != nil {
|
||||
if newNodeInfoBroadcastSecs != node!.deviceConfig!.nodeInfoBroadcastSecs { hasChanges = true }
|
||||
}
|
||||
if newNodeInfoBroadcastSecs != node?.deviceConfig?.nodeInfoBroadcastSecs ?? -1 { hasChanges = true }
|
||||
}
|
||||
.onChange(of: doubleTapAsButtonPress) { newDoubleTapAsButtonPress in
|
||||
if node != nil && node?.deviceConfig != nil {
|
||||
if newDoubleTapAsButtonPress != node!.deviceConfig!.doubleTapAsButtonPress { hasChanges = true }
|
||||
}
|
||||
.onChange(of: doubleTapAsButtonPress) {
|
||||
if $0 != node?.deviceConfig?.doubleTapAsButtonPress { hasChanges = true }
|
||||
}
|
||||
.onChange(of: isManaged) { newIsManaged in
|
||||
if node != nil && node?.deviceConfig != nil {
|
||||
if newIsManaged != node!.deviceConfig!.isManaged { hasChanges = true }
|
||||
}
|
||||
.onChange(of: isManaged) {
|
||||
if $0 != node?.deviceConfig?.isManaged { hasChanges = true }
|
||||
}
|
||||
.onChange(of: tzdef) { newTzdef in
|
||||
if node != nil && node?.deviceConfig != nil {
|
||||
if newTzdef != node!.deviceConfig!.tzdef { hasChanges = true }
|
||||
}
|
||||
if newTzdef != node?.deviceConfig?.tzdef { hasChanges = true }
|
||||
}
|
||||
.onChange(of: ledHeartbeatEnabled) { newLedHeartbeatEnabled in
|
||||
if node != nil && node?.deviceConfig != nil {
|
||||
|
|
|
|||
|
|
@ -154,13 +154,16 @@ struct DisplayConfig: View {
|
|||
}
|
||||
|
||||
.navigationTitle("display.config")
|
||||
.navigationBarItems(trailing:
|
||||
ZStack {
|
||||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
|
||||
})
|
||||
.navigationBarItems(
|
||||
trailing: ZStack {
|
||||
ConnectedDevice(
|
||||
bluetoothOn: bleManager.isSwitchedOn,
|
||||
deviceConnected: bleManager.connectedPeripheral != nil,
|
||||
name: bleManager.connectedPeripheral?.shortName ?? "?"
|
||||
)
|
||||
}
|
||||
)
|
||||
.onAppear {
|
||||
setDisplayValues()
|
||||
|
||||
// Need to request a LoRaConfig from the remote node before allowing changes
|
||||
if bleManager.connectedPeripheral != nil && node?.displayConfig == nil {
|
||||
Logger.mesh.info("empty display config")
|
||||
|
|
@ -171,49 +174,31 @@ struct DisplayConfig: View {
|
|||
}
|
||||
}
|
||||
.onChange(of: screenOnSeconds) { newScreenSecs in
|
||||
if node != nil && node!.displayConfig != nil {
|
||||
if newScreenSecs != node!.displayConfig!.screenOnSeconds { hasChanges = true }
|
||||
}
|
||||
if newScreenSecs != node?.displayConfig?.screenOnSeconds ?? -1 { hasChanges = true }
|
||||
}
|
||||
.onChange(of: screenCarouselInterval) { newCarouselSecs in
|
||||
if node != nil && node!.displayConfig != nil {
|
||||
if newCarouselSecs != node!.displayConfig!.screenCarouselInterval { hasChanges = true }
|
||||
}
|
||||
if newCarouselSecs != node?.displayConfig?.screenCarouselInterval ?? -1 { hasChanges = true }
|
||||
}
|
||||
.onChange(of: compassNorthTop) { newCompassNorthTop in
|
||||
if node != nil && node!.displayConfig != nil {
|
||||
if newCompassNorthTop != node!.displayConfig!.compassNorthTop { hasChanges = true }
|
||||
}
|
||||
.onChange(of: compassNorthTop) {
|
||||
if $0 != node?.displayConfig?.compassNorthTop { hasChanges = true }
|
||||
}
|
||||
.onChange(of: wakeOnTapOrMotion) { newWakeOnTapOrMotion in
|
||||
if node != nil && node!.displayConfig != nil {
|
||||
if newWakeOnTapOrMotion != node!.displayConfig!.wakeOnTapOrMotion { hasChanges = true }
|
||||
}
|
||||
.onChange(of: wakeOnTapOrMotion) {
|
||||
if $0 != node?.displayConfig?.wakeOnTapOrMotion { hasChanges = true }
|
||||
}
|
||||
.onChange(of: gpsFormat) { newGpsFormat in
|
||||
if node != nil && node!.displayConfig != nil {
|
||||
if newGpsFormat != node!.displayConfig!.gpsFormat { hasChanges = true }
|
||||
}
|
||||
if newGpsFormat != node?.displayConfig?.gpsFormat ?? -1 { hasChanges = true }
|
||||
}
|
||||
.onChange(of: flipScreen) { newFlipScreen in
|
||||
if node != nil && node!.displayConfig != nil {
|
||||
if newFlipScreen != node!.displayConfig!.flipScreen { hasChanges = true }
|
||||
}
|
||||
.onChange(of: flipScreen) {
|
||||
if $0 != node?.displayConfig?.flipScreen { hasChanges = true }
|
||||
}
|
||||
.onChange(of: oledType) { newOledType in
|
||||
if node != nil && node!.displayConfig != nil {
|
||||
if newOledType != node!.displayConfig!.oledType { hasChanges = true }
|
||||
}
|
||||
if newOledType != node?.displayConfig?.oledType ?? -1 { hasChanges = true }
|
||||
}
|
||||
.onChange(of: displayMode) { newDisplayMode in
|
||||
if node != nil && node!.displayConfig != nil {
|
||||
if newDisplayMode != node!.displayConfig!.displayMode { hasChanges = true }
|
||||
}
|
||||
if newDisplayMode != node?.displayConfig?.displayMode ?? -1 { hasChanges = true }
|
||||
}
|
||||
.onChange(of: units) { newUnits in
|
||||
if node != nil && node!.displayConfig != nil {
|
||||
if newUnits != node!.displayConfig!.units { hasChanges = true }
|
||||
}
|
||||
if newUnits != node?.displayConfig?.units ?? -1 { hasChanges = true }
|
||||
}
|
||||
}
|
||||
func setDisplayValues() {
|
||||
|
|
|
|||
|
|
@ -223,12 +223,16 @@ struct LoRaConfig: View {
|
|||
}
|
||||
}
|
||||
.navigationTitle("lora.config")
|
||||
.navigationBarItems(trailing:
|
||||
ZStack {
|
||||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
|
||||
})
|
||||
.navigationBarItems(
|
||||
trailing: ZStack {
|
||||
ConnectedDevice(
|
||||
bluetoothOn: bleManager.isSwitchedOn,
|
||||
deviceConnected: bleManager.connectedPeripheral != nil,
|
||||
name: bleManager.connectedPeripheral?.shortName ?? "?"
|
||||
)
|
||||
}
|
||||
)
|
||||
.onAppear {
|
||||
setLoRaValues()
|
||||
// Need to request a LoRaConfig from the remote node before allowing changes
|
||||
if bleManager.connectedPeripheral != nil && node?.loRaConfig == nil {
|
||||
Logger.mesh.info("empty lora config")
|
||||
|
|
@ -239,69 +243,43 @@ struct LoRaConfig: View {
|
|||
}
|
||||
}
|
||||
.onChange(of: region) { newRegion in
|
||||
if node != nil && node!.loRaConfig != nil {
|
||||
if newRegion != node!.loRaConfig!.regionCode { hasChanges = true }
|
||||
}
|
||||
if newRegion != node?.loRaConfig?.regionCode ?? -1 { hasChanges = true }
|
||||
}
|
||||
.onChange(of: usePreset) { newUsePreset in
|
||||
if node != nil && node!.loRaConfig != nil {
|
||||
if newUsePreset != node!.loRaConfig!.usePreset { hasChanges = true }
|
||||
}
|
||||
.onChange(of: usePreset) {
|
||||
if $0 != node?.loRaConfig?.usePreset { hasChanges = true }
|
||||
}
|
||||
.onChange(of: modemPreset) { newModemPreset in
|
||||
if node != nil && node!.loRaConfig != nil {
|
||||
if newModemPreset != node!.loRaConfig!.modemPreset { hasChanges = true }
|
||||
}
|
||||
if newModemPreset != node?.loRaConfig?.modemPreset ?? -1 { hasChanges = true }
|
||||
}
|
||||
.onChange(of: hopLimit) { newHopLimit in
|
||||
if node != nil && node!.loRaConfig != nil {
|
||||
if newHopLimit != node!.loRaConfig!.hopLimit { hasChanges = true }
|
||||
}
|
||||
if newHopLimit != node?.loRaConfig?.hopLimit ?? -1 { hasChanges = true }
|
||||
}
|
||||
.onChange(of: channelNum) { newChannelNum in
|
||||
if node != nil && node!.loRaConfig != nil {
|
||||
if newChannelNum != node!.loRaConfig!.channelNum { hasChanges = true }
|
||||
}
|
||||
if newChannelNum != node?.loRaConfig?.channelNum ?? -1 { hasChanges = true }
|
||||
}
|
||||
.onChange(of: bandwidth) { newBandwidth in
|
||||
if node != nil && node!.loRaConfig != nil {
|
||||
if newBandwidth != node!.loRaConfig!.bandwidth { hasChanges = true }
|
||||
}
|
||||
if newBandwidth != node?.loRaConfig?.bandwidth ?? -1 { hasChanges = true }
|
||||
}
|
||||
.onChange(of: codingRate) { newCodingRate in
|
||||
if node != nil && node!.loRaConfig != nil {
|
||||
if newCodingRate != node!.loRaConfig!.codingRate { hasChanges = true }
|
||||
}
|
||||
if newCodingRate != node?.loRaConfig?.codingRate ?? -1 { hasChanges = true }
|
||||
}
|
||||
.onChange(of: spreadFactor) { newSpreadFactor in
|
||||
if node != nil && node!.loRaConfig != nil {
|
||||
if newSpreadFactor != node!.loRaConfig!.spreadFactor { hasChanges = true }
|
||||
}
|
||||
if newSpreadFactor != node?.loRaConfig?.spreadFactor ?? -1 { hasChanges = true }
|
||||
}
|
||||
.onChange(of: rxBoostedGain) { newRxBoostedGain in
|
||||
if node != nil && node!.loRaConfig != nil {
|
||||
if newRxBoostedGain != node!.loRaConfig!.sx126xRxBoostedGain { hasChanges = true }
|
||||
}
|
||||
.onChange(of: rxBoostedGain) {
|
||||
if $0 != node?.loRaConfig?.sx126xRxBoostedGain { hasChanges = true }
|
||||
}
|
||||
.onChange(of: overrideFrequency) { newOverrideFrequency in
|
||||
if node != nil && node!.loRaConfig != nil {
|
||||
if newOverrideFrequency != node!.loRaConfig!.overrideFrequency { hasChanges = true }
|
||||
}
|
||||
if newOverrideFrequency != node?.loRaConfig?.overrideFrequency { hasChanges = true }
|
||||
}
|
||||
.onChange(of: txPower) { newTxPower in
|
||||
if node != nil && node!.loRaConfig != nil {
|
||||
if newTxPower != node!.loRaConfig!.txPower { hasChanges = true }
|
||||
}
|
||||
if newTxPower != node?.loRaConfig?.txPower ?? -1 { hasChanges = true }
|
||||
}
|
||||
.onChange(of: txEnabled) { newTxEnabled in
|
||||
if node != nil && node!.loRaConfig != nil {
|
||||
if newTxEnabled != node!.loRaConfig!.txEnabled { hasChanges = true }
|
||||
}
|
||||
.onChange(of: txEnabled) {
|
||||
if $0 != node?.loRaConfig?.txEnabled { hasChanges = true }
|
||||
}
|
||||
.onChange(of: ignoreMqtt) { newIgnoreMqtt in
|
||||
if node != nil && node!.loRaConfig != nil {
|
||||
if newIgnoreMqtt != node!.loRaConfig!.ignoreMqtt { hasChanges = true }
|
||||
}
|
||||
.onChange(of: ignoreMqtt) {
|
||||
if $0 != node?.loRaConfig?.ignoreMqtt { hasChanges = true }
|
||||
}
|
||||
}
|
||||
func setLoRaValues() {
|
||||
|
|
|
|||
|
|
@ -50,10 +50,6 @@ struct AmbientLightingConfig: View {
|
|||
Stepper("Current: \(current)", value: $current, in: 0...31, step: 1)
|
||||
.padding(5)
|
||||
}
|
||||
.onChange(of: color, initial: true) {
|
||||
components = color.resolve(in: environment)
|
||||
hasChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
.disabled(self.bleManager.connectedPeripheral == nil || node?.ambientLightingConfig == nil)
|
||||
|
|
@ -80,12 +76,16 @@ struct AmbientLightingConfig: View {
|
|||
}
|
||||
}
|
||||
.navigationTitle("ambient.lighting.config")
|
||||
.navigationBarItems(trailing:
|
||||
ZStack {
|
||||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
|
||||
})
|
||||
.navigationBarItems(
|
||||
trailing: ZStack {
|
||||
ConnectedDevice(
|
||||
bluetoothOn: bleManager.isSwitchedOn,
|
||||
deviceConnected: bleManager.connectedPeripheral != nil,
|
||||
name: bleManager.connectedPeripheral?.shortName ?? "?"
|
||||
)
|
||||
}
|
||||
)
|
||||
.onAppear {
|
||||
setAmbientLightingConfigValue()
|
||||
// Need to request a Ambient Lighting Config from the remote node before allowing changes
|
||||
if bleManager.connectedPeripheral != nil && node?.ambientLightingConfig == nil {
|
||||
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context)
|
||||
|
|
@ -94,9 +94,19 @@ struct AmbientLightingConfig: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
.onChange(of: ledState) { newLedState in
|
||||
if node != nil && node!.ambientLightingConfig != nil {
|
||||
if newLedState != node!.ambientLightingConfig!.ledState { hasChanges = true }
|
||||
.onChange(of: ledState) {
|
||||
if let val = node?.ambientLightingConfig?.ledState {
|
||||
hasChanges = $0 != val
|
||||
}
|
||||
}
|
||||
.onChange(of: current) {
|
||||
if let val = node?.ambientLightingConfig?.current {
|
||||
hasChanges = $0 != val
|
||||
}
|
||||
}
|
||||
.onChange(of: color) { c in
|
||||
if color != c {
|
||||
hasChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -224,12 +224,16 @@ struct CannedMessagesConfig: View {
|
|||
}
|
||||
}
|
||||
.navigationTitle("canned.messages.config")
|
||||
.navigationBarItems(trailing:
|
||||
ZStack {
|
||||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
|
||||
})
|
||||
.navigationBarItems(
|
||||
trailing: ZStack {
|
||||
ConnectedDevice(
|
||||
bluetoothOn: bleManager.isSwitchedOn,
|
||||
deviceConnected: bleManager.connectedPeripheral != nil,
|
||||
name: bleManager.connectedPeripheral?.shortName ?? "?"
|
||||
)
|
||||
}
|
||||
)
|
||||
.onAppear {
|
||||
setCannedMessagesValues()
|
||||
// Need to request a CannedMessagesModuleConfig from the remote node before allowing changes
|
||||
if bleManager.connectedPeripheral != nil && node?.cannedMessageConfig == nil {
|
||||
Logger.mesh.info("empty canned messages module config")
|
||||
|
|
@ -268,24 +272,24 @@ struct CannedMessagesConfig: View {
|
|||
|
||||
hasChanges = true
|
||||
}
|
||||
.onChange(of: enabled) { newEnabled in
|
||||
if node != nil && node!.cannedMessageConfig != nil {
|
||||
if newEnabled != node!.cannedMessageConfig!.enabled { hasChanges = true }
|
||||
.onChange(of: enabled) {
|
||||
if let val = node?.cannedMessageConfig?.enabled {
|
||||
hasChanges = $0 != val
|
||||
}
|
||||
}
|
||||
.onChange(of: sendBell) { newBell in
|
||||
if node != nil && node!.cannedMessageConfig != nil {
|
||||
if newBell != node!.cannedMessageConfig!.sendBell { hasChanges = true }
|
||||
.onChange(of: sendBell) {
|
||||
if let val = node?.cannedMessageConfig?.sendBell {
|
||||
hasChanges = $0 != val
|
||||
}
|
||||
}
|
||||
.onChange(of: rotary1Enabled) { newRot1 in
|
||||
if node != nil && node!.cannedMessageConfig != nil {
|
||||
if newRot1 != node!.cannedMessageConfig!.rotary1Enabled { hasChanges = true }
|
||||
.onChange(of: rotary1Enabled) {
|
||||
if let val = node?.cannedMessageConfig?.rotary1Enabled {
|
||||
hasChanges = $0 != val
|
||||
}
|
||||
}
|
||||
.onChange(of: updown1Enabled) { newUpDown in
|
||||
if node != nil && node!.cannedMessageConfig != nil {
|
||||
if newUpDown != node!.cannedMessageConfig!.updown1Enabled { hasChanges = true }
|
||||
.onChange(of: updown1Enabled) {
|
||||
if let val = node?.cannedMessageConfig?.updown1Enabled {
|
||||
hasChanges = $0 != val
|
||||
}
|
||||
}
|
||||
.onChange(of: inputbrokerPinA) { newPinA in
|
||||
|
|
|
|||
|
|
@ -180,12 +180,16 @@ struct DetectionSensorConfig: View {
|
|||
}
|
||||
}
|
||||
.navigationTitle("detection.sensor.config")
|
||||
.navigationBarItems(trailing:
|
||||
ZStack {
|
||||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
|
||||
})
|
||||
.navigationBarItems(
|
||||
trailing: ZStack {
|
||||
ConnectedDevice(
|
||||
bluetoothOn: bleManager.isSwitchedOn,
|
||||
deviceConnected: bleManager.connectedPeripheral != nil,
|
||||
name: bleManager.connectedPeripheral?.shortName ?? "?"
|
||||
)
|
||||
}
|
||||
)
|
||||
.onAppear {
|
||||
setDetectionSensorValues()
|
||||
// Need to request a Detection Sensor Module Config from the remote node before allowing changes
|
||||
if bleManager.connectedPeripheral != nil && node?.detectionSensorConfig == nil {
|
||||
Logger.mesh.info("empty detection sensor module config")
|
||||
|
|
@ -195,14 +199,14 @@ struct DetectionSensorConfig: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
.onChange(of: enabled) { newEnabled in
|
||||
if node != nil && node?.detectionSensorConfig != nil {
|
||||
if newEnabled != node!.detectionSensorConfig!.enabled { hasChanges = true }
|
||||
.onChange(of: enabled) {
|
||||
if let val = node?.detectionSensorConfig?.enabled {
|
||||
hasChanges = $0 != val
|
||||
}
|
||||
}
|
||||
.onChange(of: sendBell) { newSendBell in
|
||||
if node != nil && node?.detectionSensorConfig != nil {
|
||||
if newSendBell != node!.detectionSensorConfig!.sendBell { hasChanges = true }
|
||||
.onChange(of: sendBell) {
|
||||
if let val = node?.detectionSensorConfig?.sendBell {
|
||||
hasChanges = $0 != val
|
||||
}
|
||||
}
|
||||
.onChange(of: detectionTriggeredHigh) { newDetectionTriggeredHigh in
|
||||
|
|
@ -210,9 +214,9 @@ struct DetectionSensorConfig: View {
|
|||
if newDetectionTriggeredHigh != node!.detectionSensorConfig!.detectionTriggeredHigh { hasChanges = true }
|
||||
}
|
||||
}
|
||||
.onChange(of: usePullup) { newUsePullup in
|
||||
if node != nil && node?.detectionSensorConfig != nil {
|
||||
if newUsePullup != node!.detectionSensorConfig!.usePullup { hasChanges = true }
|
||||
.onChange(of: usePullup) {
|
||||
if let val = node?.detectionSensorConfig?.usePullup {
|
||||
hasChanges = $0 != val
|
||||
}
|
||||
}
|
||||
.onChange(of: name) { newName in
|
||||
|
|
|
|||
|
|
@ -190,12 +190,16 @@ struct ExternalNotificationConfig: View {
|
|||
}
|
||||
}
|
||||
.navigationTitle("external.notification.config")
|
||||
.navigationBarItems(trailing:
|
||||
ZStack {
|
||||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
|
||||
})
|
||||
.navigationBarItems(
|
||||
trailing: ZStack {
|
||||
ConnectedDevice(
|
||||
bluetoothOn: bleManager.isSwitchedOn,
|
||||
deviceConnected: bleManager.connectedPeripheral != nil,
|
||||
name: bleManager.connectedPeripheral?.shortName ?? "?"
|
||||
)
|
||||
}
|
||||
)
|
||||
.onAppear {
|
||||
setExternalNotificationValues()
|
||||
// Need to request a TelemetryModuleConfig from the remote node before allowing changes
|
||||
if bleManager.connectedPeripheral != nil && node?.externalNotificationConfig == nil {
|
||||
Logger.mesh.info("empty external notification module config")
|
||||
|
|
@ -205,44 +209,44 @@ struct ExternalNotificationConfig: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
.onChange(of: enabled) { newEnabled in
|
||||
if node != nil && node!.externalNotificationConfig != nil {
|
||||
if newEnabled != node!.externalNotificationConfig!.enabled { hasChanges = true }
|
||||
.onChange(of: enabled) {
|
||||
if let val = node?.externalNotificationConfig?.enabled {
|
||||
hasChanges = $0 != val
|
||||
}
|
||||
}
|
||||
.onChange(of: alertBell) { newAlertBell in
|
||||
if node != nil && node!.externalNotificationConfig != nil {
|
||||
if newAlertBell != node!.externalNotificationConfig!.alertBell { hasChanges = true }
|
||||
.onChange(of: alertBell) {
|
||||
if let val = node?.externalNotificationConfig?.alertBell {
|
||||
hasChanges = $0 != val
|
||||
}
|
||||
}
|
||||
.onChange(of: alertBellBuzzer) { newAlertBellBuzzer in
|
||||
if node != nil && node!.externalNotificationConfig != nil {
|
||||
if newAlertBellBuzzer != node!.externalNotificationConfig!.alertBellBuzzer { hasChanges = true }
|
||||
.onChange(of: alertBellBuzzer) {
|
||||
if let val = node?.externalNotificationConfig?.alertBellBuzzer {
|
||||
hasChanges = $0 != val
|
||||
}
|
||||
}
|
||||
.onChange(of: alertBellVibra) { newAlertBellVibra in
|
||||
if node != nil && node!.externalNotificationConfig != nil {
|
||||
if newAlertBellVibra != node!.externalNotificationConfig!.alertBellVibra { hasChanges = true }
|
||||
.onChange(of: alertBellVibra) {
|
||||
if let val = node?.externalNotificationConfig?.alertBellVibra {
|
||||
hasChanges = $0 != val
|
||||
}
|
||||
}
|
||||
.onChange(of: alertMessage) { newAlertMessage in
|
||||
if node != nil && node!.externalNotificationConfig != nil {
|
||||
if newAlertMessage != node!.externalNotificationConfig!.alertMessage { hasChanges = true }
|
||||
.onChange(of: alertMessage) {
|
||||
if let val = node?.externalNotificationConfig?.alertMessage {
|
||||
hasChanges = $0 != val
|
||||
}
|
||||
}
|
||||
.onChange(of: alertMessageBuzzer) { newAlertMessageBuzzer in
|
||||
if node != nil && node!.externalNotificationConfig != nil {
|
||||
if newAlertMessageBuzzer != node!.externalNotificationConfig!.alertMessageBuzzer { hasChanges = true }
|
||||
.onChange(of: alertMessageBuzzer) {
|
||||
if let val = node?.externalNotificationConfig?.alertMessageBuzzer {
|
||||
hasChanges = $0 != val
|
||||
}
|
||||
}
|
||||
.onChange(of: alertMessageVibra) { newAlertMessageVibra in
|
||||
if node != nil && node!.externalNotificationConfig != nil {
|
||||
if newAlertMessageVibra != node!.externalNotificationConfig!.alertMessageVibra { hasChanges = true }
|
||||
.onChange(of: alertMessageVibra) {
|
||||
if let val = node?.externalNotificationConfig?.alertMessageVibra {
|
||||
hasChanges = $0 != val
|
||||
}
|
||||
}
|
||||
.onChange(of: active) { newActive in
|
||||
if node != nil && node!.externalNotificationConfig != nil {
|
||||
if newActive != node!.externalNotificationConfig!.active { hasChanges = true }
|
||||
.onChange(of: active) {
|
||||
if let val = node?.externalNotificationConfig?.active {
|
||||
hasChanges = $0 != val
|
||||
}
|
||||
}
|
||||
.onChange(of: output) { newOutput in
|
||||
|
|
@ -265,9 +269,9 @@ struct ExternalNotificationConfig: View {
|
|||
if newOutputMs != node!.externalNotificationConfig!.outputMilliseconds { hasChanges = true }
|
||||
}
|
||||
}
|
||||
.onChange(of: usePWM) { newUsePWM in
|
||||
if node != nil && node!.externalNotificationConfig != nil {
|
||||
if newUsePWM != node!.externalNotificationConfig!.usePWM { hasChanges = true }
|
||||
.onChange(of: usePWM) {
|
||||
if let val = node?.externalNotificationConfig?.usePWM {
|
||||
hasChanges = $0 != val
|
||||
}
|
||||
}
|
||||
.onChange(of: nagTimeout) { newNagTimeout in
|
||||
|
|
@ -275,9 +279,9 @@ struct ExternalNotificationConfig: View {
|
|||
if newNagTimeout != node!.externalNotificationConfig!.nagTimeout { hasChanges = true }
|
||||
}
|
||||
}
|
||||
.onChange(of: useI2SAsBuzzer) { newUseI2SAsBuzzer in
|
||||
if node != nil && node!.externalNotificationConfig != nil {
|
||||
if newUseI2SAsBuzzer != node!.externalNotificationConfig!.useI2SAsBuzzer { hasChanges = true }
|
||||
.onChange(of: useI2SAsBuzzer) {
|
||||
if let val = node?.externalNotificationConfig?.useI2SAsBuzzer {
|
||||
hasChanges = $0 != val
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -271,10 +271,27 @@ struct MQTTConfig: View {
|
|||
}
|
||||
}
|
||||
.navigationTitle("mqtt.config")
|
||||
.navigationBarItems(trailing:
|
||||
ZStack {
|
||||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?", mqttProxyConnected: bleManager.mqttProxyConnected)
|
||||
})
|
||||
.navigationBarItems(
|
||||
trailing: ZStack {
|
||||
ConnectedDevice(
|
||||
bluetoothOn: bleManager.isSwitchedOn,
|
||||
deviceConnected: bleManager.connectedPeripheral != nil,
|
||||
name: bleManager.connectedPeripheral?.shortName ?? "?"
|
||||
)
|
||||
}
|
||||
)
|
||||
.onChange(of: enabled) {
|
||||
if $0 != node?.mqttConfig?.enabled { hasChanges = true }
|
||||
}
|
||||
.onChange(of: proxyToClientEnabled) { newProxyToClientEnabled in
|
||||
if newProxyToClientEnabled {
|
||||
jsonEnabled = false
|
||||
}
|
||||
if newProxyToClientEnabled != node?.mqttConfig?.proxyToClientEnabled { hasChanges = true }
|
||||
if newProxyToClientEnabled {
|
||||
jsonEnabled = false
|
||||
}
|
||||
}
|
||||
.onChange(of: address) { newAddress in
|
||||
if node != nil && node?.mqttConfig != nil {
|
||||
if newAddress != node!.mqttConfig!.address { hasChanges = true }
|
||||
|
|
@ -298,39 +315,17 @@ struct MQTTConfig: View {
|
|||
.onChange(of: selectedTopic) { newSelectedTopic in
|
||||
root = newSelectedTopic
|
||||
}
|
||||
.onChange(of: enabled) { newEnabled in
|
||||
if node != nil && node?.mqttConfig != nil {
|
||||
if newEnabled != node!.mqttConfig!.enabled { hasChanges = true }
|
||||
}
|
||||
}
|
||||
.onChange(of: proxyToClientEnabled) { newProxyToClientEnabled in
|
||||
if newProxyToClientEnabled {
|
||||
jsonEnabled = false
|
||||
}
|
||||
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: encryptionEnabled) {
|
||||
if $0 != node?.mqttConfig?.encryptionEnabled { hasChanges = true }
|
||||
}
|
||||
.onChange(of: jsonEnabled) { newJsonEnabled in
|
||||
if newJsonEnabled {
|
||||
proxyToClientEnabled = false
|
||||
}
|
||||
if node != nil && node?.mqttConfig != nil {
|
||||
if newJsonEnabled != node!.mqttConfig!.jsonEnabled { hasChanges = true }
|
||||
}
|
||||
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: tlsEnabled) {
|
||||
if $0 != node?.mqttConfig?.tlsEnabled { hasChanges = true }
|
||||
}
|
||||
.onChange(of: mqttConnected) { newMqttConnected in
|
||||
if newMqttConnected == false {
|
||||
|
|
@ -343,13 +338,8 @@ struct MQTTConfig: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
.onChange(of: mapReportingEnabled) { newMapReportingEnabled in
|
||||
if node != nil && node?.mqttConfig != nil {
|
||||
if newMapReportingEnabled != node!.mqttConfig!.mapReportingEnabled { hasChanges = true }
|
||||
}
|
||||
}
|
||||
.onChange(of: preciseLocation) { _ in
|
||||
hasChanges = true
|
||||
.onChange(of: mapReportingEnabled) {
|
||||
if $0 != node?.mqttConfig?.mapReportingEnabled { hasChanges = true }
|
||||
}
|
||||
.onChange(of: mapPublishIntervalSecs) { newMapPublishIntervalSecs in
|
||||
if node != nil && node?.mqttConfig != nil {
|
||||
|
|
@ -357,7 +347,6 @@ struct MQTTConfig: View {
|
|||
}
|
||||
}
|
||||
.onAppear {
|
||||
setMqttValues()
|
||||
// Need to request a TelemetryModuleConfig from the remote node before allowing changes
|
||||
if bleManager.connectedPeripheral != nil && node?.mqttConfig == nil {
|
||||
Logger.mesh.info("empty mqtt module config")
|
||||
|
|
|
|||
|
|
@ -58,7 +58,6 @@ struct PaxCounterConfig: View {
|
|||
)
|
||||
})
|
||||
.onAppear {
|
||||
setPaxValues()
|
||||
// Need to request a PAX Counter module config from the remote node before allowing changes
|
||||
if bleManager.connectedPeripheral != nil && node?.paxCounterConfig == nil {
|
||||
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? 0, context: context)
|
||||
|
|
@ -68,14 +67,10 @@ struct PaxCounterConfig: View {
|
|||
}
|
||||
}
|
||||
.onChange(of: enabled) {
|
||||
if let val = node?.paxCounterConfig?.enabled {
|
||||
hasChanges = $0 != val
|
||||
}
|
||||
if $0 != node?.paxCounterConfig?.enabled { hasChanges = true }
|
||||
}
|
||||
.onChange(of: paxcounterUpdateInterval) {
|
||||
if let val = node?.paxCounterConfig?.updateInterval {
|
||||
hasChanges = $0 != val
|
||||
}
|
||||
if $0 != node?.paxCounterConfig?.updateInterval ?? -1 { hasChanges = true }
|
||||
}
|
||||
|
||||
SaveConfigButton(node: node, hasChanges: $hasChanges) {
|
||||
|
|
|
|||
|
|
@ -72,12 +72,16 @@ struct RangeTestConfig: View {
|
|||
}
|
||||
}
|
||||
.navigationTitle("range.test.config")
|
||||
.navigationBarItems(trailing:
|
||||
ZStack {
|
||||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
|
||||
})
|
||||
.navigationBarItems(
|
||||
trailing: ZStack {
|
||||
ConnectedDevice(
|
||||
bluetoothOn: bleManager.isSwitchedOn,
|
||||
deviceConnected: bleManager.connectedPeripheral != nil,
|
||||
name: bleManager.connectedPeripheral?.shortName ?? "?"
|
||||
)
|
||||
}
|
||||
)
|
||||
.onAppear {
|
||||
setRangeTestValues()
|
||||
// Need to request a RangeTestModule Config from the remote node before allowing changes
|
||||
if bleManager.connectedPeripheral != nil && node?.rangeTestConfig == nil {
|
||||
Logger.mesh.debug("empty range test module config")
|
||||
|
|
@ -87,20 +91,14 @@ struct RangeTestConfig: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
.onChange(of: enabled) { newEnabled in
|
||||
if node != nil && node!.rangeTestConfig != nil {
|
||||
if newEnabled != node!.rangeTestConfig!.enabled { hasChanges = true }
|
||||
}
|
||||
.onChange(of: enabled) {
|
||||
if $0 != node?.rangeTestConfig?.enabled { hasChanges = true }
|
||||
}
|
||||
.onChange(of: save) { newSave in
|
||||
if node != nil && node!.rangeTestConfig != nil {
|
||||
if newSave != node!.rangeTestConfig!.save { hasChanges = true }
|
||||
}
|
||||
.onChange(of: save) {
|
||||
if $0 != node?.rangeTestConfig?.save { hasChanges = true }
|
||||
}
|
||||
.onChange(of: sender) { newSender in
|
||||
if node != nil && node!.rangeTestConfig != nil {
|
||||
if newSender != node!.rangeTestConfig!.sender { hasChanges = true }
|
||||
}
|
||||
.onChange(of: sender) {
|
||||
if $0 != node?.rangeTestConfig?.sender ?? -1 { hasChanges = true }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,12 +62,16 @@ struct RtttlConfig: View {
|
|||
}
|
||||
}
|
||||
.navigationTitle("config.ringtone.title")
|
||||
.navigationBarItems(trailing:
|
||||
ZStack {
|
||||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
|
||||
})
|
||||
.navigationBarItems(
|
||||
trailing: ZStack {
|
||||
ConnectedDevice(
|
||||
bluetoothOn: bleManager.isSwitchedOn,
|
||||
deviceConnected: bleManager.connectedPeripheral != nil,
|
||||
name: bleManager.connectedPeripheral?.shortName ?? "?"
|
||||
)
|
||||
}
|
||||
)
|
||||
.onAppear {
|
||||
setRtttLConfigValue()
|
||||
// Need to request a Rtttl Config from the remote node before allowing changes
|
||||
if bleManager.connectedPeripheral != nil && (node?.rtttlConfig == nil || node?.rtttlConfig?.ringtone?.count ?? 0 == 0) {
|
||||
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context)
|
||||
|
|
|
|||
|
|
@ -127,13 +127,16 @@ struct SerialConfig: View {
|
|||
}
|
||||
}
|
||||
.navigationTitle("serial.config")
|
||||
.navigationBarItems(trailing:
|
||||
|
||||
ZStack {
|
||||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
|
||||
})
|
||||
.navigationBarItems(
|
||||
trailing: ZStack {
|
||||
ConnectedDevice(
|
||||
bluetoothOn: bleManager.isSwitchedOn,
|
||||
deviceConnected: bleManager.connectedPeripheral != nil,
|
||||
name: bleManager.connectedPeripheral?.shortName ?? "?"
|
||||
)
|
||||
}
|
||||
)
|
||||
.onAppear {
|
||||
setSerialValues()
|
||||
// Need to request a SerialModuleConfig from the remote node before allowing changes
|
||||
if bleManager.connectedPeripheral != nil && node?.serialConfig == nil {
|
||||
Logger.mesh.debug("empty serial module config")
|
||||
|
|
@ -142,61 +145,40 @@ struct SerialConfig: View {
|
|||
_ = bleManager.requestSerialModuleConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
.onChange(of: enabled) { newEnabled in
|
||||
|
||||
if node != nil && node!.serialConfig != nil {
|
||||
|
||||
if newEnabled != node!.serialConfig!.enabled { hasChanges = true }
|
||||
}
|
||||
.onChange(of: enabled) {
|
||||
if $0 != node?.serialConfig?.enabled { hasChanges = true }
|
||||
}
|
||||
.onChange(of: echo) { newEcho in
|
||||
|
||||
if node != nil && node!.serialConfig != nil {
|
||||
|
||||
if newEcho != node!.serialConfig!.echo { hasChanges = true }
|
||||
}
|
||||
.onChange(of: echo) {
|
||||
if $0 != node?.serialConfig?.echo { hasChanges = true }
|
||||
}
|
||||
.onChange(of: rxd) { newRxd in
|
||||
|
||||
if node != nil && node!.serialConfig != nil {
|
||||
|
||||
if newRxd != node!.serialConfig!.rxd { hasChanges = true }
|
||||
}
|
||||
}
|
||||
.onChange(of: txd) { newTxd in
|
||||
|
||||
if node != nil && node!.serialConfig != nil {
|
||||
|
||||
if newTxd != node!.serialConfig!.txd { hasChanges = true }
|
||||
}
|
||||
}
|
||||
.onChange(of: baudRate) { newBaud in
|
||||
|
||||
if node != nil && node!.serialConfig != nil {
|
||||
|
||||
if newBaud != node!.serialConfig!.baudRate { hasChanges = true }
|
||||
}
|
||||
}
|
||||
.onChange(of: timeout) { newTimeout in
|
||||
|
||||
if node != nil && node!.serialConfig != nil {
|
||||
|
||||
if newTimeout != node!.serialConfig!.timeout { hasChanges = true }
|
||||
}
|
||||
}
|
||||
.onChange(of: overrideConsoleSerialPort) { newOverrideConsoleSerialPort in
|
||||
|
||||
if node != nil && node!.serialConfig != nil {
|
||||
|
||||
if newOverrideConsoleSerialPort != node!.serialConfig!.overrideConsoleSerialPort { hasChanges = true }
|
||||
}
|
||||
}
|
||||
.onChange(of: mode) { newMode in
|
||||
|
||||
if node != nil && node!.serialConfig != nil {
|
||||
|
||||
if newMode != node!.serialConfig!.mode { hasChanges = true }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -137,10 +137,15 @@ struct StoreForwardConfig: View {
|
|||
}
|
||||
}
|
||||
.navigationTitle("storeforward.config")
|
||||
.navigationBarItems(trailing:
|
||||
ZStack {
|
||||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
|
||||
})
|
||||
.navigationBarItems(
|
||||
trailing: ZStack {
|
||||
ConnectedDevice(
|
||||
bluetoothOn: bleManager.isSwitchedOn,
|
||||
deviceConnected: bleManager.connectedPeripheral != nil,
|
||||
name: bleManager.connectedPeripheral?.shortName ?? "?"
|
||||
)
|
||||
}
|
||||
)
|
||||
.onAppear {
|
||||
// Need to request a Detection Sensor Module Config from the remote node before allowing changes
|
||||
if bleManager.connectedPeripheral != nil && node?.storeForwardConfig == nil {
|
||||
|
|
@ -150,7 +155,6 @@ struct StoreForwardConfig: View {
|
|||
_ = bleManager.requestStoreAndForwardModuleConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0)
|
||||
}
|
||||
}
|
||||
setStoreAndForwardValues()
|
||||
}
|
||||
.onChange(of: enabled) { newEnabled in
|
||||
if node != nil && node?.storeForwardConfig != nil {
|
||||
|
|
|
|||
|
|
@ -125,12 +125,16 @@ struct TelemetryConfig: View {
|
|||
}
|
||||
}
|
||||
.navigationTitle("telemetry.config")
|
||||
.navigationBarItems(trailing:
|
||||
ZStack {
|
||||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
|
||||
})
|
||||
.navigationBarItems(
|
||||
trailing: ZStack {
|
||||
ConnectedDevice(
|
||||
bluetoothOn: bleManager.isSwitchedOn,
|
||||
deviceConnected: bleManager.connectedPeripheral != nil,
|
||||
name: bleManager.connectedPeripheral?.shortName ?? "?"
|
||||
)
|
||||
}
|
||||
)
|
||||
.onAppear {
|
||||
setTelemetryValues()
|
||||
// Need to request a TelemetryModuleConfig from the remote node before allowing changes
|
||||
if bleManager.connectedPeripheral != nil && node?.telemetryConfig == nil {
|
||||
Logger.mesh.info("empty telemetry module config")
|
||||
|
|
|
|||
|
|
@ -109,12 +109,16 @@ struct NetworkConfig: View {
|
|||
}
|
||||
}
|
||||
.navigationTitle("network.config")
|
||||
.navigationBarItems(trailing:
|
||||
ZStack {
|
||||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
|
||||
})
|
||||
.navigationBarItems(
|
||||
trailing: ZStack {
|
||||
ConnectedDevice(
|
||||
bluetoothOn: bleManager.isSwitchedOn,
|
||||
deviceConnected: bleManager.connectedPeripheral != nil,
|
||||
name: bleManager.connectedPeripheral?.shortName ?? "?"
|
||||
)
|
||||
}
|
||||
)
|
||||
.onAppear {
|
||||
setNetworkValues()
|
||||
// Need to request a NetworkConfig from the remote node before allowing changes
|
||||
if bleManager.connectedPeripheral != nil && node?.networkConfig == nil {
|
||||
Logger.mesh.info("empty network config")
|
||||
|
|
@ -124,30 +128,20 @@ struct NetworkConfig: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
.onChange(of: wifiEnabled) { newEnabled in
|
||||
if node != nil && node!.networkConfig != nil {
|
||||
if newEnabled != node!.networkConfig!.wifiEnabled { hasChanges = true }
|
||||
}
|
||||
.onChange(of: wifiEnabled) {
|
||||
if $0 != node?.networkConfig?.wifiEnabled { hasChanges = true }
|
||||
}
|
||||
.onChange(of: wifiSsid) { newSSID in
|
||||
if node != nil && node!.networkConfig != nil {
|
||||
if newSSID != node!.networkConfig!.wifiSsid { hasChanges = true }
|
||||
}
|
||||
if newSSID != node?.networkConfig?.wifiSsid { hasChanges = true }
|
||||
}
|
||||
.onChange(of: wifiPsk) { newPsk in
|
||||
if node != nil && node!.networkConfig != nil {
|
||||
if newPsk != node!.networkConfig!.wifiPsk { hasChanges = true }
|
||||
}
|
||||
if newPsk != node?.networkConfig?.wifiPsk { hasChanges = true }
|
||||
}
|
||||
.onChange(of: wifiMode) { newMode in
|
||||
if node != nil && node!.networkConfig != nil {
|
||||
if newMode != node!.networkConfig!.wifiMode { hasChanges = true }
|
||||
}
|
||||
.onChange(of: wifiMode) {
|
||||
if $0 != node?.networkConfig?.wifiMode ?? -1 { hasChanges = true }
|
||||
}
|
||||
.onChange(of: ethEnabled) { newEthEnabled in
|
||||
if node != nil && node!.networkConfig != nil {
|
||||
if newEthEnabled != node!.networkConfig!.ethEnabled { hasChanges = true }
|
||||
}
|
||||
.onChange(of: ethEnabled) {
|
||||
if $0 != node?.networkConfig?.ethEnabled { hasChanges = true }
|
||||
}
|
||||
}
|
||||
func setNetworkValues() {
|
||||
|
|
|
|||
|
|
@ -377,7 +377,6 @@ struct PositionConfig: View {
|
|||
}
|
||||
)
|
||||
.onAppear {
|
||||
setPositionValues()
|
||||
supportedVersion = bleManager.connectedVersion == "0.0.0" || self.minimumVersion.compare(bleManager.connectedVersion, options: .numeric) == .orderedAscending || minimumVersion.compare(bleManager.connectedVersion, options: .numeric) == .orderedSame
|
||||
// Need to request a PositionConfig from the remote node before allowing changes
|
||||
if let connectedPeripheral = bleManager.connectedPeripheral, node?.positionConfig == nil {
|
||||
|
|
@ -405,51 +404,39 @@ struct PositionConfig: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
.onChange(of: gpsMode) { _ in
|
||||
handleChanges()
|
||||
.onChange(of: gpsMode) { newGpsMode in
|
||||
if newGpsMode != node?.positionConfig?.gpsMode ?? 0 { hasChanges = true }
|
||||
}
|
||||
.onChange(of: rxGpio) { _ in
|
||||
handleChanges()
|
||||
.onChange(of: rxGpio) { newRxGpio in
|
||||
if newRxGpio != node?.positionConfig?.rxGpio ?? 0 { hasChanges = true }
|
||||
}
|
||||
.onChange(of: txGpio) { _ in
|
||||
handleChanges()
|
||||
.onChange(of: txGpio) { newTxGpio in
|
||||
if newTxGpio != node?.positionConfig?.txGpio ?? 0 { hasChanges = true }
|
||||
}
|
||||
.onChange(of: gpsEnGpio) { _ in
|
||||
handleChanges()
|
||||
.onChange(of: gpsEnGpio) { newGpsEnGpio in
|
||||
if newGpsEnGpio != node?.positionConfig?.gpsEnGpio ?? 0 { hasChanges = true }
|
||||
}
|
||||
.onChange(of: smartPositionEnabled) { _ in
|
||||
handleChanges()
|
||||
.onChange(of: smartPositionEnabled) { newSmartPositionEnabled in
|
||||
if newSmartPositionEnabled != node?.positionConfig?.smartPositionEnabled { hasChanges = true }
|
||||
}
|
||||
.onChange(of: positionBroadcastSeconds) { _ in
|
||||
handleChanges()
|
||||
.onChange(of: positionBroadcastSeconds) { newPositionBroadcastSeconds in
|
||||
if newPositionBroadcastSeconds != node?.positionConfig?.positionBroadcastSeconds ?? 0 { hasChanges = true }
|
||||
}
|
||||
.onChange(of: broadcastSmartMinimumIntervalSecs) { _ in
|
||||
handleChanges()
|
||||
.onChange(of: broadcastSmartMinimumIntervalSecs) { newBroadcastSmartMinimumIntervalSecs in
|
||||
if newBroadcastSmartMinimumIntervalSecs != node?.positionConfig?.broadcastSmartMinimumIntervalSecs ?? 0 { hasChanges = true }
|
||||
}
|
||||
.onChange(of: broadcastSmartMinimumDistance) { _ in
|
||||
handleChanges()
|
||||
.onChange(of: broadcastSmartMinimumDistance) { newBroadcastSmartMinimumDistance in
|
||||
if newBroadcastSmartMinimumDistance != node?.positionConfig?.broadcastSmartMinimumDistance ?? 0 { hasChanges = true }
|
||||
}
|
||||
.onChange(of: gpsUpdateInterval) { _ in
|
||||
handleChanges()
|
||||
}
|
||||
.onChange(of: positionFlags) { _ in
|
||||
handleChanges()
|
||||
.onChange(of: gpsUpdateInterval) { newGpsUpdateInterval in
|
||||
if newGpsUpdateInterval != node?.positionConfig?.gpsUpdateInterval ?? 0 { hasChanges = true }
|
||||
}
|
||||
}
|
||||
|
||||
func handleChanges() {
|
||||
func handlePositionFlagtChanges() {
|
||||
guard let positionConfig = node?.positionConfig else { return }
|
||||
let pf = PositionFlags(rawValue: self.positionFlags)
|
||||
hasChanges = positionConfig.deviceGpsEnabled != deviceGpsEnabled ||
|
||||
positionConfig.gpsMode != gpsMode ||
|
||||
positionConfig.rxGpio != rxGpio ||
|
||||
positionConfig.txGpio != txGpio ||
|
||||
positionConfig.gpsEnGpio != gpsEnGpio ||
|
||||
positionConfig.smartPositionEnabled != smartPositionEnabled ||
|
||||
positionConfig.positionBroadcastSeconds != positionBroadcastSeconds ||
|
||||
positionConfig.broadcastSmartMinimumIntervalSecs != broadcastSmartMinimumIntervalSecs ||
|
||||
positionConfig.broadcastSmartMinimumDistance != broadcastSmartMinimumDistance ||
|
||||
positionConfig.gpsUpdateInterval != gpsUpdateInterval ||
|
||||
hasChanges =
|
||||
pf.contains(.Altitude) ||
|
||||
pf.contains(.AltitudeMsl) ||
|
||||
pf.contains(.Satsinview) ||
|
||||
|
|
|
|||
|
|
@ -119,7 +119,6 @@ struct PowerConfig: View {
|
|||
}
|
||||
.onAppear {
|
||||
Api().loadDeviceHardwareData { (hw) in
|
||||
|
||||
for device in hw {
|
||||
let currentHardware = node?.user?.hwModel ?? "UNSET"
|
||||
let deviceString = device.hwModelSlug.replacingOccurrences(of: "_", with: "")
|
||||
|
|
@ -128,8 +127,6 @@ struct PowerConfig: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
setPowerValues()
|
||||
|
||||
// Need to request a Power config from the remote node before allowing changes
|
||||
if bleManager.connectedPeripheral != nil && node?.powerConfig == nil {
|
||||
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? 0, context: context)
|
||||
|
|
@ -139,40 +136,30 @@ struct PowerConfig: View {
|
|||
}
|
||||
}
|
||||
.onChange(of: isPowerSaving) {
|
||||
if let val = node?.powerConfig?.isPowerSaving {
|
||||
hasChanges = $0 != val
|
||||
}
|
||||
if $0 != node?.powerConfig?.isPowerSaving { hasChanges = true }
|
||||
}
|
||||
.onChange(of: shutdownOnPowerLoss) { _ in
|
||||
hasChanges = true
|
||||
.onChange(of: shutdownOnPowerLoss) { newShutdownOnPowerLoss in
|
||||
if newShutdownOnPowerLoss {
|
||||
hasChanges = true
|
||||
}
|
||||
}
|
||||
.onChange(of: shutdownAfterSecs) {
|
||||
if let val = node?.powerConfig?.onBatteryShutdownAfterSecs {
|
||||
hasChanges = $0 != val
|
||||
}
|
||||
if $0 != node?.powerConfig?.minWakeSecs ?? -1 { hasChanges = true }
|
||||
}
|
||||
.onChange(of: adcOverride) { _ in
|
||||
hasChanges = true
|
||||
}
|
||||
.onChange(of: adcMultiplier) {
|
||||
if let val = node?.powerConfig?.adcMultiplierOverride {
|
||||
hasChanges = $0 != val
|
||||
}
|
||||
.onChange(of: adcMultiplier) { newAdcMultiplier in
|
||||
if newAdcMultiplier != node?.powerConfig?.adcMultiplierOverride ?? -1 { hasChanges = true }
|
||||
}
|
||||
.onChange(of: waitBluetoothSecs) {
|
||||
if let val = node?.powerConfig?.waitBluetoothSecs {
|
||||
hasChanges = $0 != val
|
||||
}
|
||||
if $0 != node?.powerConfig?.waitBluetoothSecs ?? -1 { hasChanges = true }
|
||||
}
|
||||
.onChange(of: lsSecs) {
|
||||
if let val = node?.powerConfig?.lsSecs {
|
||||
hasChanges = $0 != val
|
||||
}
|
||||
if $0 != node?.powerConfig?.lsSecs ?? -1 { hasChanges = true }
|
||||
}
|
||||
.onChange(of: minWakeSecs) {
|
||||
if let val = node?.powerConfig?.minWakeSecs {
|
||||
hasChanges = $0 != val
|
||||
}
|
||||
if $0 != node?.powerConfig?.minWakeSecs ?? -1 { hasChanges = true }
|
||||
}
|
||||
|
||||
SaveConfigButton(node: node, hasChanges: $hasChanges) {
|
||||
|
|
|
|||
185
Meshtastic/Views/Settings/Config/SecurityConfig.swift
Normal file
185
Meshtastic/Views/Settings/Config/SecurityConfig.swift
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
//
|
||||
// Security.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Copyright(c) Garth Vander Houwen 8/7/24.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
import CoreData
|
||||
import MeshtasticProtobufs
|
||||
import OSLog
|
||||
|
||||
struct SecurityConfig: View {
|
||||
|
||||
private var idiom: UIUserInterfaceIdiom { UIDevice.current.userInterfaceIdiom }
|
||||
@Environment(\.managedObjectContext) var context
|
||||
@EnvironmentObject var bleManager: BLEManager
|
||||
@Environment(\.dismiss) private var goBack
|
||||
|
||||
var node: NodeInfoEntity?
|
||||
|
||||
@State var hasChanges = false
|
||||
@State var publicKey = ""
|
||||
@State var privateKey = ""
|
||||
@State var adminKey = ""
|
||||
@State var isManaged = false
|
||||
@State var serialEnabled = false
|
||||
@State var debugLogApiEnabled = false
|
||||
@State var bluetoothLoggingEnabled = false
|
||||
@State var adminChannelEnabled = false
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
Form {
|
||||
ConfigHeader(title: "Security", config: \.securityConfig, node: node, onAppear: setSecurityValues)
|
||||
|
||||
Section(header: Text("Admin & Direct Message Keys")) {
|
||||
VStack(alignment: .leading) {
|
||||
Label("Public Key", systemImage: "key")
|
||||
SecureInput("Public Key", text: $publicKey)
|
||||
Text("Sent out to other nodes on the mesh to allow them to compute a shared secret key.")
|
||||
.foregroundStyle(.secondary)
|
||||
.font(idiom == .phone ? .caption : .callout)
|
||||
}
|
||||
VStack(alignment: .leading) {
|
||||
Label("Private Key", systemImage: "key.fill")
|
||||
SecureInput("Private Key", text: $privateKey)
|
||||
Text("Used to create a shared key with a remote device.")
|
||||
.foregroundStyle(.secondary)
|
||||
.font(idiom == .phone ? .caption : .callout)
|
||||
}
|
||||
VStack(alignment: .leading) {
|
||||
Label("Admin Key", systemImage: "key.viewfinder")
|
||||
SecureInput("Private Key", text: $adminKey)
|
||||
Text("The public key authorized to send admin messages to this node.")
|
||||
.foregroundStyle(.secondary)
|
||||
.font(idiom == .phone ? .caption : .callout)
|
||||
}
|
||||
}
|
||||
Section(header: Text("Logs")) {
|
||||
Toggle(isOn: $bluetoothLoggingEnabled) {
|
||||
Label("Bluetooth Logs", systemImage: "dot.radiowaves.right")
|
||||
Text("View and export position-redacted device logs over Bluetooth")
|
||||
Link("View Logs", destination: URL(string: "meshtastic:///settings/debugLogs")!)
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
}
|
||||
Section(header: Text("Administration")) {
|
||||
if adminKey.length > 0 || adminChannelEnabled {
|
||||
Toggle(isOn: $isManaged) {
|
||||
Label("Managed Device", systemImage: "gearshape.arrow.triangle.2.circlepath")
|
||||
Text("Device is managed by a mesh administrator.")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
}
|
||||
Toggle(isOn: $adminChannelEnabled) {
|
||||
Label("Legacy Administration", systemImage: "lock.slash")
|
||||
Text("Allow incoming device control over the insecure legacy admin channel.")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
}
|
||||
Section(header: Text("Developer")) {
|
||||
Toggle(isOn: $serialEnabled) {
|
||||
Label("Serial Console", systemImage: "terminal")
|
||||
Text("Serial Console over the Stream API.")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
if serialEnabled {
|
||||
Toggle(isOn: $debugLogApiEnabled) {
|
||||
Label("Serial Debug Logs", systemImage: "ant.fill")
|
||||
Text("Output live debug logging over serial.")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.scrollDismissesKeyboard(.immediately)
|
||||
.navigationTitle("Security Config")
|
||||
.navigationBarItems(trailing: ZStack {
|
||||
ConnectedDevice(
|
||||
bluetoothOn: bleManager.isSwitchedOn,
|
||||
deviceConnected: bleManager.connectedPeripheral != nil,
|
||||
name: "\(bleManager.connectedPeripheral?.shortName ?? "?")"
|
||||
)
|
||||
})
|
||||
.onChange(of: isManaged) {
|
||||
if $0 != node?.securityConfig?.isManaged { hasChanges = true }
|
||||
}
|
||||
.onChange(of: serialEnabled) {
|
||||
if $0 != node?.securityConfig?.serialEnabled { hasChanges = true }
|
||||
}
|
||||
.onChange(of: debugLogApiEnabled) {
|
||||
if $0 != node?.securityConfig?.debugLogApiEnabled { hasChanges = true }
|
||||
}
|
||||
.onChange(of: bluetoothLoggingEnabled) {
|
||||
if $0 != node?.securityConfig?.bluetoothLoggingEnabled { hasChanges = true }
|
||||
}
|
||||
.onChange(of: adminChannelEnabled) {
|
||||
if $0 != node?.securityConfig?.adminChannelEnabled { hasChanges = true }
|
||||
}
|
||||
.onChange(of: publicKey) { _ in
|
||||
hasChanges = true
|
||||
}
|
||||
.onChange(of: privateKey) { _ in
|
||||
hasChanges = true
|
||||
}
|
||||
.onChange(of: adminKey) { _ in
|
||||
hasChanges = true
|
||||
}
|
||||
.onFirstAppear {
|
||||
// Need to request a Power config from the remote node before allowing changes
|
||||
if bleManager.connectedPeripheral != nil && node?.securityConfig == nil {
|
||||
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? 0, context: context)
|
||||
if node != nil && connectedNode != nil {
|
||||
_ = bleManager.requestSecurityConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SaveConfigButton(node: node, hasChanges: $hasChanges) {
|
||||
guard let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context),
|
||||
let fromUser = connectedNode.user,
|
||||
let toUser = node?.user else {
|
||||
return
|
||||
}
|
||||
|
||||
var config = Config.SecurityConfig()
|
||||
config.publicKey = Data(base64Encoded: publicKey) ?? Data()
|
||||
config.privateKey = Data(base64Encoded: privateKey) ?? Data()
|
||||
config.adminKey = Data(base64Encoded: adminKey) ?? Data()
|
||||
config.isManaged = isManaged
|
||||
config.serialEnabled = serialEnabled
|
||||
config.debugLogApiEnabled = debugLogApiEnabled
|
||||
config.bluetoothLoggingEnabled = bluetoothLoggingEnabled
|
||||
config.adminChannelEnabled = adminChannelEnabled
|
||||
|
||||
let adminMessageId = bleManager.saveSecurityConfig(
|
||||
config: config,
|
||||
fromUser: fromUser,
|
||||
toUser: toUser,
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func setSecurityValues() {
|
||||
self.publicKey = node?.securityConfig?.publicKey?.base64EncodedString() ?? ""
|
||||
self.privateKey = node?.securityConfig?.privateKey?.base64EncodedString() ?? ""
|
||||
self.adminKey = node?.securityConfig?.adminKey?.base64EncodedString() ?? ""
|
||||
self.isManaged = node?.securityConfig?.isManaged ?? false
|
||||
self.serialEnabled = node?.securityConfig?.serialEnabled ?? false
|
||||
self.debugLogApiEnabled = node?.securityConfig?.debugLogApiEnabled ?? false
|
||||
self.bluetoothLoggingEnabled = node?.securityConfig?.bluetoothLoggingEnabled ?? false
|
||||
self.adminChannelEnabled = node?.securityConfig?.adminChannelEnabled ?? false
|
||||
self.hasChanges = false
|
||||
}
|
||||
}
|
||||
|
|
@ -13,7 +13,7 @@ struct Firmware: View {
|
|||
@Environment(\.managedObjectContext) var context
|
||||
@EnvironmentObject var bleManager: BLEManager
|
||||
var node: NodeInfoEntity?
|
||||
@State var minimumVersion = "2.4.0"
|
||||
@State var minimumVersion = "2.4.2"
|
||||
@State var version = ""
|
||||
@State private var currentDevice: DeviceHardware?
|
||||
@State private var latestStable: FirmwareRelease?
|
||||
|
|
|
|||
|
|
@ -50,6 +50,18 @@ struct SaveChannelQRCode: View {
|
|||
.controlSize(.large)
|
||||
.padding()
|
||||
.disabled(!connectedToDevice)
|
||||
#if targetEnvironment(macCatalyst)
|
||||
Button {
|
||||
dismiss()
|
||||
} label: {
|
||||
Label("cancel", systemImage: "xmark")
|
||||
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
.buttonBorderShape(.capsule)
|
||||
.controlSize(.large)
|
||||
.padding()
|
||||
#endif
|
||||
} else {
|
||||
Button {
|
||||
dismiss()
|
||||
|
|
@ -62,19 +74,6 @@ struct SaveChannelQRCode: View {
|
|||
.controlSize(.large)
|
||||
.padding()
|
||||
}
|
||||
|
||||
#if targetEnvironment(macCatalyst)
|
||||
Button {
|
||||
dismiss()
|
||||
} label: {
|
||||
Label("cancel", systemImage: "xmark")
|
||||
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
.buttonBorderShape(.capsule)
|
||||
.controlSize(.large)
|
||||
.padding()
|
||||
#endif
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ struct Settings: View {
|
|||
@FetchRequest(
|
||||
sortDescriptors: [
|
||||
NSSortDescriptor(key: "favorite", ascending: false),
|
||||
NSSortDescriptor(key: "user.pkiEncrypted", ascending: false),
|
||||
NSSortDescriptor(key: "viaMqtt", ascending: true),
|
||||
NSSortDescriptor(key: "user.longName", ascending: true)
|
||||
],
|
||||
animation: .default
|
||||
|
|
@ -73,6 +75,14 @@ struct Settings: View {
|
|||
}
|
||||
.disabled(selectedNode > 0 && selectedNode != preferredNodeNum)
|
||||
|
||||
NavigationLink(value: SettingsNavigationState.security) {
|
||||
Label {
|
||||
Text("Security")
|
||||
} icon: {
|
||||
Image(systemName: "lock.shield")
|
||||
}
|
||||
}
|
||||
|
||||
NavigationLink(value: SettingsNavigationState.shareQRCode) {
|
||||
Label {
|
||||
Text("share.channels")
|
||||
|
|
@ -335,18 +345,16 @@ struct Settings: View {
|
|||
}
|
||||
}
|
||||
|
||||
let hasAdmin = node?.myInfo?.adminIndex ?? 0 > 0
|
||||
|
||||
if !(node?.deviceConfig?.isManaged ?? false) {
|
||||
if bleManager.connectedPeripheral != nil {
|
||||
Section("Configure") {
|
||||
if hasAdmin {
|
||||
if node?.canRemoteAdmin ?? false {
|
||||
Picker("Configuring Node", selection: $selectedNode) {
|
||||
if selectedNode == 0 {
|
||||
Text("Connect to a Node").tag(0)
|
||||
}
|
||||
|
||||
ForEach(nodes) { node in
|
||||
/// Connected Node
|
||||
if node.num == bleManager.connectedPeripheral?.num ?? 0 {
|
||||
Label {
|
||||
Text("BLE: \(node.user?.longName ?? "unknown".localized)")
|
||||
|
|
@ -354,16 +362,30 @@ struct Settings: View {
|
|||
Image(systemName: "antenna.radiowaves.left.and.right")
|
||||
}
|
||||
.tag(Int(node.num))
|
||||
} else if node.metadata != nil {
|
||||
} else if node.canRemoteAdmin && UserDefaults.enableAdministration && node.sessionPasskey != nil { /// Nodes using the new PKI system
|
||||
Label {
|
||||
Text("Remote: \(node.user?.longName ?? "unknown".localized)")
|
||||
Text("Remote PKI Admin: \(node.user?.longName ?? "unknown".localized)")
|
||||
} icon: {
|
||||
Image(systemName: "av.remote")
|
||||
}
|
||||
.tag(Int(node.num))
|
||||
} else if hasAdmin {
|
||||
} else if !UserDefaults.enableAdministration && node.metadata != nil { /// Nodes using the old admin system
|
||||
Label {
|
||||
Text("Request Admin: \(node.user?.longName ?? "unknown".localized)")
|
||||
Text("Remote Legacy Admin: \(node.user?.longName ?? "unknown".localized)")
|
||||
} icon: {
|
||||
Image(systemName: "av.remote")
|
||||
}
|
||||
.tag(Int(node.num))
|
||||
} else if UserDefaults.enableAdministration && node.user?.pkiEncrypted ?? false {
|
||||
Label {
|
||||
Text("Request PKI Admin: \(node.user?.longName ?? "unknown".localized)")
|
||||
} icon: {
|
||||
Image(systemName: "rectangle.and.hand.point.up.left")
|
||||
}
|
||||
.tag(Int(node.num))
|
||||
} else if !UserDefaults.enableAdministration {
|
||||
Label {
|
||||
Text("Request Legacy Admin: \(node.user?.longName ?? "unknown".localized)")
|
||||
} icon: {
|
||||
Image(systemName: "rectangle.and.hand.point.up.left")
|
||||
}
|
||||
|
|
@ -378,7 +400,7 @@ struct Settings: View {
|
|||
let node = nodes.first(where: { $0.num == newValue })
|
||||
let connectedNode = nodes.first(where: { $0.num == preferredNodeNum })
|
||||
preferredNodeNum = Int(connectedNode?.num ?? 0)// Int(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral?.num ?? 0 : 0)
|
||||
if connectedNode != nil && connectedNode?.user != nil && connectedNode?.myInfo != nil && node?.user != nil && node?.metadata == nil {
|
||||
if connectedNode != nil && connectedNode?.user != nil && connectedNode?.myInfo != nil && node?.user != nil {// && node?.metadata == nil {
|
||||
let adminMessageId = bleManager.requestDeviceMetadata(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode!.myInfo!.adminIndex, context: context)
|
||||
if adminMessageId > 0 {
|
||||
Logger.mesh.info("Sent node metadata request from node details")
|
||||
|
|
@ -461,6 +483,8 @@ struct Settings: View {
|
|||
PaxCounterConfig(node: nodes.first(where: { $0.num == selectedNode }))
|
||||
case .ringtone:
|
||||
RtttlConfig(node: nodes.first(where: { $0.num == selectedNode }))
|
||||
case .security:
|
||||
SecurityConfig(node: nodes.first(where: { $0.num == selectedNode }))
|
||||
case .serial:
|
||||
SerialConfig(node: nodes.first(where: { $0.num == selectedNode }))
|
||||
case .storeAndForward:
|
||||
|
|
|
|||
|
|
@ -1,12 +1,5 @@
|
|||
#!/bin/bash
|
||||
|
||||
# simple sanity checking for repo
|
||||
if [ ! -d "./protobufs" ]; then
|
||||
git submodule update --init
|
||||
else
|
||||
git submodule update --remote --merge
|
||||
fi
|
||||
|
||||
# simple sanity checking for executable
|
||||
if [ ! -x "$(which protoc)" ]; then
|
||||
brew install swift-protobuf
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue