Merge pull request #1248 from meshtastic/2.6.4

2.6.4 Working Changes
This commit is contained in:
Garth Vander Houwen 2025-06-11 10:08:50 -07:00 committed by GitHub
commit 882b4ef89b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 1590 additions and 258 deletions

View file

@ -352,6 +352,7 @@
DD007BAD2AA4E91200F5FA12 /* MyInfoEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyInfoEntityExtension.swift; sourceTree = "<group>"; };
DD007BAF2AA5981000F5FA12 /* NodeInfoEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeInfoEntityExtension.swift; sourceTree = "<group>"; };
DD05296F2B77F454008E44CD /* MeshtasticDataModelV 26.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 26.xcdatamodel"; sourceTree = "<group>"; };
DD0836AB2DE7C7CB00A3A973 /* MeshtasticDataModelV 52.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 52.xcdatamodel"; sourceTree = "<group>"; };
DD0BE30C2CB785D8000BA445 /* MeshtasticDataModelV 46.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 46.xcdatamodel"; sourceTree = "<group>"; };
DD0BE30F2CB9FDC4000BA445 /* DetectionSensorEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetectionSensorEnums.swift; sourceTree = "<group>"; };
DD0E20FF2B892E1300F2D100 /* MeshtasticDataModelV 28.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 28.xcdatamodel"; sourceTree = "<group>"; };
@ -1806,7 +1807,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 2.6.3;
MARKETING_VERSION = 2.6.4;
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTS_MACCATALYST = YES;
@ -1839,7 +1840,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 2.6.3;
MARKETING_VERSION = 2.6.4;
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTS_MACCATALYST = YES;
@ -1870,7 +1871,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.6.3;
MARKETING_VERSION = 2.6.4;
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@ -1902,7 +1903,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.6.3;
MARKETING_VERSION = 2.6.4;
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@ -2001,6 +2002,7 @@
DD3CC6BA28E366DF00FA9159 /* Meshtastic.xcdatamodeld */ = {
isa = XCVersionGroup;
children = (
DD0836AB2DE7C7CB00A3A973 /* MeshtasticDataModelV 52.xcdatamodel */,
DD63CB4E2DD4FBEA00AFCAE2 /* MeshtasticDataModelV 51.xcdatamodel */,
233E99B32D84969500CC3A77 /* MeshtasticDataModelV 50.xcdatamodel */,
8D3F8A3D2D44B137009EAAA4 /* MeshtasticDataModelV 49.xcdatamodel */,
@ -2053,7 +2055,7 @@
DD5D0A9A2931AD6B00F7EA61 /* MeshtasticDataModelV2.xcdatamodel */,
DD3CC6BB28E366DF00FA9159 /* MeshtasticDataModel.xcdatamodel */,
);
currentVersion = DD63CB4E2DD4FBEA00AFCAE2 /* MeshtasticDataModelV 51.xcdatamodel */;
currentVersion = DD0836AB2DE7C7CB00A3A973 /* MeshtasticDataModelV 52.xcdatamodel */;
name = Meshtastic.xcdatamodeld;
path = Meshtastic/Meshtastic.xcdatamodeld;
sourceTree = "<group>";

View file

@ -32,6 +32,7 @@ extension ChannelEntity {
channel.settings.psk = self.psk ?? Data()
channel.role = Channel.Role(rawValue: Int(self.role)) ?? Channel.Role.secondary
channel.settings.moduleSettings.positionPrecision = UInt32(self.positionPrecision)
channel.settings.moduleSettings.isClientMuted = self.mute
return channel
}
}

View file

@ -659,7 +659,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
manager.notifications = [
Notification(
id: UUID().uuidString,
title: "Firmware Notification",
title: "Firmware Notification".localized,
subtitle: "\(decodedInfo.clientNotification.level)".capitalized,
content: decodedInfo.clientNotification.message,
target: "settings",
@ -667,7 +667,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
)
]
manager.schedule()
Logger.data.error("⚠️ Client Notification \((try? decodedInfo.clientNotification.jsonString()) ?? "JSON Decode Failure")")
Logger.data.error("⚠️ Client Notification: \(decodedInfo.clientNotification.message, privacy: .public)")
}
switch decodedInfo.packet.decoded.portnum {
@ -978,6 +978,8 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
Logger.mesh.info("🕸️ MESH PACKET received for Power Stress App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure", privacy: .public)")
case .reticulumTunnelApp:
Logger.mesh.info("🕸️ MESH PACKET received for Reticulum Tunnel App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure", privacy: .public)")
case .keyVerificationApp:
Logger.mesh.warning("🕸️ MESH PACKET received for Key Verification App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure", privacy: .public)")
}
if decodedInfo.configCompleteID != 0 && decodedInfo.configCompleteID == configNonce {

View file

@ -3,6 +3,6 @@
<plist version="1.0">
<dict>
<key>_XCCurrentVersionName</key>
<string>MeshtasticDataModelV 51.xcdatamodel</string>
<string>MeshtasticDataModelV 52.xcdatamodel</string>
</dict>
</plist>

View file

@ -0,0 +1,506 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="23788" systemVersion="24D81" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<entity name="AmbientLightingConfigEntity" representedClassName="AmbientLightingConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="blue" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="current" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="green" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="ledState" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="red" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<relationship name="ambientLightingConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="ambientLightingConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="BluetoothConfigEntity" representedClassName="BluetoothConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="deviceLoggingEnabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="fixedPin" optional="YES" attributeType="Integer 32" defaultValueString="123456" usesScalarValueType="YES"/>
<attribute name="mode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<relationship name="bluetoothConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="bluetoothConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="CannedMessageConfigEntity" representedClassName="CannedMessageConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="inputbrokerEventCcw" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="inputbrokerEventCw" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="inputbrokerEventPress" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="inputbrokerPinA" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="inputbrokerPinB" optional="YES" attributeType="Integer 32" usesScalarValueType="YES"/>
<attribute name="inputbrokerPinPress" optional="YES" attributeType="Integer 32" usesScalarValueType="YES"/>
<attribute name="messages" optional="YES" attributeType="String" minValueString="0" maxValueString="198"/>
<attribute name="rotary1Enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="sendBell" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="updown1Enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<relationship name="cannedMessagesConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="cannedMessageConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="ChannelEntity" representedClassName="ChannelEntity" syncable="YES" codeGenerationType="class">
<attribute name="downlinkEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="id" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="index" attributeType="Integer 32" minValueString="0" maxValueString="13" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="mute" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="name" optional="YES" attributeType="String"/>
<attribute name="positionPrecision" optional="YES" attributeType="Integer 32" defaultValueString="32" usesScalarValueType="YES"/>
<attribute name="psk" optional="YES" attributeType="Binary"/>
<attribute name="role" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="uplinkEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<relationship name="myInfoChannel" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MyInfoEntity" inverseName="channels" inverseEntity="MyInfoEntity"/>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="index"/>
</uniquenessConstraint>
</uniquenessConstraints>
</entity>
<entity name="DetectionSensorConfigEntity" representedClassName="DetectionSensorConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="enabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="minimumBroadcastSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="monitorPin" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="name" optional="YES" attributeType="String"/>
<attribute name="sendBell" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="stateBroadcastSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="triggerType" optional="YES" attributeType="Integer 32" usesScalarValueType="YES"/>
<attribute name="usePullup" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<relationship name="detectionSensorConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="detectionSensorConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="DeviceConfigEntity" representedClassName="DeviceConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="buttonGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="buzzerGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="debugLogEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="disableTripleClick" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="doubleTapAsButtonPress" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="isManaged" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="ledHeartbeatEnabled" optional="YES" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
<attribute name="nodeInfoBroadcastSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="rebroadcastMode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="role" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="serialEnabled" optional="YES" attributeType="Boolean" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="tripleClickAsAdHocPing" optional="YES" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
<attribute name="tzdef" optional="YES" attributeType="String"/>
<relationship name="deviceConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="deviceConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="DeviceMetadataEntity" representedClassName="DeviceMetadataEntity" syncable="YES" codeGenerationType="class">
<attribute name="canShutdown" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="deviceStateVersion" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="excludedModules" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="firmwareVersion" optional="YES" attributeType="String"/>
<attribute name="hasBluetooth" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="hasEthernet" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="hasWifi" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="hwModel" optional="YES" attributeType="String"/>
<attribute name="positionFlags" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="role" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="time" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<relationship name="metadataNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="metadata" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="DisplayConfigEntity" representedClassName="DisplayConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="compassNorthTop" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="displayMode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="flipScreen" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="gpsFormat" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="headingBold" optional="YES" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
<attribute name="oledType" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="screenCarouselInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="screenOnSeconds" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="units" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="wakeOnTapOrMotion" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<relationship name="displayConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="displayConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="ExternalNotificationConfigEntity" representedClassName="ExternalNotificationConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="active" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="alertBell" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="alertBellBuzzer" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="alertBellVibra" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="alertMessage" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="alertMessageBuzzer" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="alertMessageVibra" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="nagTimeout" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="output" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="outputBuzzer" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="outputMilliseconds" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="outputVibra" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="useI2SAsBuzzer" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="usePWM" optional="YES" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
<relationship name="externalNotificationConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="externalNotificationConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="LocationEntity" representedClassName="LocationEntity" syncable="YES" codeGenerationType="class">
<attribute name="altitude" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="heading" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="id" optional="YES" attributeType="Integer 32" usesScalarValueType="YES"/>
<attribute name="latitudeI" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="longitudeI" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="speed" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<relationship name="routeLocation" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="RouteEntity" inverseName="locations" inverseEntity="RouteEntity"/>
</entity>
<entity name="LoRaConfigEntity" representedClassName="LoRaConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="bandwidth" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="channelNum" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="codingRate" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="frequencyOffset" optional="YES" attributeType="Float" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="hopLimit" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="ignoreMqtt" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="modemPreset" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="okToMqtt" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="overrideDutyCycle" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="overrideFrequency" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="regionCode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="spreadFactor" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="sx126xRxBoostedGain" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="txEnabled" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
<attribute name="txPower" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="usePreset" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
<relationship name="loRaConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="loRaConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="MessageEntity" representedClassName="MessageEntity" syncable="YES" codeGenerationType="class">
<attribute name="ackError" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="ackSNR" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="ackTimestamp" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="admin" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="adminDescription" optional="YES" attributeType="String"/>
<attribute name="channel" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="isEmoji" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="messageId" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<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"/>
<attribute name="replyID" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="rssi" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<relationship name="fromUser" optional="YES" maxCount="1" deletionRule="Nullify" ordered="YES" destinationEntity="UserEntity" inverseName="sentMessages" inverseEntity="UserEntity"/>
<relationship name="toUser" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="UserEntity" inverseName="receivedMessages" inverseEntity="UserEntity"/>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="messageId"/>
</uniquenessConstraint>
</uniquenessConstraints>
</entity>
<entity name="MQTTConfigEntity" representedClassName="MQTTConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="address" optional="YES" attributeType="String"/>
<attribute name="enabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="encryptionEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="jsonEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="mapPositionPrecision" optional="YES" attributeType="Integer 32" defaultValueString="13" usesScalarValueType="YES"/>
<attribute name="mapPublishIntervalSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="mapReportingEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="mapReportingShouldReportLocation" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="password" optional="YES" attributeType="String" maxValueString="30"/>
<attribute name="proxyToClientEnabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="root" optional="YES" attributeType="String" defaultValueString="msh"/>
<attribute name="tlsEnabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="username" optional="YES" attributeType="String" maxValueString="30"/>
<relationship name="mqttConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="mqttConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="MyInfoEntity" representedClassName="MyInfoEntity" syncable="YES" codeGenerationType="class">
<attribute name="adminIndex" optional="YES" attributeType="Integer 32" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="bleName" optional="YES" attributeType="String"/>
<attribute name="deviceId" optional="YES" attributeType="Binary"/>
<attribute name="minAppVersion" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="myNodeNum" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="peripheralId" optional="YES" attributeType="String"/>
<attribute name="rebootCount" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="registered" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<relationship name="channels" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="ChannelEntity" inverseName="myInfoChannel" inverseEntity="ChannelEntity"/>
<relationship name="myInfoNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="myInfo" inverseEntity="NodeInfoEntity"/>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="myNodeNum"/>
</uniquenessConstraint>
</uniquenessConstraints>
</entity>
<entity name="NetworkConfigEntity" representedClassName="NetworkConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="dns" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="enabledProtocols" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="ethEnabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="gateway" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="ip" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="ntpServer" optional="YES" attributeType="String"/>
<attribute name="subnet" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="wifiEnabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="wifiMode" optional="YES" attributeType="Integer 32" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="wifiPsk" optional="YES" attributeType="String" minValueString="0" maxValueString="60"/>
<attribute name="wifiSsid" optional="YES" attributeType="String" minValueString="0" maxValueString="30"/>
<relationship name="networkConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="networkConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="NodeInfoEntity" representedClassName="NodeInfoEntity" syncable="YES" codeGenerationType="class">
<attribute name="bleName" optional="YES" attributeType="String"/>
<attribute name="channel" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="favorite" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="firstHeard" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="hopsAway" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="id" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="ignored" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="lastHeard" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<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"/>
<relationship name="bluetoothConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="BluetoothConfigEntity" inverseName="bluetoothConfigNode" inverseEntity="BluetoothConfigEntity"/>
<relationship name="cannedMessageConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="CannedMessageConfigEntity" inverseName="cannedMessagesConfigNode" inverseEntity="CannedMessageConfigEntity"/>
<relationship name="detectionSensorConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="DetectionSensorConfigEntity" inverseName="detectionSensorConfigNode" inverseEntity="DetectionSensorConfigEntity"/>
<relationship name="deviceConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="DeviceConfigEntity" inverseName="deviceConfigNode" inverseEntity="DeviceConfigEntity"/>
<relationship name="displayConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="DisplayConfigEntity" inverseName="displayConfigNode" inverseEntity="DisplayConfigEntity"/>
<relationship name="externalNotificationConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="ExternalNotificationConfigEntity" inverseName="externalNotificationConfigNode" inverseEntity="ExternalNotificationConfigEntity"/>
<relationship name="loRaConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="LoRaConfigEntity" inverseName="loRaConfigNode" inverseEntity="LoRaConfigEntity"/>
<relationship name="metadata" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="DeviceMetadataEntity" inverseName="metadataNode" inverseEntity="DeviceMetadataEntity"/>
<relationship name="mqttConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MQTTConfigEntity" inverseName="mqttConfigNode" inverseEntity="MQTTConfigEntity"/>
<relationship name="myInfo" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MyInfoEntity" inverseName="myInfoNode" inverseEntity="MyInfoEntity"/>
<relationship name="networkConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NetworkConfigEntity" inverseName="networkConfigNode" inverseEntity="NetworkConfigEntity"/>
<relationship name="pax" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="PaxCounterEntity" inverseName="paxNode" inverseEntity="PaxCounterEntity"/>
<relationship name="paxCounterConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="PaxCounterConfigEntity" inverseName="paxCounterConfigNode" inverseEntity="PaxCounterConfigEntity"/>
<relationship name="positionConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="PositionConfigEntity" inverseName="positionConfigNode" inverseEntity="PositionConfigEntity"/>
<relationship name="positions" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="PositionEntity" inverseName="nodePosition" inverseEntity="PositionEntity"/>
<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"/>
<relationship name="telemetryConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="TelemetryConfigEntity" inverseName="telemetryConfigNode" inverseEntity="TelemetryConfigEntity"/>
<relationship name="traceRoutes" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="TraceRouteEntity" inverseName="node" inverseEntity="TraceRouteEntity"/>
<relationship name="user" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="UserEntity" inverseName="userNode" inverseEntity="UserEntity"/>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="num"/>
</uniquenessConstraint>
</uniquenessConstraints>
</entity>
<entity name="PaxCounterConfigEntity" representedClassName="PaxCounterConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="bleThreshold" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="enabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="updateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="wifiThreshold" optional="YES" attributeType="Integer 32" defaultValueString="-80" usesScalarValueType="YES"/>
<relationship name="paxCounterConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="paxCounterConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="PaxCounterEntity" representedClassName="PaxCounterEntity" syncable="YES" codeGenerationType="class">
<attribute name="ble" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="time" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="uptime" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="wifi" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<relationship name="paxNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="pax" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="PositionConfigEntity" representedClassName="PositionConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="broadcastSmartMinimumDistance" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="broadcastSmartMinimumIntervalSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="deviceGpsEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="fixedPosition" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="gpsAttemptTime" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="gpsEnGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="gpsMode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="gpsUpdateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="positionBroadcastSeconds" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="positionFlags" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="rxGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="smartPositionEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="txGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<relationship name="positionConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="positionConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="PositionEntity" representedClassName="PositionEntity" syncable="YES" codeGenerationType="class">
<attribute name="altitude" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="heading" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="latest" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="latitudeI" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="longitudeI" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="precisionBits" optional="YES" attributeType="Integer 32" defaultValueString="32" usesScalarValueType="YES"/>
<attribute name="rssi" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="satsInView" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="seqNo" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="speed" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="time" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<relationship name="nodePosition" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="positions" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="PowerConfigEntity" representedClassName="PowerConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="adcMultiplierOverride" optional="YES" attributeType="Float" usesScalarValueType="YES"/>
<attribute name="deviceBatteryInaAddress" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="isPowerSaving" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="lsSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="minWakeSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="onBatteryShutdownAfterSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="waitBluetoothSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<relationship name="powerConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="powerConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="RangeTestConfigEntity" representedClassName="RangeTestConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="save" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="sender" optional="YES" attributeType="Integer 32" usesScalarValueType="YES"/>
<relationship name="rangeTestConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="rangeTestConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="RouteEntity" representedClassName="RouteEntity" syncable="YES" codeGenerationType="class">
<attribute name="color" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="date" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="distance" optional="YES" attributeType="Double" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="elevationGain" optional="YES" attributeType="Double" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="enabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="endDate" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="id" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="name" optional="YES" attributeType="String"/>
<attribute name="notes" optional="YES" attributeType="String"/>
<relationship name="locations" optional="YES" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="LocationEntity" inverseName="routeLocation" inverseEntity="LocationEntity"/>
</entity>
<entity name="RTTTLConfigEntity" representedClassName="RTTTLConfigEntity" syncable="YES" codeGenerationType="class">
<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="adminKey2" optional="YES" attributeType="Binary"/>
<attribute name="adminKey3" 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"/>
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="mode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="overrideConsoleSerialPort" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="rxd" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="timeout" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="txd" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<relationship name="serialConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="serialConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="StoreForwardConfigEntity" representedClassName="StoreForwardConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="enabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="heartbeat" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
<attribute name="historyReturnMax" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="historyReturnWindow" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="isRouter" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="lastHeartbeat" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="lastRequest" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="records" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<relationship name="storeForwardConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="storeForwardConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="TelemetryConfigEntity" representedClassName="TelemetryConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="deviceUpdateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="environmentDisplayFahrenheit" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="environmentMeasurementEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="environmentScreenEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="environmentUpdateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="powerMeasurementEnabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="powerScreenEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="powerUpdateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<relationship name="telemetryConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="telemetryConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="TelemetryEntity" representedClassName="TelemetryEntity" syncable="YES">
<attribute name="airUtilTx" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="barometricPressure" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="batteryLevel" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="channelUtilization" optional="YES" attributeType="Float" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="current" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="gasResistance" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="iaq" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="irLux" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="lux" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="metricsType" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="numOnlineNodes" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="numPacketsRx" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="numPacketsRxBad" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="numPacketsTx" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="numRxDupe" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="numTotalNodes" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="numTxRelay" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="numTxRelayCanceled" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="powerCh1Current" optional="YES" attributeType="Float" usesScalarValueType="YES"/>
<attribute name="powerCh1Voltage" optional="YES" attributeType="Float" usesScalarValueType="YES"/>
<attribute name="powerCh2Current" optional="YES" attributeType="Float" usesScalarValueType="YES"/>
<attribute name="powerCh2Voltage" optional="YES" attributeType="Float" usesScalarValueType="YES"/>
<attribute name="powerCh3Current" optional="YES" attributeType="Float" usesScalarValueType="YES"/>
<attribute name="powerCh3Voltage" optional="YES" attributeType="Float" usesScalarValueType="YES"/>
<attribute name="radiation" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="rainfall1H" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="rainfall24H" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="relativeHumidity" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="rssi" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="soilMoisture" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="soilTemperature" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="temperature" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="time" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="uptimeSeconds" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="uvLux" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="voltage" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="weight" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="whiteLux" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="windDirection" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="windGust" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="windLull" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="windSpeed" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<relationship name="nodeTelemetry" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="telemetries" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="TraceRouteEntity" representedClassName="TraceRouteEntity" syncable="YES" codeGenerationType="class">
<attribute name="hasPositions" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="hopsBack" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="hopsTowards" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="id" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="response" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="routeBackText" optional="YES" attributeType="String"/>
<attribute name="routeText" optional="YES" attributeType="String"/>
<attribute name="sent" optional="YES" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="time" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<relationship name="hops" optional="YES" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="TraceRouteHopEntity" inverseName="traceRoute" inverseEntity="TraceRouteHopEntity"/>
<relationship name="node" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="traceRoutes" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="TraceRouteHopEntity" representedClassName="TraceRouteHopEntity" syncable="YES" codeGenerationType="class">
<attribute name="altitude" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="back" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="latitudeI" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="longitudeI" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="name" optional="YES" attributeType="String"/>
<attribute name="num" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="time" attributeType="Date" usesScalarValueType="NO"/>
<relationship name="traceRoute" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="TraceRouteEntity" inverseName="hops" inverseEntity="TraceRouteEntity"/>
</entity>
<entity name="UserEntity" representedClassName="UserEntity" syncable="YES" codeGenerationType="class">
<attribute name="hwDisplayName" optional="YES" attributeType="String"/>
<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="unmessagable" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="userId" attributeType="String"/>
<relationship name="receivedMessages" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="MessageEntity" inverseName="toUser" inverseEntity="MessageEntity"/>
<relationship name="sentMessages" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="MessageEntity" inverseName="fromUser" inverseEntity="MessageEntity"/>
<relationship name="userNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="user" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="WaypointEntity" representedClassName="WaypointEntity" syncable="YES" codeGenerationType="class">
<attribute name="created" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="expire" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="icon" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="id" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="lastUpdated" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="latitudeI" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="locked" attributeType="Integer 64" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="longDescription" optional="YES" attributeType="String" maxValueString="100"/>
<attribute name="longitudeI" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="name" attributeType="String" minValueString="1" maxValueString="30"/>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="id"/>
</uniquenessConstraint>
</uniquenessConstraints>
</entity>
</model>

View file

@ -283,17 +283,16 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext)
fetchedNode[0].telemetries? = NSOrderedSet(array: newTelemetries)
}
if nodeInfoMessage.hasUser {
/// Seeing Some crashes here ?
fetchedNode[0].user!.userId = nodeInfoMessage.user.id
fetchedNode[0].user!.num = Int64(nodeInfoMessage.num)
fetchedNode[0].user!.longName = nodeInfoMessage.user.longName
fetchedNode[0].user!.shortName = nodeInfoMessage.user.shortName
fetchedNode[0].user!.role = Int32(nodeInfoMessage.user.role.rawValue)
fetchedNode[0].user!.hwModel = String(describing: nodeInfoMessage.user.hwModel).uppercased()
fetchedNode[0].user!.hwModelId = Int32(nodeInfoMessage.user.hwModel.rawValue)
fetchedNode[0].user?.userId = nodeInfoMessage.user.id
fetchedNode[0].user?.num = Int64(nodeInfoMessage.num)
fetchedNode[0].user?.longName = nodeInfoMessage.user.longName
fetchedNode[0].user?.shortName = nodeInfoMessage.user.shortName
fetchedNode[0].user?.role = Int32(nodeInfoMessage.user.role.rawValue)
fetchedNode[0].user?.hwModel = String(describing: nodeInfoMessage.user.hwModel).uppercased()
fetchedNode[0].user?.hwModelId = Int32(nodeInfoMessage.user.hwModel.rawValue)
/// For nodes that have the optional isUnmessagable boolean use that, otherwise excluded roles that are unmessagable by default
if nodeInfoMessage.user.hasIsUnmessagable {
fetchedNode[0].user!.unmessagable = nodeInfoMessage.user.isUnmessagable
fetchedNode[0].user?.unmessagable = nodeInfoMessage.user.isUnmessagable
} else {
let roles = [-1, 2, 4, 5, 6, 7, 10, 11]
let containsRole = roles.contains(Int(fetchedNode[0].user?.role ?? -1))
@ -304,13 +303,13 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext)
}
}
if !nodeInfoMessage.user.publicKey.isEmpty {
fetchedNode[0].user!.pkiEncrypted = true
fetchedNode[0].user!.publicKey = nodeInfoMessage.user.publicKey
fetchedNode[0].user?.pkiEncrypted = true
fetchedNode[0].user?.publicKey = nodeInfoMessage.user.publicKey
}
Task {
Api().loadDeviceHardwareData { (hw) in
let dh = hw.first(where: { $0.hwModel == fetchedNode[0].user?.hwModelId ?? 0 })
fetchedNode[0].user!.hwDisplayName = dh?.displayName
fetchedNode[0].user?.hwDisplayName = dh?.displayName
}
}
}
@ -384,7 +383,7 @@ func upsertPositionPacket (packet: MeshPacket, context: NSManagedObjectContext)
} else {
position.time = Date(timeIntervalSince1970: TimeInterval(Int64(positionMessage.time)))
}
guard let mutablePositions = fetchedNode[0].positions!.mutableCopy() as? NSMutableOrderedSet else {
guard let mutablePositions = fetchedNode[0].positions?.mutableCopy() as? NSMutableOrderedSet else {
return
}
/// Don't save nearly the same position over and over. If the next position is less than 10 meters from the new position, delete the previous position and save the new one.
@ -1231,6 +1230,7 @@ func upsertMqttModuleConfigPacket(config: ModuleConfig.MQTTConfig, nodeNum: Int6
newMQTTConfig.jsonEnabled = config.jsonEnabled
newMQTTConfig.tlsEnabled = config.tlsEnabled
newMQTTConfig.mapReportingEnabled = config.mapReportingEnabled
newMQTTConfig.mapReportingShouldReportLocation = config.mapReportSettings.shouldReportLocation
newMQTTConfig.mapPositionPrecision = Int32(config.mapReportSettings.positionPrecision)
newMQTTConfig.mapPublishIntervalSecs = Int32(config.mapReportSettings.publishIntervalSecs)
fetchedNode[0].mqttConfig = newMQTTConfig

View file

@ -11,27 +11,22 @@ struct MeshtasticLogo: View {
@Environment(\.colorScheme) var colorScheme
var body: some View {
#if targetEnvironment(macCatalyst)
VStack {
Image("logo-white")
.resizable()
.renderingMode(.template)
.foregroundColor(.accentColor)
.scaledToFit()
}
.padding(.bottom, 5)
.padding(.top, 5)
.offset(x: -15)
#else
VStack {
Image(colorScheme == .dark ? "logo-white" : "logo-black")
.resizable()
.renderingMode(.template)
.scaledToFit()
}
.padding(.bottom, 5)
.offset(x: -15)
#endif
}
}

View file

@ -26,6 +26,12 @@ struct ChannelList: View {
var restrictedChannels = ["gpio", "mqtt", "serial", "admin"]
@FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \ChannelEntity.index, ascending: true)],
predicate: nil,
animation: .default
) private var channels: FetchedResults<ChannelEntity>
@ViewBuilder
private func makeChannelRow(
myInfo: MyInfoEntity,
@ -87,6 +93,9 @@ struct ChannelList: View {
.foregroundColor(.secondary)
}
}
if channel.mute {
Image(systemName: "bell.slash")
}
}
if channel.allPrivateMessages.count > 0 {
@ -103,7 +112,7 @@ struct ChannelList: View {
var body: some View {
VStack {
// Display Contacts for the rest of the non admin channels
if let node, let myInfo = node.myInfo, let channels = myInfo.channels?.array as? [ChannelEntity] {
if let node, let myInfo = node.myInfo {
List(selection: $channelSelection) {
ForEach(channels) { (channel: ChannelEntity) in
if !restrictedChannels.contains(channel.name?.lowercased() ?? "") {
@ -119,7 +128,7 @@ struct ChannelList: View {
}
}
Button {
channel.mute = !channel.mute
channel.mute.toggle()
do {
let adminMessageId = bleManager.saveChannel(channel: channel.protoBuf, fromUser: node.user!, toUser: node.user!)
if adminMessageId > 0 {

View file

@ -21,7 +21,6 @@ struct UserList: View {
@State private var isPkiEncrypted = false
@State private var isFavorite = false
@State private var isIgnored = false
@State private var isUnmessagable = false
@State private var isEnvironment = false
@State private var distanceFilter = false
@State private var maxDistance: Double = 800000
@ -40,18 +39,6 @@ struct UserList: View {
roleFilter
]}
@FetchRequest(
sortDescriptors: [NSSortDescriptor(key: "lastMessage", ascending: false),
NSSortDescriptor(key: "userNode.favorite", ascending: false),
NSSortDescriptor(key: "pkiEncrypted", ascending: false),
NSSortDescriptor(key: "userNode.lastHeard", ascending: false),
NSSortDescriptor(key: "longName", ascending: true)],
predicate: NSPredicate(
format: "userNode.ignored == NO AND unmessagable = NO"
), animation: .spring
)
var users: FetchedResults<UserEntity>
@Binding var node: NodeInfoEntity?
@Binding var userSelection: UserEntity?
@ -61,196 +48,161 @@ struct UserList: View {
let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMdd", options: 0, locale: Locale.current)
let dateFormatString = (localeDateFormat ?? "MM/dd/YY")
VStack {
List(users, selection: $userSelection) { (user: UserEntity) in
let mostRecent = user.messageList.last
let lastMessageTime = Date(timeIntervalSince1970: TimeInterval(Int64((mostRecent?.messageTimestamp ?? 0 ))))
let lastMessageDay = Calendar.current.dateComponents([.day], from: lastMessageTime).day ?? 0
let currentDay = Calendar.current.dateComponents([.day], from: Date()).day ?? 0
if user.num != bleManager.connectedPeripheral?.num ?? 0 {
NavigationLink(value: user) {
ZStack {
Image(systemName: "circle.fill")
.opacity(user.unreadMessages > 0 ? 1 : 0)
.font(.system(size: 10))
.foregroundColor(.accentColor)
.brightness(0.2)
}
FilteredUserList(
searchText: searchText,
viaLora: viaLora,
viaMqtt: viaMqtt,
isOnline: isOnline,
isPkiEncrypted: isPkiEncrypted,
isFavorite: isFavorite,
isIgnored: isIgnored,
isEnvironment: isEnvironment,
distanceFilter: distanceFilter,
maxDistance: maxDistance,
hopsAway: hopsAway,
roleFilter: roleFilter,
deviceRoles: deviceRoles,
userSelection: $userSelection
) { users in
List(users, selection: $userSelection) { (user: UserEntity) in
let mostRecent = user.messageList.last
let lastMessageTime = Date(timeIntervalSince1970: TimeInterval(Int64((mostRecent?.messageTimestamp ?? 0 ))))
let lastMessageDay = Calendar.current.dateComponents([.day], from: lastMessageTime).day ?? 0
let currentDay = Calendar.current.dateComponents([.day], from: Date()).day ?? 0
if user.num != bleManager.connectedPeripheral?.num ?? 0 {
NavigationLink(value: user) {
ZStack {
Image(systemName: "circle.fill")
.opacity(user.unreadMessages > 0 ? 1 : 0)
.font(.system(size: 10))
.foregroundColor(.accentColor)
.brightness(0.2)
}
CircleText(text: user.shortName ?? "?", color: Color(UIColor(hex: UInt32(user.num))))
CircleText(text: user.shortName ?? "?", color: Color(UIColor(hex: UInt32(user.num))))
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)
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.fill")
.foregroundColor(.green)
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")
.foregroundColor(.yellow)
}
if user.messageList.count > 0 {
if lastMessageDay == currentDay {
Text(lastMessageTime, style: .time )
.font(.footnote)
.foregroundColor(.secondary)
} else if lastMessageDay == (currentDay - 1) {
Text("Yesterday")
.font(.footnote)
.foregroundColor(.secondary)
} else if lastMessageDay < (currentDay - 1) && lastMessageDay > (currentDay - 5) {
Text(lastMessageTime.formattedDate(format: dateFormatString))
.font(.footnote)
.foregroundColor(.secondary)
} else if lastMessageDay < (currentDay - 1800) {
Text(lastMessageTime.formattedDate(format: dateFormatString))
.font(.footnote)
.foregroundColor(.secondary)
}
}
}
if user.messageList.count > 0 {
HStack(alignment: .top) {
Text("\(mostRecent != nil ? mostRecent!.messagePayload! : " ")")
.font(.footnote)
.foregroundColor(.secondary)
}
}
}
}
.frame(height: 62)
.contextMenu {
Button {
if node != nil && !(user.userNode?.favorite ?? false) {
let success = bleManager.setFavoriteNode(node: user.userNode!, connectedNodeNum: Int64(node!.num))
if success {
user.userNode?.favorite = !(user.userNode?.favorite ?? false)
Logger.data.info("Favorited a node")
}
} 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")
.foregroundColor(.yellow)
}
if user.messageList.count > 0 {
if lastMessageDay == currentDay {
Text(lastMessageTime, style: .time )
.font(.footnote)
.foregroundColor(.secondary)
} else if lastMessageDay == (currentDay - 1) {
Text("Yesterday")
.font(.footnote)
.foregroundColor(.secondary)
} else if lastMessageDay < (currentDay - 1) && lastMessageDay > (currentDay - 5) {
Text(lastMessageTime.formattedDate(format: dateFormatString))
.font(.footnote)
.foregroundColor(.secondary)
} else if lastMessageDay < (currentDay - 1800) {
Text(lastMessageTime.formattedDate(format: dateFormatString))
.font(.footnote)
.foregroundColor(.secondary)
let success = bleManager.removeFavoriteNode(node: user.userNode!, connectedNodeNum: Int64(node!.num))
if success {
user.userNode?.favorite = !(user.userNode?.favorite ?? false)
Logger.data.info("Unfavorited a node")
}
}
}
if user.messageList.count > 0 {
HStack(alignment: .top) {
Text("\(mostRecent != nil ? mostRecent!.messagePayload! : " ")")
.font(.footnote)
.foregroundColor(.secondary)
context.refresh(user, mergeChanges: true)
do {
try context.save()
} catch {
context.rollback()
Logger.data.error("Save Node Favorite Error")
}
}
}
}
.frame(height: 62)
.contextMenu {
Button {
if node != nil && !(user.userNode?.favorite ?? false) {
let success = bleManager.setFavoriteNode(node: user.userNode!, connectedNodeNum: Int64(node!.num))
if success {
user.userNode?.favorite = !(user.userNode?.favorite ?? true)
Logger.data.info("Favorited a node")
}
} else {
let success = bleManager.removeFavoriteNode(node: user.userNode!, connectedNodeNum: Int64(node!.num))
if success {
user.userNode?.favorite = !(user.userNode?.favorite ?? true)
Logger.data.info("Un Favorited a node")
}
}
context.refresh(user, mergeChanges: true)
do {
try context.save()
} catch {
context.rollback()
Logger.data.error("Save Node Favorite Error")
}
} label: {
Label((user.userNode?.favorite ?? false) ? "Un-Favorite" : "Favorite", systemImage: (user.userNode?.favorite ?? false) ? "star.slash.fill" : "star.fill")
}
Button {
user.mute = !user.mute
do {
try context.save()
} catch {
context.rollback()
Logger.data.error("Save User Mute Error")
}
} label: {
Label(user.mute ? "Show Alerts" : "Hide Alerts", systemImage: user.mute ? "bell" : "bell.slash")
}
if user.messageList.count > 0 {
Button(role: .destructive) {
isPresentingDeleteUserMessagesConfirm = true
userSelection = user
} label: {
Label("Delete Messages", systemImage: "trash")
Label((user.userNode?.favorite ?? false) ? "Un-Favorite" : "Favorite", systemImage: (user.userNode?.favorite ?? false) ? "star.slash.fill" : "star.fill")
}
Button {
user.mute = !user.mute
do {
try context.save()
} catch {
context.rollback()
Logger.data.error("Save User Mute Error")
}
} label: {
Label(user.mute ? "Show Alerts" : "Hide Alerts", systemImage: user.mute ? "bell" : "bell.slash")
}
if user.messageList.count > 0 {
Button(role: .destructive) {
isPresentingDeleteUserMessagesConfirm = true
userSelection = user
} label: {
Label("Delete Messages", systemImage: "trash")
}
}
}
}
.confirmationDialog(
"This conversation will be deleted.",
isPresented: $isPresentingDeleteUserMessagesConfirm,
titleVisibility: .visible
) {
Button(role: .destructive) {
deleteUserMessages(user: userSelection!, context: context)
context.refresh(node!.user!, mergeChanges: true)
} label: {
Text("Delete")
.confirmationDialog(
"This conversation will be deleted.",
isPresented: $isPresentingDeleteUserMessagesConfirm,
titleVisibility: .visible
) {
Button(role: .destructive) {
deleteUserMessages(user: userSelection!, context: context)
context.refresh(node!.user!, mergeChanges: true)
} label: {
Text("Delete")
}
}
}
}
.listStyle(.plain)
.navigationTitle(String.localizedStringWithFormat("Contacts (%@)", String(users.count)))
}
.listStyle(.plain)
.navigationTitle(String.localizedStringWithFormat("Contacts (%@)".localized, String(users.count == 0 ? 0 : users.count - 1)))
.sheet(isPresented: $editingFilters) {
NodeListFilter(filterTitle: "Contact Filters", viaLora: $viaLora, viaMqtt: $viaMqtt, isOnline: $isOnline, isPkiEncrypted: $isPkiEncrypted, isFavorite: $isFavorite, isIgnored: $isIgnored, isEnvironment: $isEnvironment, distanceFilter: $distanceFilter, maximumDistance: $maxDistance, hopsAway: $hopsAway, roleFilter: $roleFilter, deviceRoles: $deviceRoles)
}
.sheet(isPresented: $showingHelp) {
DirectMessagesHelp()
}
.onChange(of: searchText) {
Task {
await searchUserList()
}
}
.onChange(of: viaLora) {
if !viaLora && !viaMqtt {
viaMqtt = true
}
Task {
await searchUserList()
}
}
.onChange(of: viaMqtt) {
if !viaLora && !viaMqtt {
viaLora = true
}
Task {
await searchUserList()
}
}
.onChange(of: [deviceRoles]) {
Task {
await searchUserList()
}
}
.onChange(of: hopsAway) {
Task {
await searchUserList()
}
}
.onChange(of: [boolFilters]) {
Task {
await searchUserList()
}
}
.onChange(of: maxDistance) {
Task {
await searchUserList()
}
}
.onChange(of: isPkiEncrypted) {
Task {
await searchUserList()
}
}
.onFirstAppear {
Task {
await searchUserList()
}
}
.safeAreaInset(edge: .bottom, alignment: .leading) {
HStack {
Button(action: {
@ -281,23 +233,50 @@ struct UserList: View {
.padding(5)
}
.padding(.bottom, 5)
.padding(.bottom, 5)
.searchable(text: $searchText, placement: users.count > 10 ? .navigationBarDrawer(displayMode: .always) : .automatic, prompt: "Find a contact")
.searchable(text: $searchText, placement: .navigationBarDrawer(displayMode: .always), prompt: "Find a contact")
.disableAutocorrection(true)
.scrollDismissesKeyboard(.immediately)
}
}
private func searchUserList() async {
}
/// Case Insensitive Search Text Predicates
let searchPredicates = ["userId", "numString", "hwModel", "hwDisplayName", "longName", "shortName"].map { property in
return NSPredicate(format: "%K CONTAINS[c] %@", property, searchText)
}
/// Create a compound predicate using each text search preicate as an OR
let textSearchPredicate = NSCompoundPredicate(type: .or, subpredicates: searchPredicates)
/// Create an array of predicates to hold our AND predicates
struct FilteredUserList<Content: View>: View {
@FetchRequest var fetchRequest: FetchedResults<UserEntity>
let content: (FetchedResults<UserEntity>) -> Content
var body: some View {
content(fetchRequest)
}
init(
searchText: String,
viaLora: Bool,
viaMqtt: Bool,
isOnline: Bool,
isPkiEncrypted: Bool,
isFavorite: Bool,
isIgnored: Bool,
isEnvironment: Bool,
distanceFilter: Bool,
maxDistance: Double,
hopsAway: Double,
roleFilter: Bool,
deviceRoles: Set<Int>,
userSelection: Binding<UserEntity?>,
@ViewBuilder content: @escaping (FetchedResults<UserEntity>) -> Content
) {
self.content = content
// Build predicates based on filter variables
var predicates: [NSPredicate] = []
/// Mqtt and lora
// Search text predicates
if !searchText.isEmpty {
let searchPredicates = ["userId", "numString", "hwModel", "hwDisplayName", "longName", "shortName"].map { property in
return NSPredicate(format: "%K CONTAINS[c] %@", property, searchText)
}
let textSearchPredicate = NSCompoundPredicate(type: .or, subpredicates: searchPredicates)
predicates.append(textSearchPredicate)
}
// Mqtt and lora
if !(viaLora && viaMqtt) {
if viaLora {
let loraPredicate = NSPredicate(format: "userNode.viaMqtt == NO")
@ -306,11 +285,8 @@ struct UserList: View {
let mqttPredicate = NSPredicate(format: "userNode.viaMqtt == YES")
predicates.append(mqttPredicate)
}
} else {
let mqttPredicate = NSPredicate(format: "NOT (userNode.viaMqtt == YES)")
predicates.append(mqttPredicate)
}
/// Roles
// Roles
if roleFilter && deviceRoles.count > 0 {
var rolesArray: [NSPredicate] = []
for dr in deviceRoles {
@ -320,40 +296,32 @@ struct UserList: View {
let compoundPredicate = NSCompoundPredicate(type: .or, subpredicates: rolesArray)
predicates.append(compoundPredicate)
}
/// Hops Away
if hopsAway == 0.0 {
// Hops Away
if hopsAway == 0 {
let hopsAwayPredicate = NSPredicate(format: "userNode.hopsAway == %i", Int32(hopsAway))
predicates.append(hopsAwayPredicate)
} else if hopsAway > -1.0 {
let hopsAwayPredicate = NSPredicate(format: "userNode.hopsAway > 0 AND userNode.hopsAway <= %i", Int32(hopsAway))
predicates.append(hopsAwayPredicate)
}
/// Online
// Online
if isOnline {
let isOnlinePredicate = NSPredicate(format: "userNode.lastHeard >= %@", Calendar.current.date(byAdding: .minute, value: -120, to: Date())! as NSDate)
predicates.append(isOnlinePredicate)
}
/// Encrypted
// Encrypted
if isPkiEncrypted {
let isPkiEncryptedPredicate = NSPredicate(format: "pkiEncrypted == YES")
predicates.append(isPkiEncryptedPredicate)
}
/// Favorites
// Favorites
if isFavorite {
let isFavoritePredicate = NSPredicate(format: "userNode.favorite == YES")
predicates.append(isFavoritePredicate)
}
/// Ignored
let isIgnoredPredicate = NSPredicate(format: "userNode.ignored == NO")
predicates.append(isIgnoredPredicate)
/// Unmessagable
let isUnmessagablePredicate = NSPredicate(format: "unmessagable == NO")
predicates.append(isUnmessagablePredicate)
/// Distance
// Distance
if distanceFilter {
let pointOfInterest = LocationsHandler.currentLocation
if pointOfInterest.latitude != LocationsHandler.DefaultLocation.latitude && pointOfInterest.longitude != LocationsHandler.DefaultLocation.longitude {
let d: Double = maxDistance * 1.1
let r: Double = 6371009
@ -368,11 +336,26 @@ struct UserList: View {
predicates.append(distancePredicate)
}
}
if !searchText.isEmpty {
let filterPredicates = NSCompoundPredicate(type: .and, subpredicates: predicates)
users.nsPredicate = NSCompoundPredicate(type: .and, subpredicates: [textSearchPredicate, filterPredicates])
} else {
users.nsPredicate = NSCompoundPredicate(type: .and, subpredicates: predicates)
}
// Always apply unmessagable and connected node filters
let isUnmessagablePredicate = NSPredicate(format: "unmessagable == NO")
predicates.append(isUnmessagablePredicate)
let isIgnoredPredicate = NSPredicate(format: "userNode.ignored == NO")
predicates.append(isIgnoredPredicate)
let isConnectedNodePredicate = NSPredicate(format: "NOT (numString CONTAINS %@)", String(UserDefaults.preferredPeripheralNum))
predicates.append(isConnectedNodePredicate)
// Combine all predicates
let finalPredicate = predicates.isEmpty ? NSPredicate(value: true) : NSCompoundPredicate(type: .and, subpredicates: predicates)
// Initialize the fetch request with the combined predicate
_fetchRequest = FetchRequest<UserEntity>(
sortDescriptors: [
NSSortDescriptor(key: "lastMessage", ascending: false),
NSSortDescriptor(key: "userNode.favorite", ascending: false),
NSSortDescriptor(key: "pkiEncrypted", ascending: false),
NSSortDescriptor(key: "userNode.lastHeard", ascending: false),
NSSortDescriptor(key: "longName", ascending: true)
],
predicate: finalPredicate,
animation: .spring
)
}
}

View file

@ -486,7 +486,7 @@ struct NodeDetail: View {
let connectedNode,
self.bleManager.connectedPeripheral != nil {
Section("Administration") {
if connectedNode.myInfo?.hasAdmin ?? false {
if UserDefaults.enableAdministration {
Button {
let adminMessageId = bleManager.requestDeviceMetadata(
fromUser: connectedNode.user!,

View file

@ -91,20 +91,21 @@ struct NodeListFilter: View {
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
.listRowSeparator(.visible)
Toggle(isOn: $isIgnored) {
Label {
Text("Ignored")
} icon: {
Image(systemName: "minus.circle.fill")
.symbolRenderingMode(.multicolor)
}
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
.listRowSeparator(.visible)
if filterTitle == "Node Filters" {
Toggle(isOn: $isIgnored) {
Label {
Text("Ignored")
} icon: {
Image(systemName: "minus.circle.fill")
.symbolRenderingMode(.multicolor)
}
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
.listRowSeparator(.visible)
Toggle(isOn: $isEnvironment) {
Label {
Text("Environment")

View file

@ -196,6 +196,9 @@ struct Firmware: View {
}
}
Api().loadFirmwareReleaseData { (fw) in
latestStable = fw.releases.stable.first
let archString = currentDevice?.architecture.rawValue ?? ""
let ls = fw.releases.stable.first(where: { $0.zipURL.contains(archString) == true })
latestStable = fw.releases.stable.first
latestAlpha = fw.releases.alpha.first
}

View file

@ -498,6 +498,16 @@ public struct AdminMessage: @unchecked Sendable {
set {payloadVariant = .addContact(newValue)}
}
///
/// Initiate or respond to a key verification request
public var keyVerification: KeyVerificationAdmin {
get {
if case .keyVerification(let v)? = payloadVariant {return v}
return KeyVerificationAdmin()
}
set {payloadVariant = .keyVerification(newValue)}
}
///
/// Tell the node to factory reset config everything; all device state and configuration will be returned to factory defaults and BLE bonds will be cleared.
public var factoryResetDevice: Int32 {
@ -719,6 +729,9 @@ public struct AdminMessage: @unchecked Sendable {
/// Add a contact (User) to the nodedb
case addContact(SharedContact)
///
/// Initiate or respond to a key verification request
case keyVerification(KeyVerificationAdmin)
///
/// Tell the node to factory reset config everything; all device state and configuration will be returned to factory defaults and BLE bonds will be cleared.
case factoryResetDevice(Int32)
///
@ -1077,6 +1090,98 @@ public struct SharedContact: Sendable {
fileprivate var _user: User? = nil
}
///
/// This message is used by a client to initiate or complete a key verification
public struct KeyVerificationAdmin: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
public var messageType: KeyVerificationAdmin.MessageType = .initiateVerification
///
/// The nodenum we're requesting
public var remoteNodenum: UInt32 = 0
///
/// The nonce is used to track the connection
public var nonce: UInt64 = 0
///
/// The 4 digit code generated by the remote node, and communicated outside the mesh
public var securityNumber: UInt32 {
get {return _securityNumber ?? 0}
set {_securityNumber = newValue}
}
/// Returns true if `securityNumber` has been explicitly set.
public var hasSecurityNumber: Bool {return self._securityNumber != nil}
/// Clears the value of `securityNumber`. Subsequent reads from it will return its default value.
public mutating func clearSecurityNumber() {self._securityNumber = nil}
public var unknownFields = SwiftProtobuf.UnknownStorage()
///
/// Three stages of this request.
public enum MessageType: SwiftProtobuf.Enum, Swift.CaseIterable {
public typealias RawValue = Int
///
/// This is the first stage, where a client initiates
case initiateVerification // = 0
///
/// After the nonce has been returned over the mesh, the client prompts for the security number
/// And uses this message to provide it to the node.
case provideSecurityNumber // = 1
///
/// Once the user has compared the verification message, this message notifies the node.
case doVerify // = 2
///
/// This is the cancel path, can be taken at any point
case doNotVerify // = 3
case UNRECOGNIZED(Int)
public init() {
self = .initiateVerification
}
public init?(rawValue: Int) {
switch rawValue {
case 0: self = .initiateVerification
case 1: self = .provideSecurityNumber
case 2: self = .doVerify
case 3: self = .doNotVerify
default: self = .UNRECOGNIZED(rawValue)
}
}
public var rawValue: Int {
switch self {
case .initiateVerification: return 0
case .provideSecurityNumber: return 1
case .doVerify: return 2
case .doNotVerify: return 3
case .UNRECOGNIZED(let i): return i
}
}
// The compiler won't synthesize support with the UNRECOGNIZED case.
public static let allCases: [KeyVerificationAdmin.MessageType] = [
.initiateVerification,
.provideSecurityNumber,
.doVerify,
.doNotVerify,
]
}
public init() {}
fileprivate var _securityNumber: UInt32? = nil
}
// MARK: - Code below here is support for the SwiftProtobuf runtime.
fileprivate let _protobuf_package = "meshtastic"
@ -1130,6 +1235,7 @@ extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat
64: .standard(proto: "begin_edit_settings"),
65: .standard(proto: "commit_edit_settings"),
66: .standard(proto: "add_contact"),
67: .standard(proto: "key_verification"),
94: .standard(proto: "factory_reset_device"),
95: .standard(proto: "reboot_ota_seconds"),
96: .standard(proto: "exit_simulator"),
@ -1585,6 +1691,19 @@ extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat
self.payloadVariant = .addContact(v)
}
}()
case 67: try {
var v: KeyVerificationAdmin?
var hadOneofValue = false
if let current = self.payloadVariant {
hadOneofValue = true
if case .keyVerification(let m) = current {v = m}
}
try decoder.decodeSingularMessageField(value: &v)
if let v = v {
if hadOneofValue {try decoder.handleConflictingOneOf()}
self.payloadVariant = .keyVerification(v)
}
}()
case 94: try {
var v: Int32?
try decoder.decodeSingularInt32Field(value: &v)
@ -1833,6 +1952,10 @@ extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat
guard case .addContact(let v)? = self.payloadVariant else { preconditionFailure() }
try visitor.visitSingularMessageField(value: v, fieldNumber: 66)
}()
case .keyVerification?: try {
guard case .keyVerification(let v)? = self.payloadVariant else { preconditionFailure() }
try visitor.visitSingularMessageField(value: v, fieldNumber: 67)
}()
case .factoryResetDevice?: try {
guard case .factoryResetDevice(let v)? = self.payloadVariant else { preconditionFailure() }
try visitor.visitSingularInt32Field(value: v, fieldNumber: 94)
@ -2040,3 +2163,66 @@ extension SharedContact: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementa
return true
}
}
extension KeyVerificationAdmin: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
public static let protoMessageName: String = _protobuf_package + ".KeyVerificationAdmin"
public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .standard(proto: "message_type"),
2: .standard(proto: "remote_nodenum"),
3: .same(proto: "nonce"),
4: .standard(proto: "security_number"),
]
public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
while let fieldNumber = try decoder.nextFieldNumber() {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every case branch when no optimizations are
// enabled. https://github.com/apple/swift-protobuf/issues/1034
switch fieldNumber {
case 1: try { try decoder.decodeSingularEnumField(value: &self.messageType) }()
case 2: try { try decoder.decodeSingularUInt32Field(value: &self.remoteNodenum) }()
case 3: try { try decoder.decodeSingularUInt64Field(value: &self.nonce) }()
case 4: try { try decoder.decodeSingularUInt32Field(value: &self._securityNumber) }()
default: break
}
}
}
public func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every if/case branch local when no optimizations
// are enabled. https://github.com/apple/swift-protobuf/issues/1034 and
// https://github.com/apple/swift-protobuf/issues/1182
if self.messageType != .initiateVerification {
try visitor.visitSingularEnumField(value: self.messageType, fieldNumber: 1)
}
if self.remoteNodenum != 0 {
try visitor.visitSingularUInt32Field(value: self.remoteNodenum, fieldNumber: 2)
}
if self.nonce != 0 {
try visitor.visitSingularUInt64Field(value: self.nonce, fieldNumber: 3)
}
try { if let v = self._securityNumber {
try visitor.visitSingularUInt32Field(value: v, fieldNumber: 4)
} }()
try unknownFields.traverse(visitor: &visitor)
}
public static func ==(lhs: KeyVerificationAdmin, rhs: KeyVerificationAdmin) -> Bool {
if lhs.messageType != rhs.messageType {return false}
if lhs.remoteNodenum != rhs.remoteNodenum {return false}
if lhs.nonce != rhs.nonce {return false}
if lhs._securityNumber != rhs._securityNumber {return false}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}
}
extension KeyVerificationAdmin.MessageType: SwiftProtobuf._ProtoNameProviding {
public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
0: .same(proto: "INITIATE_VERIFICATION"),
1: .same(proto: "PROVIDE_SECURITY_NUMBER"),
2: .same(proto: "DO_VERIFY"),
3: .same(proto: "DO_NOT_VERIFY"),
]
}

View file

@ -756,6 +756,10 @@ public struct Config: Sendable {
/// Flags for enabling/disabling network protocols
public var enabledProtocols: UInt32 = 0
///
/// Enable/Disable ipv6 support
public var ipv6Enabled: Bool = false
public var unknownFields = SwiftProtobuf.UnknownStorage()
public enum AddressMode: SwiftProtobuf.Enum, Swift.CaseIterable {
@ -2385,6 +2389,7 @@ extension Config.NetworkConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImp
8: .standard(proto: "ipv4_config"),
9: .standard(proto: "rsyslog_server"),
10: .standard(proto: "enabled_protocols"),
11: .standard(proto: "ipv6_enabled"),
]
public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
@ -2402,6 +2407,7 @@ extension Config.NetworkConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImp
case 8: try { try decoder.decodeSingularMessageField(value: &self._ipv4Config) }()
case 9: try { try decoder.decodeSingularStringField(value: &self.rsyslogServer) }()
case 10: try { try decoder.decodeSingularUInt32Field(value: &self.enabledProtocols) }()
case 11: try { try decoder.decodeSingularBoolField(value: &self.ipv6Enabled) }()
default: break
}
}
@ -2439,6 +2445,9 @@ extension Config.NetworkConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImp
if self.enabledProtocols != 0 {
try visitor.visitSingularUInt32Field(value: self.enabledProtocols, fieldNumber: 10)
}
if self.ipv6Enabled != false {
try visitor.visitSingularBoolField(value: self.ipv6Enabled, fieldNumber: 11)
}
try unknownFields.traverse(visitor: &visitor)
}
@ -2452,6 +2461,7 @@ extension Config.NetworkConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImp
if lhs._ipv4Config != rhs._ipv4Config {return false}
if lhs.rsyslogServer != rhs.rsyslogServer {return false}
if lhs.enabledProtocols != rhs.enabledProtocols {return false}
if lhs.ipv6Enabled != rhs.ipv6Enabled {return false}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}

View file

@ -227,6 +227,14 @@ public struct NodeInfoLite: @unchecked Sendable {
set {_uniqueStorage()._nextHop = newValue}
}
///
/// Bitfield for storing booleans.
/// LSB 0 is_key_manually_verified
public var bitfield: UInt32 {
get {return _storage._bitfield}
set {_uniqueStorage()._bitfield = newValue}
}
public var unknownFields = SwiftProtobuf.UnknownStorage()
public init() {}
@ -608,6 +616,7 @@ extension NodeInfoLite: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat
10: .standard(proto: "is_favorite"),
11: .standard(proto: "is_ignored"),
12: .standard(proto: "next_hop"),
13: .same(proto: "bitfield"),
]
fileprivate class _StorageClass {
@ -623,6 +632,7 @@ extension NodeInfoLite: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat
var _isFavorite: Bool = false
var _isIgnored: Bool = false
var _nextHop: UInt32 = 0
var _bitfield: UInt32 = 0
#if swift(>=5.10)
// This property is used as the initial default value for new instances of the type.
@ -649,6 +659,7 @@ extension NodeInfoLite: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat
_isFavorite = source._isFavorite
_isIgnored = source._isIgnored
_nextHop = source._nextHop
_bitfield = source._bitfield
}
}
@ -679,6 +690,7 @@ extension NodeInfoLite: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat
case 10: try { try decoder.decodeSingularBoolField(value: &_storage._isFavorite) }()
case 11: try { try decoder.decodeSingularBoolField(value: &_storage._isIgnored) }()
case 12: try { try decoder.decodeSingularUInt32Field(value: &_storage._nextHop) }()
case 13: try { try decoder.decodeSingularUInt32Field(value: &_storage._bitfield) }()
default: break
}
}
@ -727,6 +739,9 @@ extension NodeInfoLite: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat
if _storage._nextHop != 0 {
try visitor.visitSingularUInt32Field(value: _storage._nextHop, fieldNumber: 12)
}
if _storage._bitfield != 0 {
try visitor.visitSingularUInt32Field(value: _storage._bitfield, fieldNumber: 13)
}
}
try unknownFields.traverse(visitor: &visitor)
}
@ -748,6 +763,7 @@ extension NodeInfoLite: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat
if _storage._isFavorite != rhs_storage._isFavorite {return false}
if _storage._isIgnored != rhs_storage._isIgnored {return false}
if _storage._nextHop != rhs_storage._nextHop {return false}
if _storage._bitfield != rhs_storage._bitfield {return false}
return true
}
if !storagesAreEqual {return false}

View file

@ -442,6 +442,22 @@ public enum HardwareModel: SwiftProtobuf.Enum, Swift.CaseIterable {
/// Elecrow CrowPanel Advance models, ESP32-S3 and TFT with SX1262 radio plugin
case crowpanel // = 97
///*
/// Lilygo LINK32 board with sensors
case link32 // = 98
///*
/// Seeed Tracker L1
case seeedWioTrackerL1 // = 99
///*
/// Seeed Tracker L1 EINK driver
case seeedWioTrackerL1Eink // = 100
///
/// Reserved ID for future and past use
case qwantzTinyArms // = 101
///
/// ------------------------------------------------------------------------------------------------------------------------------------------
/// Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits.
@ -553,6 +569,10 @@ public enum HardwareModel: SwiftProtobuf.Enum, Swift.CaseIterable {
case 95: self = .seeedSolarNode
case 96: self = .nomadstarMeteorPro
case 97: self = .crowpanel
case 98: self = .link32
case 99: self = .seeedWioTrackerL1
case 100: self = .seeedWioTrackerL1Eink
case 101: self = .qwantzTinyArms
case 255: self = .privateHw
default: self = .UNRECOGNIZED(rawValue)
}
@ -658,6 +678,10 @@ public enum HardwareModel: SwiftProtobuf.Enum, Swift.CaseIterable {
case .seeedSolarNode: return 95
case .nomadstarMeteorPro: return 96
case .crowpanel: return 97
case .link32: return 98
case .seeedWioTrackerL1: return 99
case .seeedWioTrackerL1Eink: return 100
case .qwantzTinyArms: return 101
case .privateHw: return 255
case .UNRECOGNIZED(let i): return i
}
@ -763,6 +787,10 @@ public enum HardwareModel: SwiftProtobuf.Enum, Swift.CaseIterable {
.seeedSolarNode,
.nomadstarMeteorPro,
.crowpanel,
.link32,
.seeedWioTrackerL1,
.seeedWioTrackerL1Eink,
.qwantzTinyArms,
.privateHw,
]
@ -1820,6 +1848,31 @@ public struct DataMessage: @unchecked Sendable {
fileprivate var _bitfield: UInt32? = nil
}
///
/// The actual over-the-mesh message doing KeyVerification
public struct KeyVerification: @unchecked Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
///
/// random value Selected by the requesting node
public var nonce: UInt64 = 0
///
/// The final authoritative hash, only to be sent by NodeA at the end of the handshake
public var hash1: Data = Data()
///
/// The intermediary hash (actually derived from hash1),
/// sent from NodeB to NodeA in response to the initial message.
public var hash2: Data = Data()
public var unknownFields = SwiftProtobuf.UnknownStorage()
public init() {}
}
///
/// Waypoint message, used to share arbitrary locations across the mesh
public struct Waypoint: Sendable {
@ -2441,6 +2494,15 @@ public struct NodeInfo: @unchecked Sendable {
set {_uniqueStorage()._isIgnored = newValue}
}
///
/// True if node public key has been verified.
/// Persists between NodeDB internal clean ups
/// LSB 0 of the bitfield
public var isKeyManuallyVerified: Bool {
get {return _storage._isKeyManuallyVerified}
set {_uniqueStorage()._isKeyManuallyVerified = newValue}
}
public var unknownFields = SwiftProtobuf.UnknownStorage()
public init() {}
@ -2903,13 +2965,94 @@ public struct ClientNotification: Sendable {
/// The message body of the notification
public var message: String = String()
public var payloadVariant: ClientNotification.OneOf_PayloadVariant? = nil
public var keyVerificationNumberInform: KeyVerificationNumberInform {
get {
if case .keyVerificationNumberInform(let v)? = payloadVariant {return v}
return KeyVerificationNumberInform()
}
set {payloadVariant = .keyVerificationNumberInform(newValue)}
}
public var keyVerificationNumberRequest: KeyVerificationNumberRequest {
get {
if case .keyVerificationNumberRequest(let v)? = payloadVariant {return v}
return KeyVerificationNumberRequest()
}
set {payloadVariant = .keyVerificationNumberRequest(newValue)}
}
public var keyVerificationFinal: KeyVerificationFinal {
get {
if case .keyVerificationFinal(let v)? = payloadVariant {return v}
return KeyVerificationFinal()
}
set {payloadVariant = .keyVerificationFinal(newValue)}
}
public var unknownFields = SwiftProtobuf.UnknownStorage()
public enum OneOf_PayloadVariant: Equatable, Sendable {
case keyVerificationNumberInform(KeyVerificationNumberInform)
case keyVerificationNumberRequest(KeyVerificationNumberRequest)
case keyVerificationFinal(KeyVerificationFinal)
}
public init() {}
fileprivate var _replyID: UInt32? = nil
}
public struct KeyVerificationNumberInform: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
public var nonce: UInt64 = 0
public var remoteLongname: String = String()
public var securityNumber: UInt32 = 0
public var unknownFields = SwiftProtobuf.UnknownStorage()
public init() {}
}
public struct KeyVerificationNumberRequest: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
public var nonce: UInt64 = 0
public var remoteLongname: String = String()
public var unknownFields = SwiftProtobuf.UnknownStorage()
public init() {}
}
public struct KeyVerificationFinal: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
public var nonce: UInt64 = 0
public var remoteLongname: String = String()
public var isSender: Bool = false
public var verificationCharacters: String = String()
public var unknownFields = SwiftProtobuf.UnknownStorage()
public init() {}
}
///
/// Individual File info for the device
public struct FileInfo: Sendable {
@ -3431,6 +3574,10 @@ extension HardwareModel: SwiftProtobuf._ProtoNameProviding {
95: .same(proto: "SEEED_SOLAR_NODE"),
96: .same(proto: "NOMADSTAR_METEOR_PRO"),
97: .same(proto: "CROWPANEL"),
98: .same(proto: "LINK_32"),
99: .same(proto: "SEEED_WIO_TRACKER_L1"),
100: .same(proto: "SEEED_WIO_TRACKER_L1_EINK"),
101: .same(proto: "QWANTZ_TINY_ARMS"),
255: .same(proto: "PRIVATE_HW"),
]
}
@ -4075,6 +4222,50 @@ extension DataMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementati
}
}
extension KeyVerification: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
public static let protoMessageName: String = _protobuf_package + ".KeyVerification"
public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .same(proto: "nonce"),
2: .same(proto: "hash1"),
3: .same(proto: "hash2"),
]
public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
while let fieldNumber = try decoder.nextFieldNumber() {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every case branch when no optimizations are
// enabled. https://github.com/apple/swift-protobuf/issues/1034
switch fieldNumber {
case 1: try { try decoder.decodeSingularUInt64Field(value: &self.nonce) }()
case 2: try { try decoder.decodeSingularBytesField(value: &self.hash1) }()
case 3: try { try decoder.decodeSingularBytesField(value: &self.hash2) }()
default: break
}
}
}
public func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
if self.nonce != 0 {
try visitor.visitSingularUInt64Field(value: self.nonce, fieldNumber: 1)
}
if !self.hash1.isEmpty {
try visitor.visitSingularBytesField(value: self.hash1, fieldNumber: 2)
}
if !self.hash2.isEmpty {
try visitor.visitSingularBytesField(value: self.hash2, fieldNumber: 3)
}
try unknownFields.traverse(visitor: &visitor)
}
public static func ==(lhs: KeyVerification, rhs: KeyVerification) -> Bool {
if lhs.nonce != rhs.nonce {return false}
if lhs.hash1 != rhs.hash1 {return false}
if lhs.hash2 != rhs.hash2 {return false}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}
}
extension Waypoint: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
public static let protoMessageName: String = _protobuf_package + ".Waypoint"
public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
@ -4511,6 +4702,7 @@ extension NodeInfo: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB
9: .standard(proto: "hops_away"),
10: .standard(proto: "is_favorite"),
11: .standard(proto: "is_ignored"),
12: .standard(proto: "is_key_manually_verified"),
]
fileprivate class _StorageClass {
@ -4525,6 +4717,7 @@ extension NodeInfo: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB
var _hopsAway: UInt32? = nil
var _isFavorite: Bool = false
var _isIgnored: Bool = false
var _isKeyManuallyVerified: Bool = false
#if swift(>=5.10)
// This property is used as the initial default value for new instances of the type.
@ -4550,6 +4743,7 @@ extension NodeInfo: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB
_hopsAway = source._hopsAway
_isFavorite = source._isFavorite
_isIgnored = source._isIgnored
_isKeyManuallyVerified = source._isKeyManuallyVerified
}
}
@ -4579,6 +4773,7 @@ extension NodeInfo: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB
case 9: try { try decoder.decodeSingularUInt32Field(value: &_storage._hopsAway) }()
case 10: try { try decoder.decodeSingularBoolField(value: &_storage._isFavorite) }()
case 11: try { try decoder.decodeSingularBoolField(value: &_storage._isIgnored) }()
case 12: try { try decoder.decodeSingularBoolField(value: &_storage._isKeyManuallyVerified) }()
default: break
}
}
@ -4624,6 +4819,9 @@ extension NodeInfo: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB
if _storage._isIgnored != false {
try visitor.visitSingularBoolField(value: _storage._isIgnored, fieldNumber: 11)
}
if _storage._isKeyManuallyVerified != false {
try visitor.visitSingularBoolField(value: _storage._isKeyManuallyVerified, fieldNumber: 12)
}
}
try unknownFields.traverse(visitor: &visitor)
}
@ -4644,6 +4842,7 @@ extension NodeInfo: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB
if _storage._hopsAway != rhs_storage._hopsAway {return false}
if _storage._isFavorite != rhs_storage._isFavorite {return false}
if _storage._isIgnored != rhs_storage._isIgnored {return false}
if _storage._isKeyManuallyVerified != rhs_storage._isKeyManuallyVerified {return false}
return true
}
if !storagesAreEqual {return false}
@ -5146,6 +5345,9 @@ extension ClientNotification: SwiftProtobuf.Message, SwiftProtobuf._MessageImple
2: .same(proto: "time"),
3: .same(proto: "level"),
4: .same(proto: "message"),
11: .standard(proto: "key_verification_number_inform"),
12: .standard(proto: "key_verification_number_request"),
13: .standard(proto: "key_verification_final"),
]
public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
@ -5158,6 +5360,45 @@ extension ClientNotification: SwiftProtobuf.Message, SwiftProtobuf._MessageImple
case 2: try { try decoder.decodeSingularFixed32Field(value: &self.time) }()
case 3: try { try decoder.decodeSingularEnumField(value: &self.level) }()
case 4: try { try decoder.decodeSingularStringField(value: &self.message) }()
case 11: try {
var v: KeyVerificationNumberInform?
var hadOneofValue = false
if let current = self.payloadVariant {
hadOneofValue = true
if case .keyVerificationNumberInform(let m) = current {v = m}
}
try decoder.decodeSingularMessageField(value: &v)
if let v = v {
if hadOneofValue {try decoder.handleConflictingOneOf()}
self.payloadVariant = .keyVerificationNumberInform(v)
}
}()
case 12: try {
var v: KeyVerificationNumberRequest?
var hadOneofValue = false
if let current = self.payloadVariant {
hadOneofValue = true
if case .keyVerificationNumberRequest(let m) = current {v = m}
}
try decoder.decodeSingularMessageField(value: &v)
if let v = v {
if hadOneofValue {try decoder.handleConflictingOneOf()}
self.payloadVariant = .keyVerificationNumberRequest(v)
}
}()
case 13: try {
var v: KeyVerificationFinal?
var hadOneofValue = false
if let current = self.payloadVariant {
hadOneofValue = true
if case .keyVerificationFinal(let m) = current {v = m}
}
try decoder.decodeSingularMessageField(value: &v)
if let v = v {
if hadOneofValue {try decoder.handleConflictingOneOf()}
self.payloadVariant = .keyVerificationFinal(v)
}
}()
default: break
}
}
@ -5180,6 +5421,21 @@ extension ClientNotification: SwiftProtobuf.Message, SwiftProtobuf._MessageImple
if !self.message.isEmpty {
try visitor.visitSingularStringField(value: self.message, fieldNumber: 4)
}
switch self.payloadVariant {
case .keyVerificationNumberInform?: try {
guard case .keyVerificationNumberInform(let v)? = self.payloadVariant else { preconditionFailure() }
try visitor.visitSingularMessageField(value: v, fieldNumber: 11)
}()
case .keyVerificationNumberRequest?: try {
guard case .keyVerificationNumberRequest(let v)? = self.payloadVariant else { preconditionFailure() }
try visitor.visitSingularMessageField(value: v, fieldNumber: 12)
}()
case .keyVerificationFinal?: try {
guard case .keyVerificationFinal(let v)? = self.payloadVariant else { preconditionFailure() }
try visitor.visitSingularMessageField(value: v, fieldNumber: 13)
}()
case nil: break
}
try unknownFields.traverse(visitor: &visitor)
}
@ -5188,6 +5444,139 @@ extension ClientNotification: SwiftProtobuf.Message, SwiftProtobuf._MessageImple
if lhs.time != rhs.time {return false}
if lhs.level != rhs.level {return false}
if lhs.message != rhs.message {return false}
if lhs.payloadVariant != rhs.payloadVariant {return false}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}
}
extension KeyVerificationNumberInform: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
public static let protoMessageName: String = _protobuf_package + ".KeyVerificationNumberInform"
public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .same(proto: "nonce"),
2: .standard(proto: "remote_longname"),
3: .standard(proto: "security_number"),
]
public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
while let fieldNumber = try decoder.nextFieldNumber() {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every case branch when no optimizations are
// enabled. https://github.com/apple/swift-protobuf/issues/1034
switch fieldNumber {
case 1: try { try decoder.decodeSingularUInt64Field(value: &self.nonce) }()
case 2: try { try decoder.decodeSingularStringField(value: &self.remoteLongname) }()
case 3: try { try decoder.decodeSingularUInt32Field(value: &self.securityNumber) }()
default: break
}
}
}
public func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
if self.nonce != 0 {
try visitor.visitSingularUInt64Field(value: self.nonce, fieldNumber: 1)
}
if !self.remoteLongname.isEmpty {
try visitor.visitSingularStringField(value: self.remoteLongname, fieldNumber: 2)
}
if self.securityNumber != 0 {
try visitor.visitSingularUInt32Field(value: self.securityNumber, fieldNumber: 3)
}
try unknownFields.traverse(visitor: &visitor)
}
public static func ==(lhs: KeyVerificationNumberInform, rhs: KeyVerificationNumberInform) -> Bool {
if lhs.nonce != rhs.nonce {return false}
if lhs.remoteLongname != rhs.remoteLongname {return false}
if lhs.securityNumber != rhs.securityNumber {return false}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}
}
extension KeyVerificationNumberRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
public static let protoMessageName: String = _protobuf_package + ".KeyVerificationNumberRequest"
public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .same(proto: "nonce"),
2: .standard(proto: "remote_longname"),
]
public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
while let fieldNumber = try decoder.nextFieldNumber() {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every case branch when no optimizations are
// enabled. https://github.com/apple/swift-protobuf/issues/1034
switch fieldNumber {
case 1: try { try decoder.decodeSingularUInt64Field(value: &self.nonce) }()
case 2: try { try decoder.decodeSingularStringField(value: &self.remoteLongname) }()
default: break
}
}
}
public func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
if self.nonce != 0 {
try visitor.visitSingularUInt64Field(value: self.nonce, fieldNumber: 1)
}
if !self.remoteLongname.isEmpty {
try visitor.visitSingularStringField(value: self.remoteLongname, fieldNumber: 2)
}
try unknownFields.traverse(visitor: &visitor)
}
public static func ==(lhs: KeyVerificationNumberRequest, rhs: KeyVerificationNumberRequest) -> Bool {
if lhs.nonce != rhs.nonce {return false}
if lhs.remoteLongname != rhs.remoteLongname {return false}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}
}
extension KeyVerificationFinal: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
public static let protoMessageName: String = _protobuf_package + ".KeyVerificationFinal"
public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .same(proto: "nonce"),
2: .standard(proto: "remote_longname"),
3: .same(proto: "isSender"),
4: .standard(proto: "verification_characters"),
]
public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
while let fieldNumber = try decoder.nextFieldNumber() {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every case branch when no optimizations are
// enabled. https://github.com/apple/swift-protobuf/issues/1034
switch fieldNumber {
case 1: try { try decoder.decodeSingularUInt64Field(value: &self.nonce) }()
case 2: try { try decoder.decodeSingularStringField(value: &self.remoteLongname) }()
case 3: try { try decoder.decodeSingularBoolField(value: &self.isSender) }()
case 4: try { try decoder.decodeSingularStringField(value: &self.verificationCharacters) }()
default: break
}
}
}
public func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
if self.nonce != 0 {
try visitor.visitSingularUInt64Field(value: self.nonce, fieldNumber: 1)
}
if !self.remoteLongname.isEmpty {
try visitor.visitSingularStringField(value: self.remoteLongname, fieldNumber: 2)
}
if self.isSender != false {
try visitor.visitSingularBoolField(value: self.isSender, fieldNumber: 3)
}
if !self.verificationCharacters.isEmpty {
try visitor.visitSingularStringField(value: self.verificationCharacters, fieldNumber: 4)
}
try unknownFields.traverse(visitor: &visitor)
}
public static func ==(lhs: KeyVerificationFinal, rhs: KeyVerificationFinal) -> Bool {
if lhs.nonce != rhs.nonce {return false}
if lhs.remoteLongname != rhs.remoteLongname {return false}
if lhs.isSender != rhs.isSender {return false}
if lhs.verificationCharacters != rhs.verificationCharacters {return false}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}

View file

@ -111,6 +111,10 @@ public enum PortNum: SwiftProtobuf.Enum, Swift.CaseIterable {
/// Same as Text Message but used for critical alerts.
case alertApp // = 11
///
/// Module/port for handling key verification requests.
case keyVerificationApp // = 12
///
/// Provides a 'ping' service that replies to any packet it receives.
/// Also serves as a small example module.
@ -232,6 +236,7 @@ public enum PortNum: SwiftProtobuf.Enum, Swift.CaseIterable {
case 9: self = .audioApp
case 10: self = .detectionSensorApp
case 11: self = .alertApp
case 12: self = .keyVerificationApp
case 32: self = .replyApp
case 33: self = .ipTunnelApp
case 34: self = .paxcounterApp
@ -268,6 +273,7 @@ public enum PortNum: SwiftProtobuf.Enum, Swift.CaseIterable {
case .audioApp: return 9
case .detectionSensorApp: return 10
case .alertApp: return 11
case .keyVerificationApp: return 12
case .replyApp: return 32
case .ipTunnelApp: return 33
case .paxcounterApp: return 34
@ -304,6 +310,7 @@ public enum PortNum: SwiftProtobuf.Enum, Swift.CaseIterable {
.audioApp,
.detectionSensorApp,
.alertApp,
.keyVerificationApp,
.replyApp,
.ipTunnelApp,
.paxcounterApp,
@ -342,6 +349,7 @@ extension PortNum: SwiftProtobuf._ProtoNameProviding {
9: .same(proto: "AUDIO_APP"),
10: .same(proto: "DETECTION_SENSOR_APP"),
11: .same(proto: "ALERT_APP"),
12: .same(proto: "KEY_VERIFICATION_APP"),
32: .same(proto: "REPLY_APP"),
33: .same(proto: "IP_TUNNEL_APP"),
34: .same(proto: "PAXCOUNTER_APP"),

View file

@ -180,6 +180,10 @@ public enum TelemetrySensorType: SwiftProtobuf.Enum, Swift.CaseIterable {
///
/// MAX17261 lipo battery gauge
case max17261 // = 38
///
/// PCT2075 Temperature Sensor
case pct2075 // = 39
case UNRECOGNIZED(Int)
public init() {
@ -227,6 +231,7 @@ public enum TelemetrySensorType: SwiftProtobuf.Enum, Swift.CaseIterable {
case 36: self = .dps310
case 37: self = .rak12035
case 38: self = .max17261
case 39: self = .pct2075
default: self = .UNRECOGNIZED(rawValue)
}
}
@ -272,6 +277,7 @@ public enum TelemetrySensorType: SwiftProtobuf.Enum, Swift.CaseIterable {
case .dps310: return 36
case .rak12035: return 37
case .max17261: return 38
case .pct2075: return 39
case .UNRECOGNIZED(let i): return i
}
}
@ -317,6 +323,7 @@ public enum TelemetrySensorType: SwiftProtobuf.Enum, Swift.CaseIterable {
.dps310,
.rak12035,
.max17261,
.pct2075,
]
}
@ -959,6 +966,14 @@ public struct LocalStats: Sendable {
/// This will always be zero for ROUTERs/REPEATERs. If this number is high, some other node(s) is/are relaying faster than you.
public var numTxRelayCanceled: UInt32 = 0
///
/// Number of bytes used in the heap
public var heapTotalBytes: UInt32 = 0
///
/// Number of bytes free in the heap
public var heapFreeBytes: UInt32 = 0
public var unknownFields = SwiftProtobuf.UnknownStorage()
public init() {}
@ -1013,6 +1028,80 @@ public struct HealthMetrics: Sendable {
fileprivate var _temperature: Float? = nil
}
///
/// Linux host metrics
public struct HostMetrics: Sendable {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
///
/// Host system uptime
public var uptimeSeconds: UInt32 = 0
///
/// Host system free memory
public var freememBytes: UInt64 = 0
///
/// Host system disk space free for /
public var diskfree1Bytes: UInt64 = 0
///
/// Secondary system disk space free
public var diskfree2Bytes: UInt64 {
get {return _diskfree2Bytes ?? 0}
set {_diskfree2Bytes = newValue}
}
/// Returns true if `diskfree2Bytes` has been explicitly set.
public var hasDiskfree2Bytes: Bool {return self._diskfree2Bytes != nil}
/// Clears the value of `diskfree2Bytes`. Subsequent reads from it will return its default value.
public mutating func clearDiskfree2Bytes() {self._diskfree2Bytes = nil}
///
/// Tertiary disk space free
public var diskfree3Bytes: UInt64 {
get {return _diskfree3Bytes ?? 0}
set {_diskfree3Bytes = newValue}
}
/// Returns true if `diskfree3Bytes` has been explicitly set.
public var hasDiskfree3Bytes: Bool {return self._diskfree3Bytes != nil}
/// Clears the value of `diskfree3Bytes`. Subsequent reads from it will return its default value.
public mutating func clearDiskfree3Bytes() {self._diskfree3Bytes = nil}
///
/// Host system one minute load in 1/100ths
public var load1: UInt32 = 0
///
/// Host system five minute load in 1/100ths
public var load5: UInt32 = 0
///
/// Host system fifteen minute load in 1/100ths
public var load15: UInt32 = 0
///
/// Optional User-provided string for arbitrary host system information
/// that doesn't make sense as a dedicated entry.
public var userString: String {
get {return _userString ?? String()}
set {_userString = newValue}
}
/// Returns true if `userString` has been explicitly set.
public var hasUserString: Bool {return self._userString != nil}
/// Clears the value of `userString`. Subsequent reads from it will return its default value.
public mutating func clearUserString() {self._userString = nil}
public var unknownFields = SwiftProtobuf.UnknownStorage()
public init() {}
fileprivate var _diskfree2Bytes: UInt64? = nil
fileprivate var _diskfree3Bytes: UInt64? = nil
fileprivate var _userString: String? = nil
}
///
/// Types of Measurements the telemetry module is equipped to handle
public struct Telemetry: Sendable {
@ -1086,6 +1175,16 @@ public struct Telemetry: Sendable {
set {variant = .healthMetrics(newValue)}
}
///
/// Linux host metrics
public var hostMetrics: HostMetrics {
get {
if case .hostMetrics(let v)? = variant {return v}
return HostMetrics()
}
set {variant = .hostMetrics(newValue)}
}
public var unknownFields = SwiftProtobuf.UnknownStorage()
public enum OneOf_Variant: Equatable, Sendable {
@ -1107,6 +1206,9 @@ public struct Telemetry: Sendable {
///
/// Health telemetry metrics
case healthMetrics(HealthMetrics)
///
/// Linux host metrics
case hostMetrics(HostMetrics)
}
@ -1178,6 +1280,7 @@ extension TelemetrySensorType: SwiftProtobuf._ProtoNameProviding {
36: .same(proto: "DPS310"),
37: .same(proto: "RAK12035"),
38: .same(proto: "MAX17261"),
39: .same(proto: "PCT2075"),
]
}
@ -1673,6 +1776,8 @@ extension LocalStats: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio
9: .standard(proto: "num_rx_dupe"),
10: .standard(proto: "num_tx_relay"),
11: .standard(proto: "num_tx_relay_canceled"),
12: .standard(proto: "heap_total_bytes"),
13: .standard(proto: "heap_free_bytes"),
]
public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
@ -1692,6 +1797,8 @@ extension LocalStats: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio
case 9: try { try decoder.decodeSingularUInt32Field(value: &self.numRxDupe) }()
case 10: try { try decoder.decodeSingularUInt32Field(value: &self.numTxRelay) }()
case 11: try { try decoder.decodeSingularUInt32Field(value: &self.numTxRelayCanceled) }()
case 12: try { try decoder.decodeSingularUInt32Field(value: &self.heapTotalBytes) }()
case 13: try { try decoder.decodeSingularUInt32Field(value: &self.heapFreeBytes) }()
default: break
}
}
@ -1731,6 +1838,12 @@ extension LocalStats: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio
if self.numTxRelayCanceled != 0 {
try visitor.visitSingularUInt32Field(value: self.numTxRelayCanceled, fieldNumber: 11)
}
if self.heapTotalBytes != 0 {
try visitor.visitSingularUInt32Field(value: self.heapTotalBytes, fieldNumber: 12)
}
if self.heapFreeBytes != 0 {
try visitor.visitSingularUInt32Field(value: self.heapFreeBytes, fieldNumber: 13)
}
try unknownFields.traverse(visitor: &visitor)
}
@ -1746,6 +1859,8 @@ extension LocalStats: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio
if lhs.numRxDupe != rhs.numRxDupe {return false}
if lhs.numTxRelay != rhs.numTxRelay {return false}
if lhs.numTxRelayCanceled != rhs.numTxRelayCanceled {return false}
if lhs.heapTotalBytes != rhs.heapTotalBytes {return false}
if lhs.heapFreeBytes != rhs.heapFreeBytes {return false}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}
@ -1799,6 +1914,90 @@ extension HealthMetrics: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementa
}
}
extension HostMetrics: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
public static let protoMessageName: String = _protobuf_package + ".HostMetrics"
public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .standard(proto: "uptime_seconds"),
2: .standard(proto: "freemem_bytes"),
3: .standard(proto: "diskfree1_bytes"),
4: .standard(proto: "diskfree2_bytes"),
5: .standard(proto: "diskfree3_bytes"),
6: .same(proto: "load1"),
7: .same(proto: "load5"),
8: .same(proto: "load15"),
9: .standard(proto: "user_string"),
]
public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
while let fieldNumber = try decoder.nextFieldNumber() {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every case branch when no optimizations are
// enabled. https://github.com/apple/swift-protobuf/issues/1034
switch fieldNumber {
case 1: try { try decoder.decodeSingularUInt32Field(value: &self.uptimeSeconds) }()
case 2: try { try decoder.decodeSingularUInt64Field(value: &self.freememBytes) }()
case 3: try { try decoder.decodeSingularUInt64Field(value: &self.diskfree1Bytes) }()
case 4: try { try decoder.decodeSingularUInt64Field(value: &self._diskfree2Bytes) }()
case 5: try { try decoder.decodeSingularUInt64Field(value: &self._diskfree3Bytes) }()
case 6: try { try decoder.decodeSingularUInt32Field(value: &self.load1) }()
case 7: try { try decoder.decodeSingularUInt32Field(value: &self.load5) }()
case 8: try { try decoder.decodeSingularUInt32Field(value: &self.load15) }()
case 9: try { try decoder.decodeSingularStringField(value: &self._userString) }()
default: break
}
}
}
public func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every if/case branch local when no optimizations
// are enabled. https://github.com/apple/swift-protobuf/issues/1034 and
// https://github.com/apple/swift-protobuf/issues/1182
if self.uptimeSeconds != 0 {
try visitor.visitSingularUInt32Field(value: self.uptimeSeconds, fieldNumber: 1)
}
if self.freememBytes != 0 {
try visitor.visitSingularUInt64Field(value: self.freememBytes, fieldNumber: 2)
}
if self.diskfree1Bytes != 0 {
try visitor.visitSingularUInt64Field(value: self.diskfree1Bytes, fieldNumber: 3)
}
try { if let v = self._diskfree2Bytes {
try visitor.visitSingularUInt64Field(value: v, fieldNumber: 4)
} }()
try { if let v = self._diskfree3Bytes {
try visitor.visitSingularUInt64Field(value: v, fieldNumber: 5)
} }()
if self.load1 != 0 {
try visitor.visitSingularUInt32Field(value: self.load1, fieldNumber: 6)
}
if self.load5 != 0 {
try visitor.visitSingularUInt32Field(value: self.load5, fieldNumber: 7)
}
if self.load15 != 0 {
try visitor.visitSingularUInt32Field(value: self.load15, fieldNumber: 8)
}
try { if let v = self._userString {
try visitor.visitSingularStringField(value: v, fieldNumber: 9)
} }()
try unknownFields.traverse(visitor: &visitor)
}
public static func ==(lhs: HostMetrics, rhs: HostMetrics) -> Bool {
if lhs.uptimeSeconds != rhs.uptimeSeconds {return false}
if lhs.freememBytes != rhs.freememBytes {return false}
if lhs.diskfree1Bytes != rhs.diskfree1Bytes {return false}
if lhs._diskfree2Bytes != rhs._diskfree2Bytes {return false}
if lhs._diskfree3Bytes != rhs._diskfree3Bytes {return false}
if lhs.load1 != rhs.load1 {return false}
if lhs.load5 != rhs.load5 {return false}
if lhs.load15 != rhs.load15 {return false}
if lhs._userString != rhs._userString {return false}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}
}
extension Telemetry: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
public static let protoMessageName: String = _protobuf_package + ".Telemetry"
public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
@ -1809,6 +2008,7 @@ extension Telemetry: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementation
5: .standard(proto: "power_metrics"),
6: .standard(proto: "local_stats"),
7: .standard(proto: "health_metrics"),
8: .standard(proto: "host_metrics"),
]
public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
@ -1896,6 +2096,19 @@ extension Telemetry: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementation
self.variant = .healthMetrics(v)
}
}()
case 8: try {
var v: HostMetrics?
var hadOneofValue = false
if let current = self.variant {
hadOneofValue = true
if case .hostMetrics(let m) = current {v = m}
}
try decoder.decodeSingularMessageField(value: &v)
if let v = v {
if hadOneofValue {try decoder.handleConflictingOneOf()}
self.variant = .hostMetrics(v)
}
}()
default: break
}
}
@ -1934,6 +2147,10 @@ extension Telemetry: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementation
guard case .healthMetrics(let v)? = self.variant else { preconditionFailure() }
try visitor.visitSingularMessageField(value: v, fieldNumber: 7)
}()
case .hostMetrics?: try {
guard case .hostMetrics(let v)? = self.variant else { preconditionFailure() }
try visitor.visitSingularMessageField(value: v, fieldNumber: 8)
}()
case nil: break
}
try unknownFields.traverse(visitor: &visitor)

View file

@ -5,6 +5,10 @@ if [ ! -x "$(which protoc)" ]; then
brew install swift-protobuf
fi
git submodule update --init --recursive
git submodule foreach --recursive git pull origin master
protoc --proto_path=./protobufs --swift_opt=Visibility=Public --swift_out=./MeshtasticProtobufs/Sources ./protobufs/meshtastic/*.proto
echo "Done generating the swift files from the proto files."