mirror of
https://github.com/meshtastic/Meshtastic-Apple.git
synced 2026-04-20 22:13:56 +00:00
Merge pull request #551 from meshtastic/2.3.0_Working_Changes
2.3.0 working changes - Yolo 😝
This commit is contained in:
commit
f417b80fc0
29 changed files with 1069 additions and 319 deletions
|
|
@ -294,6 +294,7 @@
|
|||
DD3501882852FC3B000FC853 /* Settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Settings.swift; sourceTree = "<group>"; };
|
||||
DD3619132B1EE20700C41C8C /* MeshtasticDataModelV21.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV21.xcdatamodel; sourceTree = "<group>"; };
|
||||
DD3619142B1EF9F900C41C8C /* LocationsHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationsHandler.swift; sourceTree = "<group>"; };
|
||||
DD398EBD2B93F640002B4C51 /* MeshtasticDataModelV 29.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 29.xcdatamodel"; sourceTree = "<group>"; };
|
||||
DD3CC6B428E33FD100FA9159 /* ShareChannels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareChannels.swift; sourceTree = "<group>"; };
|
||||
DD3CC6BB28E366DF00FA9159 /* MeshtasticDataModel.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModel.xcdatamodel; sourceTree = "<group>"; };
|
||||
DD3CC6BD28E4CD9800FA9159 /* BatteryGauge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryGauge.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -1571,7 +1572,7 @@
|
|||
CODE_SIGN_ENTITLEMENTS = Meshtastic/Meshtastic.entitlements;
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
CURRENT_PROJECT_VERSION = 842;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"Meshtastic/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = GCH7VS5Y9R;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
|
|
@ -1583,7 +1584,7 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.2.25;
|
||||
MARKETING_VERSION = 2.3.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
|
|
@ -1605,7 +1606,7 @@
|
|||
CODE_SIGN_ENTITLEMENTS = Meshtastic/Meshtastic.entitlements;
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
CURRENT_PROJECT_VERSION = 842;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"Meshtastic/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = GCH7VS5Y9R;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
|
|
@ -1617,7 +1618,7 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.2.25;
|
||||
MARKETING_VERSION = 2.3.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
|
|
@ -1727,7 +1728,7 @@
|
|||
CODE_SIGN_ENTITLEMENTS = Widgets/WidgetsExtension.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
CURRENT_PROJECT_VERSION = 841;
|
||||
DEVELOPMENT_TEAM = GCH7VS5Y9R;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = Widgets/Info.plist;
|
||||
|
|
@ -1739,7 +1740,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.2.25;
|
||||
MARKETING_VERSION = 2.3.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
|
|
@ -1760,7 +1761,7 @@
|
|||
CODE_SIGN_ENTITLEMENTS = Widgets/WidgetsExtension.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
CURRENT_PROJECT_VERSION = 841;
|
||||
DEVELOPMENT_TEAM = GCH7VS5Y9R;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = Widgets/Info.plist;
|
||||
|
|
@ -1772,7 +1773,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.2.25;
|
||||
MARKETING_VERSION = 2.3.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
|
|
@ -1883,6 +1884,7 @@
|
|||
DD3CC6BA28E366DF00FA9159 /* Meshtastic.xcdatamodeld */ = {
|
||||
isa = XCVersionGroup;
|
||||
children = (
|
||||
DD398EBD2B93F640002B4C51 /* MeshtasticDataModelV 29.xcdatamodel */,
|
||||
DD0E20FF2B892E1300F2D100 /* MeshtasticDataModelV 28.xcdatamodel */,
|
||||
D93069062B81D8900066FBC8 /* MeshtasticDataModelV 27.xcdatamodel */,
|
||||
DD05296F2B77F454008E44CD /* MeshtasticDataModelV 26.xcdatamodel */,
|
||||
|
|
@ -1912,7 +1914,7 @@
|
|||
DD5D0A9A2931AD6B00F7EA61 /* MeshtasticDataModelV2.xcdatamodel */,
|
||||
DD3CC6BB28E366DF00FA9159 /* MeshtasticDataModel.xcdatamodel */,
|
||||
);
|
||||
currentVersion = DD0E20FF2B892E1300F2D100 /* MeshtasticDataModelV 28.xcdatamodel */;
|
||||
currentVersion = DD398EBD2B93F640002B4C51 /* MeshtasticDataModelV 29.xcdatamodel */;
|
||||
name = Meshtastic.xcdatamodeld;
|
||||
path = Meshtastic/Meshtastic.xcdatamodeld;
|
||||
sourceTree = "<group>";
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@ extension UserDefaults {
|
|||
case enableDetectionNotifications
|
||||
case detectionSensorRole
|
||||
case enableSmartPosition
|
||||
case modemPreset
|
||||
case firmwareVersion
|
||||
}
|
||||
|
||||
func reset() {
|
||||
|
|
@ -202,4 +204,20 @@ extension UserDefaults {
|
|||
UserDefaults.standard.set(newValue, forKey: "enableSmartPosition")
|
||||
}
|
||||
}
|
||||
static var modemPreset: Int {
|
||||
get {
|
||||
UserDefaults.standard.integer(forKey: "modemPreset")
|
||||
}
|
||||
set {
|
||||
UserDefaults.standard.set(newValue, forKey: "modemPreset")
|
||||
}
|
||||
}
|
||||
static var firmwareVersion: String {
|
||||
get {
|
||||
UserDefaults.standard.string(forKey: "firmwareVersion") ?? "0.0.0"
|
||||
}
|
||||
set {
|
||||
UserDefaults.standard.set(newValue, forKey: "firmwareVersion")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -582,6 +582,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
nowKnown = true
|
||||
connectedVersion = String(version.dropLast())
|
||||
appState.firmwareVersion = connectedVersion
|
||||
UserDefaults.firmwareVersion = connectedVersion
|
||||
}
|
||||
let supportedVersion = connectedVersion == "0.0.0" || self.minimumVersion.compare(connectedVersion, options: .numeric) == .orderedAscending || minimumVersion.compare(connectedVersion, options: .numeric) == .orderedSame
|
||||
if !supportedVersion {
|
||||
|
|
@ -607,11 +608,14 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
case .adminApp:
|
||||
adminAppPacket(packet: decodedInfo.packet, context: context!)
|
||||
case .replyApp:
|
||||
MeshLogger.log("🕸️ MESH PACKET received for Reply App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")")
|
||||
MeshLogger.log("🕸️ MESH PACKET received for Reply App handling as a text message")
|
||||
textMessageAppPacket(packet: decodedInfo.packet, wantRangeTestPackets: wantRangeTestPackets, connectedNode: (self.connectedPeripheral != nil ? connectedPeripheral.num : 0), context: context!)
|
||||
case .ipTunnelApp:
|
||||
MeshLogger.log("🕸️ MESH PACKET received for IP Tunnel App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")")
|
||||
//MeshLogger.log("🕸️ MESH PACKET received for IP Tunnel App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")")
|
||||
MeshLogger.log("🕸️ MESH PACKET received for IP Tunnel App UNHANDLED UNHANDLED")
|
||||
case .serialApp:
|
||||
MeshLogger.log("🕸️ MESH PACKET received for Serial App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")")
|
||||
//MeshLogger.log("🕸️ MESH PACKET received for Serial App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")")
|
||||
MeshLogger.log("🕸️ MESH PACKET received for Serial App UNHANDLED UNHANDLED")
|
||||
case .storeForwardApp:
|
||||
if wantStoreAndForwardPackets {
|
||||
storeAndForwardPacket(packet: decodedInfo.packet, connectedNodeNum: (self.connectedPeripheral != nil ? connectedPeripheral.num : 0), context: context!)
|
||||
|
|
@ -628,17 +632,23 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
case .telemetryApp:
|
||||
if !invalidVersion { telemetryPacket(packet: decodedInfo.packet, connectedNode: (self.connectedPeripheral != nil ? connectedPeripheral.num : 0), context: context!) }
|
||||
case .textMessageCompressedApp:
|
||||
MeshLogger.log("🕸️ MESH PACKET received for Text Message Compressed App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")")
|
||||
//MeshLogger.log("🕸️ MESH PACKET received for Text Message Compressed App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")")
|
||||
MeshLogger.log("🕸️ MESH PACKET received for Text Message Compressed App UNHANDLED")
|
||||
case .zpsApp:
|
||||
MeshLogger.log("🕸️ MESH PACKET received for ZPS App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")")
|
||||
// MeshLogger.log("🕸️ MESH PACKET received for Zero Positioning System App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")")
|
||||
MeshLogger.log("🕸️ MESH PACKET received for Zero Positioning System App UNHANDLED")
|
||||
case .privateApp:
|
||||
MeshLogger.log("🕸️ MESH PACKET received for Private App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")")
|
||||
//MeshLogger.log("🕸️ MESH PACKET received for Private App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")")
|
||||
MeshLogger.log("🕸️ MESH PACKET received for Private App UNHANDLED UNHANDLED")
|
||||
case .atakForwarder:
|
||||
MeshLogger.log("🕸️ MESH PACKET received for ATAK Forwarder App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")")
|
||||
//MeshLogger.log("🕸️ MESH PACKET received for ATAK Forwarder App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")")
|
||||
MeshLogger.log("🕸️ MESH PACKET received for ATAK Forwarder App UNHANDLED UNHANDLED")
|
||||
case .simulatorApp:
|
||||
MeshLogger.log("🕸️ MESH PACKET received for Simulator App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")")
|
||||
//MeshLogger.log("🕸️ MESH PACKET received for Simulator App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")")
|
||||
MeshLogger.log("🕸️ MESH PACKET received for Simulator App UNHANDLED UNHANDLED")
|
||||
case .audioApp:
|
||||
MeshLogger.log("🕸️ MESH PACKET received for Audio App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")")
|
||||
//MeshLogger.log("🕸️ MESH PACKET received for Audio App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")")
|
||||
MeshLogger.log("🕸️ MESH PACKET received for Audio App UNHANDLED UNHANDLED")
|
||||
case .tracerouteApp:
|
||||
if let routingMessage = try? RouteDiscovery(serializedData: decodedInfo.packet.decoded.payload) {
|
||||
let traceRoute = getTraceRoute(id: Int64(decodedInfo.packet.decoded.requestID), context: context!)
|
||||
|
|
@ -1030,9 +1040,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
} catch {
|
||||
return false
|
||||
}
|
||||
return false
|
||||
|
||||
|
||||
|
||||
var meshPacket = MeshPacket()
|
||||
meshPacket.to = UInt32(destNum)
|
||||
|
|
|
|||
|
|
@ -60,14 +60,10 @@ import CoreLocation
|
|||
self.isStationary = update.isStationary
|
||||
|
||||
var locationAdded: Bool
|
||||
if enableSmartPosition {
|
||||
locationAdded = addLocation(loc)
|
||||
//print("Added Location \(self.count): \(loc)")
|
||||
} else {
|
||||
locationsArray.append(loc)
|
||||
locationAdded = true
|
||||
}
|
||||
if locationAdded {
|
||||
locationAdded = addLocation(loc, smartPostion: enableSmartPosition)
|
||||
if !isRecording && locationAdded {
|
||||
self.count = 1
|
||||
} else if locationAdded && isRecording {
|
||||
self.count += 1
|
||||
}
|
||||
}
|
||||
|
|
@ -84,19 +80,21 @@ import CoreLocation
|
|||
self.updatesStarted = false
|
||||
}
|
||||
|
||||
func addLocation(_ location: CLLocation) -> Bool {
|
||||
let age = -location.timestamp.timeIntervalSinceNow
|
||||
if age > 10 {
|
||||
print("Bad Location \(self.count): Too Old \(age) seconds ago \(location)")
|
||||
return false
|
||||
}
|
||||
if location.horizontalAccuracy < 0 {
|
||||
print("Bad Location \(self.count): Horizontal Accuracy: \(location.horizontalAccuracy) \(location)")
|
||||
return false
|
||||
}
|
||||
if location.horizontalAccuracy > 25 {
|
||||
print("Bad Location \(self.count): Horizontal Accuracy: \(location.horizontalAccuracy) \(location)")
|
||||
return false
|
||||
func addLocation(_ location: CLLocation, smartPostion: Bool) -> Bool {
|
||||
if smartPostion {
|
||||
let age = -location.timestamp.timeIntervalSinceNow
|
||||
if age > 10 {
|
||||
print("Bad Location \(self.count): Too Old \(age) seconds ago \(location)")
|
||||
return false
|
||||
}
|
||||
if location.horizontalAccuracy < 0 {
|
||||
print("Bad Location \(self.count): Horizontal Accuracy: \(location.horizontalAccuracy) \(location)")
|
||||
return false
|
||||
}
|
||||
if location.horizontalAccuracy > 25 {
|
||||
print("Bad Location \(self.count): Horizontal Accuracy: \(location.horizontalAccuracy) \(location)")
|
||||
return false
|
||||
}
|
||||
}
|
||||
if isRecording {
|
||||
if let lastLocation = locationsArray.last {
|
||||
|
|
@ -107,8 +105,10 @@ import CoreLocation
|
|||
elevationGain += gain
|
||||
}
|
||||
}
|
||||
locationsArray.append(location)
|
||||
} else {
|
||||
locationsArray = [location]
|
||||
}
|
||||
locationsArray.append(location)
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -775,7 +775,7 @@ func telemetryPacket(packet: MeshPacket, connectedNode: Int64, context: NSManage
|
|||
func textMessageAppPacket(packet: MeshPacket, wantRangeTestPackets: Bool, connectedNode: Int64, storeForward: Bool = false, context: NSManagedObjectContext) {
|
||||
|
||||
var messageText = String(bytes: packet.decoded.payload, encoding: .utf8)
|
||||
if !wantRangeTestPackets && ((messageText?.starts(with: "seq ")) != nil) {
|
||||
if !wantRangeTestPackets && (String(messageText ?? "seq ").starts(with: "seq ")) {
|
||||
return
|
||||
}
|
||||
var storeForwardBroadcast = false
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ class MqttClientProxyManager {
|
|||
private static let defaultKeepAliveInterval: Int32 = 60
|
||||
weak var delegate: MqttClientProxyManagerDelegate?
|
||||
var mqttClientProxy: CocoaMQTT?
|
||||
var topic = "msh/2/c"
|
||||
var topic = "msh"
|
||||
var debugLog = false
|
||||
func connectFromConfigSettings(node: NodeInfoEntity) {
|
||||
let defaultServerAddress = "mqtt.meshtastic.org"
|
||||
|
|
@ -36,13 +36,16 @@ class MqttClientProxyManager {
|
|||
defaultServerPort = Int(fullHost.components(separatedBy: ":")[1]) ?? (useSsl ? 8883 : 1883)
|
||||
}
|
||||
}
|
||||
let minimumVersion = "2.3.0"
|
||||
let latestVersion = minimumVersion.compare(UserDefaults.firmwareVersion, options: .numeric) == .orderedSame
|
||||
|
||||
if let host = host {
|
||||
let port = defaultServerPort
|
||||
let username = node.mqttConfig?.username
|
||||
let password = node.mqttConfig?.password
|
||||
let root = node.mqttConfig?.root?.count ?? 0 > 0 ? node.mqttConfig?.root : "msh"
|
||||
let prefix = root! + "/2/c"
|
||||
topic = prefix + "/#"
|
||||
let prefix = root!
|
||||
topic = prefix + (latestVersion ? "/2/e" : "/2/c") + "/#"
|
||||
let qos = CocoaMQTTQoS(rawValue: UInt8(1))!
|
||||
connect(host: host, port: port, useSsl: useSsl, username: username, password: password, topic: topic, qos: qos, cleanSession: true)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,6 @@
|
|||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>_XCCurrentVersionName</key>
|
||||
<string>MeshtasticDataModelV 28.xcdatamodel</string>
|
||||
<string>MeshtasticDataModelV 29.xcdatamodel</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="22522" systemVersion="23E5205c" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="22522" systemVersion="23E5211a" 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"/>
|
||||
|
|
@ -230,8 +230,8 @@
|
|||
<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="detection" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="environment" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="detection" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="environment" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="id" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="lastHeard" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="num" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
|
|
@ -239,31 +239,31 @@
|
|||
<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="viaMqtt" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<relationship name="ambientLightingConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="AmbientLightingConfigEntity" inverseName="ambientLightingConfigNode" inverseEntity="AmbientLightingConfigEntity"/>
|
||||
<relationship name="bluetoothConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="BluetoothConfigEntity" inverseName="bluetoothConfigNode" inverseEntity="BluetoothConfigEntity"/>
|
||||
<relationship name="cannedMessageConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="CannedMessageConfigEntity" inverseName="cannedMessagesConfigNode" inverseEntity="CannedMessageConfigEntity"/>
|
||||
<relationship name="detectionSensorConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="DetectionSensorConfigEntity" inverseName="detectionSensorConfigNode" inverseEntity="DetectionSensorConfigEntity"/>
|
||||
<relationship name="deviceConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="DeviceConfigEntity" inverseName="deviceConfigNode" inverseEntity="DeviceConfigEntity"/>
|
||||
<relationship name="displayConfig" optional="YES" maxCount="1" deletionRule="Nullify" 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="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"/>
|
||||
<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="Cascade" destinationEntity="ExternalNotificationConfigEntity" inverseName="externalNotificationConfigNode" inverseEntity="ExternalNotificationConfigEntity"/>
|
||||
<relationship name="loRaConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="LoRaConfigEntity" inverseName="loRaConfigNode" inverseEntity="LoRaConfigEntity"/>
|
||||
<relationship name="metadata" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="DeviceMetadataEntity" inverseName="metadataNode" inverseEntity="DeviceMetadataEntity"/>
|
||||
<relationship name="mqttConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="MQTTConfigEntity" inverseName="mqttConfigNode" inverseEntity="MQTTConfigEntity"/>
|
||||
<relationship name="myInfo" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="MyInfoEntity" inverseName="myInfoNode" inverseEntity="MyInfoEntity"/>
|
||||
<relationship name="networkConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="NetworkConfigEntity" inverseName="networkConfigNode" inverseEntity="NetworkConfigEntity"/>
|
||||
<relationship name="pax" optional="YES" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="PaxCounterEntity" inverseName="paxNode" inverseEntity="PaxCounterEntity"/>
|
||||
<relationship name="paxCounterConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="PaxCounterConfigEntity" inverseName="paxCounterConfigNode" inverseEntity="PaxCounterConfigEntity"/>
|
||||
<relationship name="positionConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="PositionConfigEntity" inverseName="positionConfigNode" inverseEntity="PositionConfigEntity"/>
|
||||
<relationship name="positions" optional="YES" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="PositionEntity" inverseName="nodePosition" inverseEntity="PositionEntity"/>
|
||||
<relationship name="powerConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="PowerConfigEntity" inverseName="powerConfigNode" inverseEntity="PowerConfigEntity"/>
|
||||
<relationship name="rangeTestConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="RangeTestConfigEntity" inverseName="rangeTestConfigNode" inverseEntity="RangeTestConfigEntity"/>
|
||||
<relationship name="rtttlConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="RTTTLConfigEntity" inverseName="rtttlConfigNode" inverseEntity="RTTTLConfigEntity"/>
|
||||
<relationship name="serialConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="SerialConfigEntity" inverseName="serialConfigNode" inverseEntity="SerialConfigEntity"/>
|
||||
<relationship name="storeForwardConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="StoreForwardConfigEntity" inverseName="storeForwardConfigNode" inverseEntity="StoreForwardConfigEntity"/>
|
||||
<relationship name="telemetries" optional="YES" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="TelemetryEntity" inverseName="nodeTelemetry" inverseEntity="TelemetryEntity"/>
|
||||
<relationship name="telemetryConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="TelemetryConfigEntity" inverseName="telemetryConfigNode" inverseEntity="TelemetryConfigEntity"/>
|
||||
<relationship name="traceRoutes" optional="YES" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="TraceRouteEntity" inverseName="node" inverseEntity="TraceRouteEntity"/>
|
||||
<relationship name="user" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="UserEntity" inverseName="userNode" inverseEntity="UserEntity"/>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="num"/>
|
||||
|
|
@ -422,8 +422,8 @@
|
|||
<attribute name="shortName" attributeType="String"/>
|
||||
<attribute name="userId" attributeType="String"/>
|
||||
<attribute name="vip" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<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="receivedMessages" optional="YES" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="MessageEntity" inverseName="toUser" inverseEntity="MessageEntity"/>
|
||||
<relationship name="sentMessages" optional="YES" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="MessageEntity" inverseName="fromUser" inverseEntity="MessageEntity"/>
|
||||
<relationship name="userNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="user" inverseEntity="NodeInfoEntity"/>
|
||||
<fetchedProperty name="adminMessages" optional="YES">
|
||||
<fetchRequest name="fetchedPropertyFetchRequest" entity="MessageEntity" predicateString="(fromUser.num == $FETCH_SOURCE.num) AND isEmoji == false AND admin = true"/>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,449 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="22522" systemVersion="23E5211a" 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="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" optional="YES" attributeType="Boolean" 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" usesScalarValueType="YES"/>
|
||||
<relationship name="myInfoChannel" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MyInfoEntity" inverseName="channels" inverseEntity="MyInfoEntity"/>
|
||||
<fetchedProperty name="allPrivateMessages" optional="YES">
|
||||
<fetchRequest name="fetchedPropertyFetchRequest" entity="MessageEntity" predicateString="channel == $FETCH_SOURCE.index && toUser == nil AND isEmoji == false"/>
|
||||
</fetchedProperty>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="index"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="DetectionSensorConfigEntity" representedClassName="DetectionSensorConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="detectionTriggeredHigh" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<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="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="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"/>
|
||||
<relationship name="deviceConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="deviceConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="DeviceHardwareEntity" representedClassName="DeviceHardwareEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="activelySupported" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="architecture" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="displayName" optional="YES" attributeType="String"/>
|
||||
<attribute name="hwModel" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="hwModelSlug" optional="YES" attributeType="String"/>
|
||||
<attribute name="platformioTarget" optional="YES" attributeType="String"/>
|
||||
</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="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"/>
|
||||
<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"/>
|
||||
<fetchedProperty name="fetchedProperty" optional="YES">
|
||||
<fetchRequest name="fetchedPropertyFetchRequest" entity="ExternalNotificationConfigEntity"/>
|
||||
</fetchedProperty>
|
||||
</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="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="portNum" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<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="receivedTimestamp" optional="YES" attributeType="Integer 32" defaultValueString="0" 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"/>
|
||||
<fetchedProperty name="tapbacks" optional="YES">
|
||||
<fetchRequest name="fetchedPropertyFetchRequest" entity="MessageEntity" predicateString="replyID == $FETCH_SOURCE.messageId AND isEmoji == true"/>
|
||||
</fetchedProperty>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="messageId"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="MQTTConfigEntity" representedClassName="MQTTConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="address" optional="YES" attributeType="String" maxValueString="30"/>
|
||||
<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="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="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"/>
|
||||
<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"/>
|
||||
<fetchedProperty name="allMessages" optional="YES">
|
||||
<fetchRequest name="fetchedPropertyFetchRequest" entity="MessageEntity" predicateString="toUser == nil"/>
|
||||
</fetchedProperty>
|
||||
<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="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="hopsAway" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="id" attributeType="Integer 64" defaultValueString="0" 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="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="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="enabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="paxcounterUpdateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" 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="enabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<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="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"/>
|
||||
<relationship name="telemetryConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="telemetryConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="TelemetryEntity" representedClassName="TelemetryEntity" syncable="YES" codeGenerationType="class">
|
||||
<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="metricsType" optional="YES" attributeType="Integer 32" defaultValueString="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="temperature" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="time" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="voltage" 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="altitude" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="hasPositions" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="id" optional="YES" attributeType="Integer 64" defaultValueString="0" 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="num" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="response" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="route" optional="YES" attributeType="Transformable" customClassName="[UInt32]"/>
|
||||
<attribute name="routeText" optional="YES" attributeType="String"/>
|
||||
<attribute name="time" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<relationship name="hops" optional="YES" toMany="YES" deletionRule="Nullify" 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="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="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="hwModel" attributeType="String"/>
|
||||
<attribute name="isLicensed" optional="YES" attributeType="Boolean" defaultValueString="NO" 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="num" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="role" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="shortName" attributeType="String"/>
|
||||
<attribute name="userId" attributeType="String"/>
|
||||
<attribute name="vip" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<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"/>
|
||||
<fetchedProperty name="adminMessages" optional="YES">
|
||||
<fetchRequest name="fetchedPropertyFetchRequest" entity="MessageEntity" predicateString="(fromUser.num == $FETCH_SOURCE.num) AND isEmoji == false AND admin = true"/>
|
||||
</fetchedProperty>
|
||||
<fetchedProperty name="allMessages" optional="YES">
|
||||
<fetchRequest name="fetchedPropertyFetchRequest" entity="MessageEntity" predicateString="((toUser.num == $FETCH_SOURCE.num) OR (fromUser.num == $FETCH_SOURCE.num)) AND toUser != nil AND fromUser != nil AND isEmoji == false AND admin = false AND portNum != 10 "/>
|
||||
</fetchedProperty>
|
||||
<fetchedProperty name="detectionSensorMessages" optional="YES">
|
||||
<fetchRequest name="fetchedPropertyFetchRequest" entity="MessageEntity" predicateString="(fromUser.num == $FETCH_SOURCE.num) AND portNum = 10"/>
|
||||
</fetchedProperty>
|
||||
</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"/>
|
||||
</entity>
|
||||
</model>
|
||||
|
|
@ -147,11 +147,24 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext)
|
|||
newNode.channel = Int32(packet.channel)
|
||||
if let nodeInfoMessage = try? NodeInfo(serializedData: packet.decoded.payload) {
|
||||
newNode.channel = Int32(nodeInfoMessage.channel)
|
||||
print(packet.channel)
|
||||
print("Channel From Message\(nodeInfoMessage.channel)")
|
||||
newNode.hopsAway = Int32(truncatingIfNeeded: nodeInfoMessage.hopsAway)
|
||||
}
|
||||
if let newUserMessage = try? User(serializedData: packet.decoded.payload) {
|
||||
let newUser = UserEntity(context: context)
|
||||
|
||||
if newUserMessage.id.isEmpty {
|
||||
let newUser = UserEntity(context: context)
|
||||
newUser.num = Int64(packet.from)
|
||||
let userId = String(format:"%2X", packet.from)
|
||||
newUser.userId = "!\(userId)"
|
||||
let last4 = String(userId.suffix(4))
|
||||
newUser.longName = "Meshtastic \(last4)"
|
||||
newUser.shortName = last4
|
||||
newUser.hwModel = "UNSET"
|
||||
newNode.user = newUser
|
||||
|
||||
} else {
|
||||
|
||||
let newUser = UserEntity(context: context)
|
||||
newUser.userId = newUserMessage.id
|
||||
newUser.num = Int64(packet.from)
|
||||
newUser.longName = newUserMessage.longName
|
||||
|
|
@ -159,6 +172,17 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext)
|
|||
newUser.role = Int32(newUserMessage.role.rawValue)
|
||||
newUser.hwModel = String(describing: newUserMessage.hwModel).uppercased()
|
||||
newNode.user = newUser
|
||||
}
|
||||
} else {
|
||||
let newUser = UserEntity(context: context)
|
||||
newUser.num = Int64(packet.from)
|
||||
let userId = String(format:"%2X", packet.from)
|
||||
newUser.userId = "!\(userId)"
|
||||
let last4 = String(userId.suffix(4))
|
||||
newUser.longName = "Meshtastic \(last4)"
|
||||
newUser.shortName = last4
|
||||
newUser.hwModel = "UNSET"
|
||||
newNode.user = newUser
|
||||
}
|
||||
|
||||
if newNode.user == nil {
|
||||
|
|
@ -177,7 +201,6 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext)
|
|||
print("💥 Error Inserting New Core Data MyInfoEntity: \(nsError)")
|
||||
}
|
||||
newNode.myInfo = myInfoEntity
|
||||
//newNode.objectWillChange.send()
|
||||
|
||||
} else {
|
||||
// Update an existing node
|
||||
|
|
@ -193,6 +216,7 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext)
|
|||
|
||||
if let nodeInfoMessage = try? NodeInfo(serializedData: packet.decoded.payload) {
|
||||
fetchedNode[0].channel = Int32(nodeInfoMessage.channel)
|
||||
fetchedNode[0].hopsAway = Int32(truncatingIfNeeded: nodeInfoMessage.hopsAway)
|
||||
if nodeInfoMessage.hasDeviceMetrics {
|
||||
let telemetry = TelemetryEntity(context: context)
|
||||
telemetry.batteryLevel = Int32(nodeInfoMessage.deviceMetrics.batteryLevel)
|
||||
|
|
@ -211,20 +235,19 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext)
|
|||
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()
|
||||
} else {
|
||||
if (fetchedNode[0].user == nil) {
|
||||
let newUser = UserEntity(context: context)
|
||||
newUser.num = Int64(nodeInfoMessage.num)
|
||||
let userId = String(format:"%2X", nodeInfoMessage.num)
|
||||
newUser.userId = "!\(userId)"
|
||||
let last4 = String(userId.suffix(4))
|
||||
newUser.longName = "Meshtastic \(last4)"
|
||||
newUser.shortName = last4
|
||||
newUser.hwModel = "UNSET"
|
||||
fetchedNode[0].user! = newUser
|
||||
}
|
||||
}
|
||||
}
|
||||
if (fetchedNode[0].user == nil) {
|
||||
let newUser = UserEntity(context: context)
|
||||
newUser.num = Int64(packet.from)
|
||||
let userId = String(format:"%2X", packet.from)
|
||||
newUser.userId = "!\(userId)"
|
||||
let last4 = String(userId.suffix(4))
|
||||
newUser.longName = "Meshtastic \(last4)"
|
||||
newUser.shortName = last4
|
||||
newUser.hwModel = "UNSET"
|
||||
fetchedNode[0].user! = newUser
|
||||
}
|
||||
do {
|
||||
try context.save()
|
||||
print("💾 Updated NodeInfo from Node Info App Packet For: \(fetchedNode[0].num)")
|
||||
|
|
|
|||
|
|
@ -254,6 +254,20 @@ struct NodeInfoLite {
|
|||
set {_uniqueStorage()._channel = newValue}
|
||||
}
|
||||
|
||||
///
|
||||
/// True if we witnessed the node over MQTT instead of LoRA transport
|
||||
var viaMqtt: Bool {
|
||||
get {return _storage._viaMqtt}
|
||||
set {_uniqueStorage()._viaMqtt = newValue}
|
||||
}
|
||||
|
||||
///
|
||||
/// Number of hops away from us this node is (0 if adjacent)
|
||||
var hopsAway: UInt32 {
|
||||
get {return _storage._hopsAway}
|
||||
set {_uniqueStorage()._hopsAway = newValue}
|
||||
}
|
||||
|
||||
var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
init() {}
|
||||
|
|
@ -583,6 +597,8 @@ extension NodeInfoLite: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat
|
|||
5: .standard(proto: "last_heard"),
|
||||
6: .standard(proto: "device_metrics"),
|
||||
7: .same(proto: "channel"),
|
||||
8: .standard(proto: "via_mqtt"),
|
||||
9: .standard(proto: "hops_away"),
|
||||
]
|
||||
|
||||
fileprivate class _StorageClass {
|
||||
|
|
@ -593,6 +609,8 @@ extension NodeInfoLite: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat
|
|||
var _lastHeard: UInt32 = 0
|
||||
var _deviceMetrics: DeviceMetrics? = nil
|
||||
var _channel: UInt32 = 0
|
||||
var _viaMqtt: Bool = false
|
||||
var _hopsAway: UInt32 = 0
|
||||
|
||||
static let defaultInstance = _StorageClass()
|
||||
|
||||
|
|
@ -606,6 +624,8 @@ extension NodeInfoLite: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat
|
|||
_lastHeard = source._lastHeard
|
||||
_deviceMetrics = source._deviceMetrics
|
||||
_channel = source._channel
|
||||
_viaMqtt = source._viaMqtt
|
||||
_hopsAway = source._hopsAway
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -631,6 +651,8 @@ extension NodeInfoLite: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat
|
|||
case 5: try { try decoder.decodeSingularFixed32Field(value: &_storage._lastHeard) }()
|
||||
case 6: try { try decoder.decodeSingularMessageField(value: &_storage._deviceMetrics) }()
|
||||
case 7: try { try decoder.decodeSingularUInt32Field(value: &_storage._channel) }()
|
||||
case 8: try { try decoder.decodeSingularBoolField(value: &_storage._viaMqtt) }()
|
||||
case 9: try { try decoder.decodeSingularUInt32Field(value: &_storage._hopsAway) }()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
|
@ -664,6 +686,12 @@ extension NodeInfoLite: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat
|
|||
if _storage._channel != 0 {
|
||||
try visitor.visitSingularUInt32Field(value: _storage._channel, fieldNumber: 7)
|
||||
}
|
||||
if _storage._viaMqtt != false {
|
||||
try visitor.visitSingularBoolField(value: _storage._viaMqtt, fieldNumber: 8)
|
||||
}
|
||||
if _storage._hopsAway != 0 {
|
||||
try visitor.visitSingularUInt32Field(value: _storage._hopsAway, fieldNumber: 9)
|
||||
}
|
||||
}
|
||||
try unknownFields.traverse(visitor: &visitor)
|
||||
}
|
||||
|
|
@ -680,6 +708,8 @@ extension NodeInfoLite: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat
|
|||
if _storage._lastHeard != rhs_storage._lastHeard {return false}
|
||||
if _storage._deviceMetrics != rhs_storage._deviceMetrics {return false}
|
||||
if _storage._channel != rhs_storage._channel {return false}
|
||||
if _storage._viaMqtt != rhs_storage._viaMqtt {return false}
|
||||
if _storage._hopsAway != rhs_storage._hopsAway {return false}
|
||||
return true
|
||||
}
|
||||
if !storagesAreEqual {return false}
|
||||
|
|
|
|||
|
|
@ -1434,8 +1434,7 @@ struct MeshPacket {
|
|||
}
|
||||
|
||||
///
|
||||
/// The (immediatSee Priority description for more details.y should be fixed32 instead, this encoding only
|
||||
/// hurts the ble link though.
|
||||
/// The (immediate) destination for this packet
|
||||
var to: UInt32 {
|
||||
get {return _storage._to}
|
||||
set {_uniqueStorage()._to = newValue}
|
||||
|
|
@ -1566,6 +1565,14 @@ struct MeshPacket {
|
|||
set {_uniqueStorage()._viaMqtt = newValue}
|
||||
}
|
||||
|
||||
///
|
||||
/// Hop limit with which the original packet started. Sent via LoRa using three bits in the unencrypted header.
|
||||
/// When receiving a packet, the difference between hop_start and hop_limit gives how many hops it traveled.
|
||||
var hopStart: UInt32 {
|
||||
get {return _storage._hopStart}
|
||||
set {_uniqueStorage()._hopStart = newValue}
|
||||
}
|
||||
|
||||
var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
enum OneOf_PayloadVariant: Equatable {
|
||||
|
|
@ -1779,62 +1786,86 @@ struct NodeInfo {
|
|||
|
||||
///
|
||||
/// The node number
|
||||
var num: UInt32 = 0
|
||||
var num: UInt32 {
|
||||
get {return _storage._num}
|
||||
set {_uniqueStorage()._num = newValue}
|
||||
}
|
||||
|
||||
///
|
||||
/// The user info for this node
|
||||
var user: User {
|
||||
get {return _user ?? User()}
|
||||
set {_user = newValue}
|
||||
get {return _storage._user ?? User()}
|
||||
set {_uniqueStorage()._user = newValue}
|
||||
}
|
||||
/// Returns true if `user` has been explicitly set.
|
||||
var hasUser: Bool {return self._user != nil}
|
||||
var hasUser: Bool {return _storage._user != nil}
|
||||
/// Clears the value of `user`. Subsequent reads from it will return its default value.
|
||||
mutating func clearUser() {self._user = nil}
|
||||
mutating func clearUser() {_uniqueStorage()._user = nil}
|
||||
|
||||
///
|
||||
/// This position data. Note: before 1.2.14 we would also store the last time we've heard from this node in position.time, that is no longer true.
|
||||
/// Position.time now indicates the last time we received a POSITION from that node.
|
||||
var position: Position {
|
||||
get {return _position ?? Position()}
|
||||
set {_position = newValue}
|
||||
get {return _storage._position ?? Position()}
|
||||
set {_uniqueStorage()._position = newValue}
|
||||
}
|
||||
/// Returns true if `position` has been explicitly set.
|
||||
var hasPosition: Bool {return self._position != nil}
|
||||
var hasPosition: Bool {return _storage._position != nil}
|
||||
/// Clears the value of `position`. Subsequent reads from it will return its default value.
|
||||
mutating func clearPosition() {self._position = nil}
|
||||
mutating func clearPosition() {_uniqueStorage()._position = nil}
|
||||
|
||||
///
|
||||
/// Returns the Signal-to-noise ratio (SNR) of the last received message,
|
||||
/// as measured by the receiver. Return SNR of the last received message in dB
|
||||
var snr: Float = 0
|
||||
var snr: Float {
|
||||
get {return _storage._snr}
|
||||
set {_uniqueStorage()._snr = newValue}
|
||||
}
|
||||
|
||||
///
|
||||
/// Set to indicate the last time we received a packet from this node
|
||||
var lastHeard: UInt32 = 0
|
||||
var lastHeard: UInt32 {
|
||||
get {return _storage._lastHeard}
|
||||
set {_uniqueStorage()._lastHeard = newValue}
|
||||
}
|
||||
|
||||
///
|
||||
/// The latest device metrics for the node.
|
||||
var deviceMetrics: DeviceMetrics {
|
||||
get {return _deviceMetrics ?? DeviceMetrics()}
|
||||
set {_deviceMetrics = newValue}
|
||||
get {return _storage._deviceMetrics ?? DeviceMetrics()}
|
||||
set {_uniqueStorage()._deviceMetrics = newValue}
|
||||
}
|
||||
/// Returns true if `deviceMetrics` has been explicitly set.
|
||||
var hasDeviceMetrics: Bool {return self._deviceMetrics != nil}
|
||||
var hasDeviceMetrics: Bool {return _storage._deviceMetrics != nil}
|
||||
/// Clears the value of `deviceMetrics`. Subsequent reads from it will return its default value.
|
||||
mutating func clearDeviceMetrics() {self._deviceMetrics = nil}
|
||||
mutating func clearDeviceMetrics() {_uniqueStorage()._deviceMetrics = nil}
|
||||
|
||||
///
|
||||
/// local channel index we heard that node on. Only populated if its not the default channel.
|
||||
var channel: UInt32 = 0
|
||||
var channel: UInt32 {
|
||||
get {return _storage._channel}
|
||||
set {_uniqueStorage()._channel = newValue}
|
||||
}
|
||||
|
||||
///
|
||||
/// True if we witnessed the node over MQTT instead of LoRA transport
|
||||
var viaMqtt: Bool {
|
||||
get {return _storage._viaMqtt}
|
||||
set {_uniqueStorage()._viaMqtt = newValue}
|
||||
}
|
||||
|
||||
///
|
||||
/// Number of hops away from us this node is (0 if adjacent)
|
||||
var hopsAway: UInt32 {
|
||||
get {return _storage._hopsAway}
|
||||
set {_uniqueStorage()._hopsAway = newValue}
|
||||
}
|
||||
|
||||
var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
init() {}
|
||||
|
||||
fileprivate var _user: User? = nil
|
||||
fileprivate var _position: Position? = nil
|
||||
fileprivate var _deviceMetrics: DeviceMetrics? = nil
|
||||
fileprivate var _storage = _StorageClass.defaultInstance
|
||||
}
|
||||
|
||||
///
|
||||
|
|
@ -3369,6 +3400,7 @@ extension MeshPacket: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio
|
|||
12: .standard(proto: "rx_rssi"),
|
||||
13: .same(proto: "delayed"),
|
||||
14: .standard(proto: "via_mqtt"),
|
||||
15: .standard(proto: "hop_start"),
|
||||
]
|
||||
|
||||
fileprivate class _StorageClass {
|
||||
|
|
@ -3385,6 +3417,7 @@ extension MeshPacket: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio
|
|||
var _rxRssi: Int32 = 0
|
||||
var _delayed: MeshPacket.Delayed = .noDelay
|
||||
var _viaMqtt: Bool = false
|
||||
var _hopStart: UInt32 = 0
|
||||
|
||||
static let defaultInstance = _StorageClass()
|
||||
|
||||
|
|
@ -3404,6 +3437,7 @@ extension MeshPacket: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio
|
|||
_rxRssi = source._rxRssi
|
||||
_delayed = source._delayed
|
||||
_viaMqtt = source._viaMqtt
|
||||
_hopStart = source._hopStart
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -3455,6 +3489,7 @@ extension MeshPacket: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio
|
|||
case 12: try { try decoder.decodeSingularInt32Field(value: &_storage._rxRssi) }()
|
||||
case 13: try { try decoder.decodeSingularEnumField(value: &_storage._delayed) }()
|
||||
case 14: try { try decoder.decodeSingularBoolField(value: &_storage._viaMqtt) }()
|
||||
case 15: try { try decoder.decodeSingularUInt32Field(value: &_storage._hopStart) }()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
|
@ -3514,6 +3549,9 @@ extension MeshPacket: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio
|
|||
if _storage._viaMqtt != false {
|
||||
try visitor.visitSingularBoolField(value: _storage._viaMqtt, fieldNumber: 14)
|
||||
}
|
||||
if _storage._hopStart != 0 {
|
||||
try visitor.visitSingularUInt32Field(value: _storage._hopStart, fieldNumber: 15)
|
||||
}
|
||||
}
|
||||
try unknownFields.traverse(visitor: &visitor)
|
||||
}
|
||||
|
|
@ -3536,6 +3574,7 @@ extension MeshPacket: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio
|
|||
if _storage._rxRssi != rhs_storage._rxRssi {return false}
|
||||
if _storage._delayed != rhs_storage._delayed {return false}
|
||||
if _storage._viaMqtt != rhs_storage._viaMqtt {return false}
|
||||
if _storage._hopStart != rhs_storage._hopStart {return false}
|
||||
return true
|
||||
}
|
||||
if !storagesAreEqual {return false}
|
||||
|
|
@ -3575,63 +3614,123 @@ extension NodeInfo: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB
|
|||
5: .standard(proto: "last_heard"),
|
||||
6: .standard(proto: "device_metrics"),
|
||||
7: .same(proto: "channel"),
|
||||
8: .standard(proto: "via_mqtt"),
|
||||
9: .standard(proto: "hops_away"),
|
||||
]
|
||||
|
||||
fileprivate class _StorageClass {
|
||||
var _num: UInt32 = 0
|
||||
var _user: User? = nil
|
||||
var _position: Position? = nil
|
||||
var _snr: Float = 0
|
||||
var _lastHeard: UInt32 = 0
|
||||
var _deviceMetrics: DeviceMetrics? = nil
|
||||
var _channel: UInt32 = 0
|
||||
var _viaMqtt: Bool = false
|
||||
var _hopsAway: UInt32 = 0
|
||||
|
||||
static let defaultInstance = _StorageClass()
|
||||
|
||||
private init() {}
|
||||
|
||||
init(copying source: _StorageClass) {
|
||||
_num = source._num
|
||||
_user = source._user
|
||||
_position = source._position
|
||||
_snr = source._snr
|
||||
_lastHeard = source._lastHeard
|
||||
_deviceMetrics = source._deviceMetrics
|
||||
_channel = source._channel
|
||||
_viaMqtt = source._viaMqtt
|
||||
_hopsAway = source._hopsAway
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate mutating func _uniqueStorage() -> _StorageClass {
|
||||
if !isKnownUniquelyReferenced(&_storage) {
|
||||
_storage = _StorageClass(copying: _storage)
|
||||
}
|
||||
return _storage
|
||||
}
|
||||
|
||||
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.num) }()
|
||||
case 2: try { try decoder.decodeSingularMessageField(value: &self._user) }()
|
||||
case 3: try { try decoder.decodeSingularMessageField(value: &self._position) }()
|
||||
case 4: try { try decoder.decodeSingularFloatField(value: &self.snr) }()
|
||||
case 5: try { try decoder.decodeSingularFixed32Field(value: &self.lastHeard) }()
|
||||
case 6: try { try decoder.decodeSingularMessageField(value: &self._deviceMetrics) }()
|
||||
case 7: try { try decoder.decodeSingularUInt32Field(value: &self.channel) }()
|
||||
default: break
|
||||
_ = _uniqueStorage()
|
||||
try withExtendedLifetime(_storage) { (_storage: _StorageClass) in
|
||||
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: &_storage._num) }()
|
||||
case 2: try { try decoder.decodeSingularMessageField(value: &_storage._user) }()
|
||||
case 3: try { try decoder.decodeSingularMessageField(value: &_storage._position) }()
|
||||
case 4: try { try decoder.decodeSingularFloatField(value: &_storage._snr) }()
|
||||
case 5: try { try decoder.decodeSingularFixed32Field(value: &_storage._lastHeard) }()
|
||||
case 6: try { try decoder.decodeSingularMessageField(value: &_storage._deviceMetrics) }()
|
||||
case 7: try { try decoder.decodeSingularUInt32Field(value: &_storage._channel) }()
|
||||
case 8: try { try decoder.decodeSingularBoolField(value: &_storage._viaMqtt) }()
|
||||
case 9: try { try decoder.decodeSingularUInt32Field(value: &_storage._hopsAway) }()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.num != 0 {
|
||||
try visitor.visitSingularUInt32Field(value: self.num, fieldNumber: 1)
|
||||
}
|
||||
try { if let v = self._user {
|
||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 2)
|
||||
} }()
|
||||
try { if let v = self._position {
|
||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 3)
|
||||
} }()
|
||||
if self.snr != 0 {
|
||||
try visitor.visitSingularFloatField(value: self.snr, fieldNumber: 4)
|
||||
}
|
||||
if self.lastHeard != 0 {
|
||||
try visitor.visitSingularFixed32Field(value: self.lastHeard, fieldNumber: 5)
|
||||
}
|
||||
try { if let v = self._deviceMetrics {
|
||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 6)
|
||||
} }()
|
||||
if self.channel != 0 {
|
||||
try visitor.visitSingularUInt32Field(value: self.channel, fieldNumber: 7)
|
||||
try withExtendedLifetime(_storage) { (_storage: _StorageClass) in
|
||||
// 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 _storage._num != 0 {
|
||||
try visitor.visitSingularUInt32Field(value: _storage._num, fieldNumber: 1)
|
||||
}
|
||||
try { if let v = _storage._user {
|
||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 2)
|
||||
} }()
|
||||
try { if let v = _storage._position {
|
||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 3)
|
||||
} }()
|
||||
if _storage._snr != 0 {
|
||||
try visitor.visitSingularFloatField(value: _storage._snr, fieldNumber: 4)
|
||||
}
|
||||
if _storage._lastHeard != 0 {
|
||||
try visitor.visitSingularFixed32Field(value: _storage._lastHeard, fieldNumber: 5)
|
||||
}
|
||||
try { if let v = _storage._deviceMetrics {
|
||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 6)
|
||||
} }()
|
||||
if _storage._channel != 0 {
|
||||
try visitor.visitSingularUInt32Field(value: _storage._channel, fieldNumber: 7)
|
||||
}
|
||||
if _storage._viaMqtt != false {
|
||||
try visitor.visitSingularBoolField(value: _storage._viaMqtt, fieldNumber: 8)
|
||||
}
|
||||
if _storage._hopsAway != 0 {
|
||||
try visitor.visitSingularUInt32Field(value: _storage._hopsAway, fieldNumber: 9)
|
||||
}
|
||||
}
|
||||
try unknownFields.traverse(visitor: &visitor)
|
||||
}
|
||||
|
||||
static func ==(lhs: NodeInfo, rhs: NodeInfo) -> Bool {
|
||||
if lhs.num != rhs.num {return false}
|
||||
if lhs._user != rhs._user {return false}
|
||||
if lhs._position != rhs._position {return false}
|
||||
if lhs.snr != rhs.snr {return false}
|
||||
if lhs.lastHeard != rhs.lastHeard {return false}
|
||||
if lhs._deviceMetrics != rhs._deviceMetrics {return false}
|
||||
if lhs.channel != rhs.channel {return false}
|
||||
if lhs._storage !== rhs._storage {
|
||||
let storagesAreEqual: Bool = withExtendedLifetime((lhs._storage, rhs._storage)) { (_args: (_StorageClass, _StorageClass)) in
|
||||
let _storage = _args.0
|
||||
let rhs_storage = _args.1
|
||||
if _storage._num != rhs_storage._num {return false}
|
||||
if _storage._user != rhs_storage._user {return false}
|
||||
if _storage._position != rhs_storage._position {return false}
|
||||
if _storage._snr != rhs_storage._snr {return false}
|
||||
if _storage._lastHeard != rhs_storage._lastHeard {return false}
|
||||
if _storage._deviceMetrics != rhs_storage._deviceMetrics {return false}
|
||||
if _storage._channel != rhs_storage._channel {return false}
|
||||
if _storage._viaMqtt != rhs_storage._viaMqtt {return false}
|
||||
if _storage._hopsAway != rhs_storage._hopsAway {return false}
|
||||
return true
|
||||
}
|
||||
if !storagesAreEqual {return false}
|
||||
}
|
||||
if lhs.unknownFields != rhs.unknownFields {return false}
|
||||
return true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,25 +58,25 @@ enum PortNum: SwiftProtobuf.Enum {
|
|||
|
||||
///
|
||||
/// The built-in position messaging app.
|
||||
/// Payload is a [Position](/docs/developers/protobufs/api#position) message
|
||||
/// Payload is a Position message.
|
||||
/// ENCODING: Protobuf
|
||||
case positionApp // = 3
|
||||
|
||||
///
|
||||
/// The built-in user info app.
|
||||
/// Payload is a [User](/docs/developers/protobufs/api#user) message
|
||||
/// Payload is a User message.
|
||||
/// ENCODING: Protobuf
|
||||
case nodeinfoApp // = 4
|
||||
|
||||
///
|
||||
/// Protocol control packets for mesh protocol use.
|
||||
/// Payload is a [Routing](/docs/developers/protobufs/api#routing) message
|
||||
/// Payload is a Routing message.
|
||||
/// ENCODING: Protobuf
|
||||
case routingApp // = 5
|
||||
|
||||
///
|
||||
/// Admin control packets.
|
||||
/// Payload is a [AdminMessage](/docs/developers/protobufs/api#adminmessage) message
|
||||
/// Payload is a AdminMessage message.
|
||||
/// ENCODING: Protobuf
|
||||
case adminApp // = 6
|
||||
|
||||
|
|
@ -90,7 +90,7 @@ enum PortNum: SwiftProtobuf.Enum {
|
|||
|
||||
///
|
||||
/// Waypoint payloads.
|
||||
/// Payload is a [Waypoint](/docs/developers/protobufs/api#waypoint) message
|
||||
/// Payload is a Waypoint message.
|
||||
/// ENCODING: Protobuf
|
||||
case waypointApp // = 8
|
||||
|
||||
|
|
|
|||
|
|
@ -84,6 +84,10 @@ enum TelemetrySensorType: SwiftProtobuf.Enum {
|
|||
///
|
||||
/// INA3221 3 Channel Voltage / Current Sensor
|
||||
case ina3221 // = 14
|
||||
|
||||
///
|
||||
/// BMP085/BMP180 High accuracy temperature and pressure (older Version of BMP280)
|
||||
case bmp085 // = 15
|
||||
case UNRECOGNIZED(Int)
|
||||
|
||||
init() {
|
||||
|
|
@ -107,6 +111,7 @@ enum TelemetrySensorType: SwiftProtobuf.Enum {
|
|||
case 12: self = .sht31
|
||||
case 13: self = .pmsa003I
|
||||
case 14: self = .ina3221
|
||||
case 15: self = .bmp085
|
||||
default: self = .UNRECOGNIZED(rawValue)
|
||||
}
|
||||
}
|
||||
|
|
@ -128,6 +133,7 @@ enum TelemetrySensorType: SwiftProtobuf.Enum {
|
|||
case .sht31: return 12
|
||||
case .pmsa003I: return 13
|
||||
case .ina3221: return 14
|
||||
case .bmp085: return 15
|
||||
case .UNRECOGNIZED(let i): return i
|
||||
}
|
||||
}
|
||||
|
|
@ -154,6 +160,7 @@ extension TelemetrySensorType: CaseIterable {
|
|||
.sht31,
|
||||
.pmsa003I,
|
||||
.ina3221,
|
||||
.bmp085,
|
||||
]
|
||||
}
|
||||
|
||||
|
|
@ -450,6 +457,7 @@ extension TelemetrySensorType: SwiftProtobuf._ProtoNameProviding {
|
|||
12: .same(proto: "SHT31"),
|
||||
13: .same(proto: "PMSA003I"),
|
||||
14: .same(proto: "INA3221"),
|
||||
15: .same(proto: "BMP085"),
|
||||
]
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,12 +17,19 @@ struct UserList: View {
|
|||
@Environment(\.managedObjectContext) var context
|
||||
@EnvironmentObject var bleManager: BLEManager
|
||||
@State private var searchText = ""
|
||||
|
||||
var usersQuery: Binding<String> {
|
||||
Binding {
|
||||
searchText
|
||||
} set: { newValue in
|
||||
searchText = newValue
|
||||
users.nsPredicate = newValue.isEmpty ? nil : NSPredicate(format: "longName CONTAINS[c] %@ OR shortName CONTAINS[c] %@", newValue, newValue)
|
||||
/// Case Insensitive Search Text Predicates
|
||||
let searchPredicates = ["userId", "hwModel", "longName", "shortName"].map { property in
|
||||
return NSPredicate(format: "%K CONTAINS[c] %@", property, searchText)
|
||||
}
|
||||
/// Create a compound predicate using each text search predicate as an OR
|
||||
let textSearchPredicate = NSCompoundPredicate(type: .or, subpredicates: searchPredicates)
|
||||
users.nsPredicate = newValue.isEmpty ? nil : textSearchPredicate
|
||||
}
|
||||
}
|
||||
@FetchRequest(
|
||||
|
|
@ -48,94 +55,94 @@ struct UserList: View {
|
|||
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(destination: UserMessageList(user: 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))))
|
||||
|
||||
VStack(alignment: .leading){
|
||||
HStack{
|
||||
Text(user.longName ?? "unknown".localized)
|
||||
.font(.headline)
|
||||
Spacer()
|
||||
if user.vip {
|
||||
Image(systemName: "star.fill")
|
||||
.foregroundColor(.yellow)
|
||||
NavigationLink(destination: UserMessageList(user: 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))))
|
||||
|
||||
VStack(alignment: .leading){
|
||||
HStack{
|
||||
Text(user.longName ?? "unknown".localized)
|
||||
.font(.headline)
|
||||
Spacer()
|
||||
if user.vip {
|
||||
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 {
|
||||
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))
|
||||
HStack(alignment: .top) {
|
||||
Text("\(mostRecent != nil ? mostRecent!.messagePayload! : " ")")
|
||||
.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 {
|
||||
user.vip = !user.vip
|
||||
do {
|
||||
try context.save()
|
||||
} catch {
|
||||
context.rollback()
|
||||
print("💥 Save User VIP Error")
|
||||
}
|
||||
} label: {
|
||||
Label(user.vip ? "Un-Favorite" : "Favorite", systemImage: user.vip ? "star.slash.fill" : "star.fill")
|
||||
}
|
||||
Button {
|
||||
user.mute = !user.mute
|
||||
do {
|
||||
try context.save()
|
||||
} catch {
|
||||
context.rollback()
|
||||
print("💥 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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(height: 62)
|
||||
.contextMenu {
|
||||
Button {
|
||||
user.vip = !user.vip
|
||||
do {
|
||||
try context.save()
|
||||
} catch {
|
||||
context.rollback()
|
||||
print("💥 Save User VIP Error")
|
||||
}
|
||||
} label: {
|
||||
Label(user.vip ? "Un-Favorite" : "Favorite", systemImage: user.vip ? "star.slash.fill" : "star.fill")
|
||||
}
|
||||
Button {
|
||||
user.mute = !user.mute
|
||||
do {
|
||||
try context.save()
|
||||
} catch {
|
||||
context.rollback()
|
||||
print("💥 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
|
||||
) {
|
||||
.confirmationDialog(
|
||||
"This conversation will be deleted.",
|
||||
isPresented: $isPresentingDeleteUserMessagesConfirm,
|
||||
titleVisibility: .visible
|
||||
) {
|
||||
Button(role: .destructive) {
|
||||
deleteUserMessages(user: userSelection!, context: context)
|
||||
context.refresh(node!.user!, mergeChanges: true)
|
||||
|
|
@ -149,7 +156,9 @@ struct UserList: View {
|
|||
}
|
||||
.listStyle(.plain)
|
||||
.navigationTitle(String.localizedStringWithFormat("contacts %@".localized, String(users.count == 0 ? 0 : users.count - 1)))
|
||||
.searchable(text: usersQuery, prompt: "Find a contact")
|
||||
.searchable(text: usersQuery, placement: users.count > 10 ? .navigationBarDrawer(displayMode: .always) : .automatic, prompt: "Find a contact")
|
||||
.disableAutocorrection(true)
|
||||
.scrollDismissesKeyboard(.immediately)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -107,6 +107,7 @@ struct NodeMapSwiftUI: View {
|
|||
if radius > 0.0 {
|
||||
MapCircle(center: position.coordinate, radius: radius)
|
||||
.foregroundStyle(Color(nodeColor).opacity(0.25))
|
||||
.stroke(.white, lineWidth: 2)
|
||||
}
|
||||
}
|
||||
Annotation(position.latest ? node.user?.shortName ?? "?": "", coordinate: position.coordinate) {
|
||||
|
|
|
|||
|
|
@ -130,6 +130,19 @@ struct PositionPopover: View {
|
|||
.frame(width: 35)
|
||||
}
|
||||
.padding(.bottom, 5)
|
||||
if position.nodePosition?.viaMqtt ?? false {
|
||||
|
||||
Label {
|
||||
let heading = Measurement(value: degrees.degrees, unit: UnitAngle.degrees)
|
||||
Text("MQTT")
|
||||
} icon: {
|
||||
Image(systemName: "network")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.frame(width: 35)
|
||||
.rotationEffect(degrees)
|
||||
}
|
||||
.padding(.bottom, 5)
|
||||
}
|
||||
if let lastLocation = locationsHandler.locationsArray.last {
|
||||
/// Distance
|
||||
if lastLocation.distance(from: CLLocation(latitude: LocationsHandler.DefaultLocation.latitude, longitude: LocationsHandler.DefaultLocation.longitude)) > 0.0 {
|
||||
|
|
@ -181,8 +194,7 @@ struct PositionPopover: View {
|
|||
}
|
||||
BatteryGauge(node: position.nodePosition!)
|
||||
}
|
||||
let mpInt = Int(position.nodePosition?.loRaConfig?.modemPreset ?? 0)
|
||||
LoRaSignalStrengthMeter(snr: position.nodePosition?.snr ?? 0.0, rssi: position.nodePosition?.rssi ?? 0, preset: ModemPresets(rawValue: mpInt) ?? ModemPresets.longFast, compact: false)
|
||||
LoRaSignalStrengthMeter(snr: position.nodePosition?.snr ?? 0.0, rssi: position.nodePosition?.rssi ?? 0, preset: ModemPresets(rawValue: UserDefaults.modemPreset) ?? ModemPresets.longFast, compact: false)
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
|
|
@ -202,7 +214,7 @@ struct PositionPopover: View {
|
|||
#endif
|
||||
}
|
||||
}
|
||||
.presentationDetents([.fraction(0.45), .fraction(0.55), .fraction(0.65)])
|
||||
.presentationDetents([.fraction(0.55), .fraction(0.65), .fraction(0.75)])
|
||||
.presentationDragIndicator(.visible)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import MapKit
|
|||
struct NodeInfoItem: View {
|
||||
|
||||
@ObservedObject var node: NodeInfoEntity
|
||||
var modemPreset: ModemPresets = ModemPresets(rawValue: UserDefaults.modemPreset) ?? ModemPresets.longFast
|
||||
|
||||
var body: some View {
|
||||
|
||||
|
|
@ -37,11 +38,11 @@ struct NodeInfoItem: View {
|
|||
if node.snr != 0 && !node.viaMqtt {
|
||||
Divider()
|
||||
VStack(alignment: .center) {
|
||||
let signalStrength = getLoRaSignalStrength(snr: node.snr, rssi: node.rssi, preset: ModemPresets.longModerate)
|
||||
let signalStrength = getLoRaSignalStrength(snr: node.snr, rssi: node.rssi, preset: modemPreset)
|
||||
LoRaSignalStrengthIndicator(signalStrength: signalStrength)
|
||||
Text("Signal \(signalStrength.description)").font(.footnote)
|
||||
Text("SNR \(String(format: "%.2f", node.snr))dB")
|
||||
.foregroundColor(getSnrColor(snr: node.snr, preset: ModemPresets.longModerate))
|
||||
.foregroundColor(getSnrColor(snr: node.snr, preset: modemPreset))
|
||||
.font(.caption2)
|
||||
Text("RSSI \(node.rssi)dB")
|
||||
.foregroundColor(getRssiColor(rssi: node.rssi))
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ struct NodeListItem: View {
|
|||
@ObservedObject var node: NodeInfoEntity
|
||||
var connected: Bool
|
||||
var connectedNode: Int64
|
||||
var modemPreset: Int
|
||||
|
||||
var body: some View {
|
||||
|
||||
|
|
@ -21,6 +20,7 @@ struct NodeListItem: View {
|
|||
LazyVStack(alignment: .leading) {
|
||||
HStack {
|
||||
VStack(alignment: .leading) {
|
||||
|
||||
CircleText(text: node.user?.shortName ?? "?", color: Color(UIColor(hex: UInt32(node.num))), circleSize: 70)
|
||||
.padding(.trailing, 5)
|
||||
BatteryLevelCompact(node: node, font: .caption, iconFont: .callout, color: .accentColor)
|
||||
|
|
@ -119,24 +119,41 @@ struct NodeListItem: View {
|
|||
}
|
||||
HStack {
|
||||
if node.channel > 0 {
|
||||
Image(systemName: "fibrechannel")
|
||||
.font(.callout)
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.frame(width: 30)
|
||||
Text("Channel: \(node.channel)")
|
||||
.foregroundColor(.gray)
|
||||
.font(UIDevice.current.userInterfaceIdiom == .phone ? .callout : .caption)
|
||||
HStack {
|
||||
Image(systemName: "\(node.channel).circle.fill")
|
||||
.font(.title2)
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.frame(width: 30)
|
||||
.foregroundColor(.accentColor)
|
||||
Text("Channel")
|
||||
.foregroundColor(.gray)
|
||||
.font(UIDevice.current.userInterfaceIdiom == .phone ? .callout : .caption)
|
||||
}
|
||||
}
|
||||
|
||||
if node.viaMqtt && connectedNode != node.num {
|
||||
Image(systemName: "network")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.font(.callout)
|
||||
.frame(width: 30)
|
||||
Text("Via MQTT")
|
||||
Text("MQTT")
|
||||
.foregroundColor(.gray)
|
||||
.font(UIDevice.current.userInterfaceIdiom == .phone ? .callout : .caption)
|
||||
}
|
||||
}
|
||||
if node.hopsAway > 0 {
|
||||
HStack {
|
||||
Image(systemName: "hare")
|
||||
.font(.callout)
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text("Hops Away:")
|
||||
.foregroundColor(.gray)
|
||||
.font(UIDevice.current.userInterfaceIdiom == .phone ? .callout : .caption)
|
||||
Image(systemName: "\(node.hopsAway).square")
|
||||
.font(.title2)
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
}
|
||||
}
|
||||
if node.hasPositions || node.hasEnvironmentMetrics || node.hasDetectionSensorMetrics || node.hasTraceRoutes {
|
||||
HStack {
|
||||
Image(systemName: "scroll")
|
||||
|
|
@ -170,21 +187,16 @@ struct NodeListItem: View {
|
|||
.font(.callout)
|
||||
.frame(width: 30)
|
||||
}
|
||||
if node.hasTraceRoutes {
|
||||
Image(systemName: "signpost.right.and.left")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.font(.callout)
|
||||
.frame(width: 30)
|
||||
if #available(iOS 17.0, macOS 14.0, *) {
|
||||
if node.hasTraceRoutes {
|
||||
Image(systemName: "signpost.right.and.left")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.font(.callout)
|
||||
.frame(width: 30)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !connected {
|
||||
HStack {
|
||||
let preset = ModemPresets(rawValue: Int(modemPreset))
|
||||
LoRaSignalStrengthMeter(snr: node.snr, rssi: node.rssi, preset: preset ?? ModemPresets.longFast, compact: true)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -123,6 +123,7 @@ struct MeshMap: View {
|
|||
if radius > 0.0 {
|
||||
MapCircle(center: position.coordinate, radius: radius)
|
||||
.foregroundStyle(Color(nodeColor).opacity(0.25))
|
||||
.stroke(.white, lineWidth: 2)
|
||||
}
|
||||
}
|
||||
/// Routes
|
||||
|
|
|
|||
|
|
@ -7,6 +7,28 @@
|
|||
import SwiftUI
|
||||
import CoreLocation
|
||||
|
||||
struct NodeSearchState {
|
||||
var searchText = ""
|
||||
var searchScope = SearchScopes.all
|
||||
var predicate: NSPredicate = .init()
|
||||
|
||||
enum SearchScopes: CaseIterable, Identifiable {
|
||||
case all
|
||||
case lora
|
||||
case mqtt
|
||||
|
||||
var id: Self { self }
|
||||
|
||||
var title: LocalizedStringKey {
|
||||
switch self {
|
||||
case .all: return "All"
|
||||
case .lora: return "LoRa"
|
||||
case .mqtt: return "MQTT"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct NodeList: View {
|
||||
|
||||
@State private var columnVisibility = NavigationSplitViewVisibility.all
|
||||
|
|
@ -14,19 +36,13 @@ struct NodeList: View {
|
|||
@State private var isPresentingTraceRouteSentAlert = false
|
||||
@State private var isPresentingClientHistorySentAlert = false
|
||||
@State private var isPresentingDeleteNodeAlert = false
|
||||
@State private var isPresentingPositionSentAlert = false
|
||||
@State private var deleteNodeId: Int64 = 0
|
||||
@State private var searchState = NodeSearchState()
|
||||
|
||||
@SceneStorage("selectedDetailView") var selectedDetailView: String?
|
||||
|
||||
@State private var searchText = ""
|
||||
var nodesQuery: Binding<String> {
|
||||
Binding {
|
||||
searchText
|
||||
} set: { newValue in
|
||||
searchText = newValue
|
||||
nodes.nsPredicate = newValue.isEmpty ? nil : NSPredicate(format: "user.longName CONTAINS[c] %@ OR user.shortName CONTAINS[c] %@", newValue, newValue)
|
||||
}
|
||||
}
|
||||
|
||||
@Environment(\.managedObjectContext) var context
|
||||
@EnvironmentObject var bleManager: BLEManager
|
||||
|
|
@ -37,8 +53,6 @@ struct NodeList: View {
|
|||
|
||||
var nodes: FetchedResults<NodeInfoEntity>
|
||||
|
||||
|
||||
|
||||
var body: some View {
|
||||
NavigationSplitView(columnVisibility: $columnVisibility) {
|
||||
|
||||
|
|
@ -48,8 +62,7 @@ struct NodeList: View {
|
|||
|
||||
NodeListItem(node: node,
|
||||
connected: bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral?.num ?? -1 == node.num,
|
||||
connectedNode: (bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral?.num ?? -1 : -1),
|
||||
modemPreset: Int(connectedNode?.loRaConfig?.modemPreset ?? 0))
|
||||
connectedNode: (bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral?.num ?? -1 : -1))
|
||||
.contextMenu {
|
||||
if node.user != nil {
|
||||
Button {
|
||||
|
|
@ -76,7 +89,21 @@ struct NodeList: View {
|
|||
} label: {
|
||||
Label(node.user!.mute ? "Show Alerts" : "Hide Alerts", systemImage: node.user!.mute ? "bell" : "bell.slash")
|
||||
}
|
||||
if connectedNodeNum != node.num {
|
||||
if bleManager.connectedPeripheral != nil && node.num != connectedNodeNum {
|
||||
Button {
|
||||
let positionSent = bleManager.sendPosition(
|
||||
channel: node.channel,
|
||||
destNum: node.num,
|
||||
wantResponse: true
|
||||
)
|
||||
if positionSent {
|
||||
isPresentingPositionSentAlert = true
|
||||
}
|
||||
} label: {
|
||||
Label("Exchange Positions", systemImage: "arrow.triangle.2.circlepath")
|
||||
}
|
||||
}
|
||||
if bleManager.connectedPeripheral != nil && connectedNodeNum != node.num {
|
||||
Button {
|
||||
let success = bleManager.sendTraceRouteRequest(destNum: node.user?.num ?? 0, wantResponse: true)
|
||||
if success {
|
||||
|
|
@ -107,6 +134,14 @@ struct NodeList: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
.alert(
|
||||
"Position Sent",
|
||||
isPresented: $isPresentingPositionSentAlert
|
||||
) {
|
||||
Button("OK", role: .cancel) { }
|
||||
} message: {
|
||||
Text("Your position has been sent with a request for a response with their position.")
|
||||
}
|
||||
.alert(
|
||||
"Trace Route Sent",
|
||||
isPresented: $isPresentingTraceRouteSentAlert
|
||||
|
|
@ -124,7 +159,14 @@ struct NodeList: View {
|
|||
Text("Any missed messages will be delivered again.")
|
||||
}
|
||||
}
|
||||
.searchable(text: nodesQuery, prompt: "Find a node")
|
||||
.searchable(text: $searchState.searchText, placement: nodes.count > 10 ? .navigationBarDrawer(displayMode: .always) : .automatic, prompt: "Find a node")
|
||||
.disableAutocorrection(true)
|
||||
.scrollDismissesKeyboard(.immediately)
|
||||
.searchScopes($searchState.searchScope) {
|
||||
ForEach(NodeSearchState.SearchScopes.allCases) { scope in
|
||||
Text(scope.title).tag(scope)
|
||||
}
|
||||
}
|
||||
.navigationTitle(String.localizedStringWithFormat("nodes %@".localized, String(nodes.count)))
|
||||
.listStyle(.plain)
|
||||
.confirmationDialog(
|
||||
|
|
@ -195,33 +237,51 @@ struct NodeList: View {
|
|||
|
||||
}
|
||||
.navigationSplitViewStyle(.balanced)
|
||||
// .onChange(of: selectedNode) { _ in
|
||||
// if selectedNode == nil {
|
||||
// columnVisibility = .all
|
||||
// } else {
|
||||
// columnVisibility = .doubleColumn
|
||||
// }
|
||||
// }
|
||||
.onChange(of: searchState.searchText) { _ in
|
||||
searchNodeList()
|
||||
}
|
||||
.onChange(of: searchState.searchScope) { _ in
|
||||
searchNodeList()
|
||||
}
|
||||
.onAppear {
|
||||
if self.bleManager.context == nil {
|
||||
self.bleManager.context = context
|
||||
}
|
||||
}
|
||||
|
||||
// } detail: {
|
||||
// VStack {
|
||||
// Button("Detail Only") {
|
||||
// columnVisibility = .detailOnly
|
||||
// }
|
||||
//
|
||||
// Button("Content and Detail") {
|
||||
// columnVisibility = .doubleColumn
|
||||
// }
|
||||
//
|
||||
// Button("Show All") {
|
||||
// columnVisibility = .all
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
private func searchNodeList() {
|
||||
/// Case Insensitive Search Text Predicates
|
||||
let searchPredicates = ["user.userId", "user.hwModel", "user.longName", "user.shortName"].map { property in
|
||||
return NSPredicate(format: "%K CONTAINS[c] %@", property, searchState.searchText)
|
||||
}
|
||||
/// Create a compound predicate using each text search preicate as an OR
|
||||
let textSearchPredicate = NSCompoundPredicate(type: .or, subpredicates: searchPredicates)
|
||||
|
||||
/// Set the predicate to nil if the search string is empty
|
||||
if searchState.searchText.isEmpty {
|
||||
nodes.nsPredicate = nil
|
||||
return
|
||||
}
|
||||
|
||||
/// Add a predicate for the search scope if selected
|
||||
if searchState.searchScope != .all {
|
||||
|
||||
if searchState.searchScope == .lora {
|
||||
let loraPredicate = NSPredicate(format: "viaMqtt == NO")
|
||||
let scopePredicate = NSCompoundPredicate(type: .and, subpredicates: [loraPredicate])
|
||||
nodes.nsPredicate = NSCompoundPredicate(type: .and, subpredicates: [textSearchPredicate, scopePredicate])
|
||||
return
|
||||
|
||||
} else if searchState.searchScope == .mqtt {
|
||||
let mqttPredicate = NSPredicate(format: "viaMqtt == YES")
|
||||
let scopePredicate = NSCompoundPredicate(type: .and, subpredicates: [mqttPredicate])
|
||||
nodes.nsPredicate = NSCompoundPredicate(type: .and, subpredicates: [textSearchPredicate, scopePredicate])
|
||||
return
|
||||
}
|
||||
} else {
|
||||
/// Use the text search predicate
|
||||
nodes.nsPredicate = textSearchPredicate
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -75,8 +75,8 @@ struct PaxCounterLog: View {
|
|||
.chartXAxis(.automatic)
|
||||
.chartYScale(domain: 0...maxValue)
|
||||
.chartForegroundStyleScale([
|
||||
"paxcounter.ble": .blue,
|
||||
"paxcounter.wifi": .orange,
|
||||
"paxcounter.ble".localized: .blue,
|
||||
"paxcounter.wifi".localized: .orange,
|
||||
"paxcounter.total".localized: .green
|
||||
])
|
||||
.chartLegend(position: .automatic, alignment: .bottom)
|
||||
|
|
@ -111,11 +111,11 @@ struct PaxCounterLog: View {
|
|||
} else {
|
||||
ScrollView {
|
||||
let columns = [
|
||||
GridItem(.flexible(minimum: 20, maximum: 55), spacing: 0.1),
|
||||
GridItem(.flexible(minimum: 20, maximum: 55), spacing: 0.1),
|
||||
GridItem(.flexible(minimum: 20, maximum: 55), spacing: 0.1),
|
||||
GridItem(.flexible(minimum: 60, maximum: 100), spacing: 0.1),
|
||||
GridItem(.flexible(minimum: 130, maximum: 200), spacing: 0.1)
|
||||
GridItem(.flexible(minimum: 20, maximum: 50), spacing: 0.1),
|
||||
GridItem(.flexible(minimum: 20, maximum: 50), spacing: 0.1),
|
||||
GridItem(.flexible(minimum: 20, maximum: 50), spacing: 0.1),
|
||||
GridItem(.flexible(minimum: 60, maximum: 140), spacing: 0.1),
|
||||
GridItem(.flexible(minimum: 100, maximum: 160), spacing: 0.1)
|
||||
]
|
||||
LazyVGrid(columns: columns, alignment: .leading, spacing: 1) {
|
||||
GridRow {
|
||||
|
|
|
|||
|
|
@ -24,9 +24,15 @@ struct AppSettings: View {
|
|||
Label("appsettings.provide.location", systemImage: "location.circle.fill")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
Text("Use your phone's gps to provide a location to your node. Must have location access and precise location enabled for Meshtastic in Settings.")
|
||||
.font(.caption2)
|
||||
.foregroundColor(.gray)
|
||||
if provideLocation {
|
||||
Toggle(isOn: $enableSmartPosition) {
|
||||
Label("appsettings.smartposition", systemImage: "brain")
|
||||
Text("Will only send a position to the phone if it is recent and of high horizontal accuracy.")
|
||||
.font(.caption2)
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
VStack {
|
||||
|
|
|
|||
|
|
@ -68,6 +68,8 @@ struct Channels: View {
|
|||
channelKeySize = 0
|
||||
} else if channelKey == "AQ==" {
|
||||
channelKeySize = -1
|
||||
} else if channelKey.count == 4 {
|
||||
channelKeySize = 1
|
||||
} else if channelKey.count == 24 {
|
||||
channelKeySize = 16
|
||||
} else if channelKey.count == 32 {
|
||||
|
|
@ -268,7 +270,7 @@ struct Channels: View {
|
|||
|
||||
if !preciseLocation {
|
||||
VStack(alignment: .leading) {
|
||||
Label("Reduce Precision", systemImage: "location.viewfinder")
|
||||
Label("Approximate Location", systemImage: "location.slash.circle.fill")
|
||||
Slider(
|
||||
value: $positionPrecision,
|
||||
in: 11...16,
|
||||
|
|
@ -303,7 +305,7 @@ struct Channels: View {
|
|||
}
|
||||
.onAppear {
|
||||
let tempKey = Data(base64Encoded: channelKey) ?? Data()
|
||||
if tempKey.count == channelKeySize || channelKeySize == -1{
|
||||
if tempKey.count == channelKeySize || channelKeySize == -1 {
|
||||
hasValidKey = true
|
||||
}
|
||||
else {
|
||||
|
|
|
|||
|
|
@ -179,6 +179,10 @@ struct DeviceConfig: View {
|
|||
dc.nodeInfoBroadcastSecs = UInt32(nodeInfoBroadcastSecs)
|
||||
dc.doubleTapAsButtonPress = doubleTapAsButtonPress
|
||||
dc.isManaged = isManaged
|
||||
if isManaged {
|
||||
serialEnabled = false
|
||||
debugLogEnabled = false
|
||||
}
|
||||
let adminMessageId = bleManager.saveDeviceConfig(config: dc, fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0)
|
||||
if adminMessageId > 0 {
|
||||
// Should show a saved successfully alert once I know that to be true
|
||||
|
|
|
|||
|
|
@ -205,6 +205,9 @@ struct LoRaConfig: View {
|
|||
lc.sx126XRxBoostedGain = rxBoostedGain
|
||||
lc.overrideFrequency = overrideFrequency
|
||||
lc.ignoreMqtt = ignoreMqtt
|
||||
if connectedNode?.num ?? -1 == node?.user?.num ?? 0 {
|
||||
UserDefaults.modemPreset = modemPreset
|
||||
}
|
||||
let adminMessageId = bleManager.saveLoRaConfig(config: lc, fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0)
|
||||
if adminMessageId > 0 {
|
||||
// Should show a saved successfully alert once I know that to be true
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ struct Firmware: View {
|
|||
@Environment(\.managedObjectContext) var context
|
||||
@EnvironmentObject var bleManager: BLEManager
|
||||
var node: NodeInfoEntity?
|
||||
@State var minimumVersion = "2.2.21"
|
||||
@State var minimumVersion = "2.3.0"
|
||||
@State var version = ""
|
||||
@State private var currentDevice: DeviceHardware?
|
||||
@State private var latestStable: FirmwareRelease?
|
||||
|
|
|
|||
|
|
@ -144,7 +144,7 @@ struct Settings: View {
|
|||
Text("Your region has a \(rc?.dutyCycle ?? 0)% hourly duty cycle, your radio will stop sending packets when it reaches the hourly limit.")
|
||||
.foregroundColor(.orange)
|
||||
.font(.caption)
|
||||
Text("Limit all periodic broadcasts intervals especially telemetry and position. If you need to increase hops, do it on nodes at the edges, not the ones in the middle. MQTT is not advised when you are duty cycle restricted because the gateway node is then doing all the work.")
|
||||
Text("Limit all periodic broadcast intervals especially telemetry and position. If you need to increase hops, do it on nodes at the edges, not the ones in the middle. MQTT is not advised when you are duty cycle restricted because the gateway node is then doing all the work.")
|
||||
.font(.caption2)
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit 5241583565ccbbb4986180bf4c6eb7f8a0dec285
|
||||
Subproject commit 5a97acb17543a10e114675a205e3274a83e721af
|
||||
Loading…
Add table
Add a link
Reference in a new issue