Merge pull request #472 from meshtastic/2.2.21_Working_Changes

2.2.21 working changes
This commit is contained in:
Garth Vander Houwen 2024-02-07 09:45:36 -07:00 committed by GitHub
commit cd2d70c056
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
29 changed files with 846 additions and 153 deletions

View file

@ -241,6 +241,7 @@
DD1BF2F82776FE2E008C8D2F /* UserMessageList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserMessageList.swift; sourceTree = "<group>"; };
DD2160AE28C5552500C17253 /* MQTTConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MQTTConfig.swift; sourceTree = "<group>"; };
DD23A50E26FD1B4400D9B90C /* PeripheralModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeripheralModel.swift; sourceTree = "<group>"; };
DD23D9AB2B7133F6003F5CBE /* MeshtasticDataModelV 25.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 25.xcdatamodel"; sourceTree = "<group>"; };
DD2553562855B02500E55709 /* LoRaConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoRaConfig.swift; sourceTree = "<group>"; };
DD2553582855B52700E55709 /* PositionConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PositionConfig.swift; sourceTree = "<group>"; };
DD295CE92B323ED9002CC4AC /* MeshtasticDataModelV22.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV22.xcdatamodel; sourceTree = "<group>"; };
@ -1492,7 +1493,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 2.2.20;
MARKETING_VERSION = 2.2.21;
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTS_MACCATALYST = YES;
@ -1526,7 +1527,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 2.2.20;
MARKETING_VERSION = 2.2.21;
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTS_MACCATALYST = YES;
@ -1648,7 +1649,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.2.20;
MARKETING_VERSION = 2.2.21;
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@ -1681,7 +1682,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.2.20;
MARKETING_VERSION = 2.2.21;
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@ -1792,6 +1793,7 @@
DD3CC6BA28E366DF00FA9159 /* Meshtastic.xcdatamodeld */ = {
isa = XCVersionGroup;
children = (
DD23D9AB2B7133F6003F5CBE /* MeshtasticDataModelV 25.xcdatamodel */,
DDB234392B5CA9B000DA6FB1 /* MeshtasticDataModelV 24.xcdatamodel */,
DD33DB602B3D1ECC003E1EA0 /* MeshtasticDataModelV 23.xcdatamodel */,
DD295CE92B323ED9002CC4AC /* MeshtasticDataModelV22.xcdatamodel */,
@ -1817,7 +1819,7 @@
DD5D0A9A2931AD6B00F7EA61 /* MeshtasticDataModelV2.xcdatamodel */,
DD3CC6BB28E366DF00FA9159 /* MeshtasticDataModel.xcdatamodel */,
);
currentVersion = DDB234392B5CA9B000DA6FB1 /* MeshtasticDataModelV 24.xcdatamodel */;
currentVersion = DD23D9AB2B7133F6003F5CBE /* MeshtasticDataModelV 25.xcdatamodel */;
name = Meshtastic.xcdatamodeld;
path = Meshtastic/Meshtastic.xcdatamodeld;
sourceTree = "<group>";

View file

@ -12,22 +12,22 @@ enum DeviceRoles: Int, CaseIterable, Identifiable {
case client = 0
case clientMute = 1
case router = 2
case routerClient = 3
case repeater = 4
case clientHidden = 8
case tracker = 5
case lostAndFound = 9
case sensor = 6
case tak = 7
case clientHidden = 8
case lostAndFound = 9
case repeater = 4
case router = 2
case routerClient = 3
var id: Int { self.rawValue }
var name: String {
switch self {
case .client:
return "Client"
case .clientMute:
return "Muted Client"
return "Client Mute"
case .router:
return "Router"
case .routerClient:

View file

@ -24,6 +24,8 @@ enum RegionCodes: Int, CaseIterable, Identifiable {
case th = 12
case ua433 = 14
case ua868 = 15
case my_433 = 16
case my_919 = 17
case lora24 = 13
var id: Int { self.rawValue }
@ -61,9 +63,52 @@ enum RegionCodes: Int, CaseIterable, Identifiable {
return "Ukraine 868mhz"
case .lora24:
return "2.4 GHZ"
case .my_433:
return "Malaysia 433mhz"
case .my_919:
return "Malaysia 919mhz"
}
}
var dutyCycle: Int {
switch self {
case .unset:
return 0
case .us:
return 100
case .eu433:
return 10
case .eu868:
return 10
case .cn:
return 100
case .jp:
return 100
case .anz:
return 100
case .kr:
return 100
case .tw:
return 100
case .ru:
return 100
case .in:
return 100
case .nz865:
return 100
case .th:
return 100
case .ua433:
return 10
case .ua868:
return 10
case .lora24:
return 100
case .my_433:
return 100
case .my_919:
return 100
}
}
func protoEnumValue() -> Config.LoRaConfig.RegionCode {
switch self {
@ -99,6 +144,10 @@ enum RegionCodes: Int, CaseIterable, Identifiable {
return Config.LoRaConfig.RegionCode.ua868
case .lora24:
return Config.LoRaConfig.RegionCode.lora24
case .my_433:
return Config.LoRaConfig.RegionCode.my433
case .my_919:
return Config.LoRaConfig.RegionCode.my919
}
}
}

View file

@ -52,3 +52,77 @@ enum GpsFormats: Int, CaseIterable, Identifiable {
}
}
}
enum GpsUpdateIntervals: Int, CaseIterable, Identifiable {
case thirtySeconds = 30
case oneMinute = 60
case fiveMinutes = 300
case tenMinutes = 600
case fifteenMinutes = 900
case thirtyMinutes = 1800
case oneHour = 3600
case sixHours = 21600
case twelveHours = 43200
case twentyFourHours = 86400
case maxInt32 = 2147483647
var id: Int { self.rawValue }
var description: String {
switch self {
case .thirtySeconds:
return "interval.thirty.seconds".localized
case .oneMinute:
return "interval.one.minute".localized
case .fiveMinutes:
return "interval.five.minutes".localized
case .tenMinutes:
return "interval.ten.minutes".localized
case .fifteenMinutes:
return "interval.fifteen.minutes".localized
case .thirtyMinutes:
return "interval.thirty.minutes".localized
case .oneHour:
return "interval.one.hour".localized
case .sixHours:
return "interval.six.hours".localized
case .twelveHours:
return "interval.twelve.hours".localized
case .twentyFourHours:
return "interval.twentyfour.hours".localized
case .maxInt32:
return "on.boot".localized
}
}
}
enum GpsMode: Int, CaseIterable, Equatable {
case disabled = 0
case enabled = 1
case notPresent = 2
var id: Int { self.rawValue }
var description: String {
switch self {
case .disabled:
return "gpsmode.disabled".localized
case .enabled:
return "gpsmode.enabled".localized
case .notPresent:
return "gpsmode.notPresent".localized
}
}
func protoEnumValue() -> Config.PositionConfig.GpsMode {
switch self {
case .enabled:
return Config.PositionConfig.GpsMode.enabled
case .disabled:
return Config.PositionConfig.GpsMode.disabled
case .notPresent:
return Config.PositionConfig.GpsMode.notPresent
}
}
}

View file

@ -9,7 +9,6 @@ import Foundation
extension UserDefaults {
enum Keys: String, CaseIterable {
case enableRangeTest
case preferredPeripheralId
case preferredPeripheralNum
case provideLocation
@ -27,18 +26,12 @@ extension UserDefaults {
case mapUseLegacy
case enableDetectionNotifications
case detectionSensorRole
case enableSmartPosition
}
func reset() {
Keys.allCases.forEach { removeObject(forKey: $0.rawValue) }
}
static var blockRangeTest: Bool {
get {
UserDefaults.standard.bool(forKey: "blockRangeTest")
} set {
UserDefaults.standard.set(newValue, forKey: "blockRangeTest")
}
}
static var preferredPeripheralId: String {
get {
UserDefaults.standard.string(forKey: "preferredPeripheralId") ?? ""
@ -201,4 +194,12 @@ extension UserDefaults {
UserDefaults.standard.set(newValue.rawValue, forKey: "detectionSensorRole")
}
}
static var enableSmartPosition: Bool {
get {
UserDefaults.standard.bool(forKey: "enableSmartPosition")
}
set {
UserDefaults.standard.set(newValue, forKey: "enableSmartPosition")
}
}
}

View file

@ -594,7 +594,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
// Log any other unknownApp calls
if !nowKnown { MeshLogger.log("🕸️ MESH PACKET received for Unknown App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") }
case .textMessageApp, .detectionSensorApp:
textMessageAppPacket(packet: decodedInfo.packet, blockRangeTest: UserDefaults.blockRangeTest, connectedNode: (self.connectedPeripheral != nil ? connectedPeripheral.num : 0), context: context!)
textMessageAppPacket(packet: decodedInfo.packet, connectedNode: (self.connectedPeripheral != nil ? connectedPeripheral.num : 0), context: context!)
case .remoteHardwareApp:
MeshLogger.log("🕸️ MESH PACKET received for Remote Hardware App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")")
case .positionApp:
@ -620,8 +620,8 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
MeshLogger.log("🕸️ MESH PACKET received for Store and Forward App - Store and Forward is disabled.")
}
case .rangeTestApp:
if wantRangeTestPackets && !UserDefaults.blockRangeTest {
textMessageAppPacket(packet: decodedInfo.packet, blockRangeTest: false, connectedNode: (self.connectedPeripheral != nil ? connectedPeripheral.num : 0), context: context!)
if wantRangeTestPackets {
textMessageAppPacket(packet: decodedInfo.packet, connectedNode: (self.connectedPeripheral != nil ? connectedPeripheral.num : 0), context: context!)
}
else {
MeshLogger.log("🕸️ MESH PACKET received for Range Test App Range testing is disabled.")
@ -976,10 +976,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
if let lastLocation = LocationsHandler.shared.locationsArray.last {
/// Throw out crappy locations and only send a position if we are connected to a device
if fromNodeNum <= 0 || lastLocation.horizontalAccuracy < 0 || lastLocation.horizontalAccuracy > 100 {
return false
}
positionPacket.latitudeI = Int32(lastLocation.coordinate.latitude * 1e7)
positionPacket.longitudeI = Int32(lastLocation.coordinate.longitude * 1e7)
let timestamp = lastLocation.timestamp
@ -2386,7 +2382,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
/// send a request for ClientHistory with a time period matching the heartbeat
var sfPacket = StoreAndForward()
sfPacket.rr = StoreAndForward.RequestResponse.clientHistory
sfPacket.history.window = 18000000 // storeAndForwardMessage.heartbeat.period
sfPacket.history.window = 120 // storeAndForwardMessage.heartbeat.period
var meshPacket: MeshPacket = MeshPacket()
meshPacket.to = UInt32(packet.from)
meshPacket.from = UInt32(connectedNodeNum)
@ -2432,6 +2428,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
case .clientAbort:
MeshLogger.log("🛑 Store and Forward \(storeAndForwardMessage.rr) message received \(storeAndForwardMessage)")
case .UNRECOGNIZED:
textMessageAppPacket(packet: packet, connectedNode: connectedNodeNum, context: context)
MeshLogger.log("📮 Store and Forward \(storeAndForwardMessage.rr) message received \(storeAndForwardMessage)")
}
}

View file

@ -15,7 +15,7 @@ import CoreLocation
static let shared = LocationsHandler() // Create a single, shared instance of the object.
private let manager: CLLocationManager
private var background: CLBackgroundActivitySession?
var enableSmartPosition: Bool
var enableSmartPosition: Bool = UserDefaults.enableSmartPosition
@Published var locationsArray: [CLLocation]
@Published var isStationary = false
@ -41,8 +41,8 @@ import CoreLocation
private init() {
self.manager = CLLocationManager() // Creating a location manager instance is safe to call here in `MainActor`.
self.manager.allowsBackgroundLocationUpdates = true
locationsArray = [CLLocation]()
enableSmartPosition = true
}
func startLocationUpdates() {
@ -55,7 +55,7 @@ import CoreLocation
self.updatesStarted = true
let updates = CLLocationUpdate.liveUpdates()
for try await update in updates {
if !self.updatesStarted { break } // End location updates by breaking out of the loop.
if !self.updatesStarted { break }
if let loc = update.location {
self.isStationary = update.isStationary

View file

@ -721,13 +721,9 @@ func telemetryPacket(packet: MeshPacket, connectedNode: Int64, context: NSManage
}
}
func textMessageAppPacket(packet: MeshPacket, blockRangeTest: Bool, connectedNode: Int64, context: NSManagedObjectContext) {
func textMessageAppPacket(packet: MeshPacket, connectedNode: Int64, context: NSManagedObjectContext) {
if let messageText = String(bytes: packet.decoded.payload, encoding: .utf8) {
if blockRangeTest && messageText.starts(with: "seq ") {
return
}
MeshLogger.log("💬 \("mesh.log.textmessage.received".localized)")

View file

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

View file

@ -0,0 +1,412 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="22522" systemVersion="23D56" 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="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 &amp;&amp; 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="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="detection" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="environment" optional="YES" attributeType="Boolean" defaultValueString="NO" 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="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="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="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="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="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="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="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>

View file

@ -61,8 +61,14 @@ class PersistenceController {
do {
try persistentStoreCoordinator.destroyPersistentStore(at: url, ofType: NSSQLiteStoreType, options: nil)
try persistentStoreCoordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: url, options: nil)
print("💥 CoreData database truncated. All app data has been erased.")
do {
try persistentStoreCoordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: url, options: nil)
} catch let error {
print("💣 Failed to re-create CoreData database: " + error.localizedDescription)
try persistentStoreCoordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: url, options: nil)
}
} catch let error {
print("💣 Failed to destroy CoreData database, delete the app and re-install to clear data. Attempted to clear persistent store: " + error.localizedDescription)

View file

@ -118,6 +118,7 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext)
newNode.lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(packet.rxTime)))
newNode.snr = packet.rxSnr
newNode.rssi = packet.rxRssi
newNode.viaMqtt = packet.viaMqtt
if let nodeInfoMessage = try? NodeInfo(serializedData: packet.decoded.payload) {
newNode.channel = Int32(nodeInfoMessage.channel)
print(packet.channel)
@ -161,6 +162,7 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext)
}
fetchedNode[0].snr = packet.rxSnr
fetchedNode[0].rssi = packet.rxRssi
fetchedNode[0].viaMqtt = packet.viaMqtt
if let nodeInfoMessage = try? NodeInfo(serializedData: packet.decoded.payload) {
fetchedNode[0].channel = Int32(nodeInfoMessage.channel)
@ -277,6 +279,7 @@ func upsertPositionPacket (packet: MeshPacket, context: NSManagedObjectContext)
}
fetchedNode[0].snr = packet.rxSnr
fetchedNode[0].rssi = packet.rxRssi
fetchedNode[0].viaMqtt = packet.viaMqtt
fetchedNode[0].positions = mutablePositions.copy() as? NSOrderedSet
do {
@ -492,6 +495,7 @@ func upsertLoRaConfigPacket(config: Meshtastic.Config.LoRaConfig, nodeNum: Int64
newLoRaConfig.txEnabled = config.txEnabled
newLoRaConfig.channelNum = Int32(config.channelNum)
newLoRaConfig.sx126xRxBoostedGain = config.sx126XRxBoostedGain
newLoRaConfig.ignoreMqtt = config.ignoreMqtt
fetchedNode[0].loRaConfig = newLoRaConfig
} else {
fetchedNode[0].loRaConfig?.regionCode = Int32(config.region.rawValue)
@ -508,6 +512,8 @@ func upsertLoRaConfigPacket(config: Meshtastic.Config.LoRaConfig, nodeNum: Int64
fetchedNode[0].loRaConfig?.txEnabled = config.txEnabled
fetchedNode[0].loRaConfig?.channelNum = Int32(config.channelNum)
fetchedNode[0].loRaConfig?.sx126xRxBoostedGain = config.sx126XRxBoostedGain
fetchedNode[0].loRaConfig?.ignoreMqtt = config.ignoreMqtt
fetchedNode[0].loRaConfig?.sx126xRxBoostedGain = config.sx126XRxBoostedGain
}
do {
try context.save()
@ -592,6 +598,7 @@ func upsertPositionConfigPacket(config: Meshtastic.Config.PositionConfig, nodeNu
let newPositionConfig = PositionConfigEntity(context: context)
newPositionConfig.smartPositionEnabled = config.positionBroadcastSmartEnabled
newPositionConfig.deviceGpsEnabled = config.gpsEnabled
newPositionConfig.gpsMode = Int32(config.gpsMode.rawValue)
newPositionConfig.rxGpio = Int32(config.rxGpio)
newPositionConfig.txGpio = Int32(config.txGpio)
newPositionConfig.gpsEnGpio = Int32(config.gpsEnGpio)
@ -601,11 +608,12 @@ func upsertPositionConfigPacket(config: Meshtastic.Config.PositionConfig, nodeNu
newPositionConfig.broadcastSmartMinimumDistance = Int32(config.broadcastSmartMinimumDistance)
newPositionConfig.positionFlags = Int32(config.positionFlags)
newPositionConfig.gpsAttemptTime = 900
newPositionConfig.gpsUpdateInterval = 120
newPositionConfig.gpsUpdateInterval = Int32(config.gpsUpdateInterval)
fetchedNode[0].positionConfig = newPositionConfig
} else {
fetchedNode[0].positionConfig?.smartPositionEnabled = config.positionBroadcastSmartEnabled
fetchedNode[0].positionConfig?.deviceGpsEnabled = config.gpsEnabled
fetchedNode[0].positionConfig?.gpsMode = Int32(config.gpsMode.rawValue)
fetchedNode[0].positionConfig?.rxGpio = Int32(config.rxGpio)
fetchedNode[0].positionConfig?.txGpio = Int32(config.txGpio)
fetchedNode[0].positionConfig?.gpsEnGpio = Int32(config.gpsEnGpio)
@ -614,7 +622,7 @@ func upsertPositionConfigPacket(config: Meshtastic.Config.PositionConfig, nodeNu
fetchedNode[0].positionConfig?.broadcastSmartMinimumIntervalSecs = Int32(config.broadcastSmartMinimumIntervalSecs)
fetchedNode[0].positionConfig?.broadcastSmartMinimumDistance = Int32(config.broadcastSmartMinimumDistance)
fetchedNode[0].positionConfig?.gpsAttemptTime = 900
fetchedNode[0].positionConfig?.gpsUpdateInterval = 120
fetchedNode[0].positionConfig?.gpsUpdateInterval = Int32(config.gpsUpdateInterval)
fetchedNode[0].positionConfig?.positionFlags = Int32(config.positionFlags)
}
do {

View file

@ -235,6 +235,16 @@ struct AdminMessage {
set {payloadVariant = .enterDfuModeRequest(newValue)}
}
///
/// Delete the file by the specified path from the device
var deleteFileRequest: String {
get {
if case .deleteFileRequest(let v)? = payloadVariant {return v}
return String()
}
set {payloadVariant = .deleteFileRequest(newValue)}
}
///
/// Set the owner for this node
var setOwner: User {
@ -460,6 +470,9 @@ struct AdminMessage {
/// Only implemented on NRF52 currently
case enterDfuModeRequest(Bool)
///
/// Delete the file by the specified path from the device
case deleteFileRequest(String)
///
/// Set the owner for this node
case setOwner(User)
///
@ -598,6 +611,10 @@ struct AdminMessage {
guard case .enterDfuModeRequest(let l) = lhs, case .enterDfuModeRequest(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.deleteFileRequest, .deleteFileRequest): return {
guard case .deleteFileRequest(let l) = lhs, case .deleteFileRequest(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.setOwner, .setOwner): return {
guard case .setOwner(let l) = lhs, case .setOwner(let r) = rhs else { preconditionFailure() }
return l == r
@ -953,6 +970,7 @@ extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat
19: .standard(proto: "get_node_remote_hardware_pins_request"),
20: .standard(proto: "get_node_remote_hardware_pins_response"),
21: .standard(proto: "enter_dfu_mode_request"),
22: .standard(proto: "delete_file_request"),
32: .standard(proto: "set_owner"),
33: .standard(proto: "set_channel"),
34: .standard(proto: "set_config"),
@ -1176,6 +1194,14 @@ extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat
self.payloadVariant = .enterDfuModeRequest(v)
}
}()
case 22: try {
var v: String?
try decoder.decodeSingularStringField(value: &v)
if let v = v {
if self.payloadVariant != nil {try decoder.handleConflictingOneOf()}
self.payloadVariant = .deleteFileRequest(v)
}
}()
case 32: try {
var v: User?
var hadOneofValue = false
@ -1407,6 +1433,10 @@ extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat
guard case .enterDfuModeRequest(let v)? = self.payloadVariant else { preconditionFailure() }
try visitor.visitSingularBoolField(value: v, fieldNumber: 21)
}()
case .deleteFileRequest?: try {
guard case .deleteFileRequest(let v)? = self.payloadVariant else { preconditionFailure() }
try visitor.visitSingularStringField(value: v, fieldNumber: 22)
}()
case .setOwner?: try {
guard case .setOwner(let v)? = self.payloadVariant else { preconditionFailure() }
try visitor.visitSingularMessageField(value: v, fieldNumber: 32)

View file

@ -415,6 +415,10 @@ struct Config {
/// (Re)define PIN_GPS_EN for your board.
var gpsEnGpio: UInt32 = 0
///
/// Set where GPS is enabled, disabled, or not present
var gpsMode: Config.PositionConfig.GpsMode = .disabled
var unknownFields = SwiftProtobuf.UnknownStorage()
///
@ -516,6 +520,46 @@ struct Config {
}
enum GpsMode: SwiftProtobuf.Enum {
typealias RawValue = Int
///
/// GPS is present but disabled
case disabled // = 0
///
/// GPS is present and enabled
case enabled // = 1
///
/// GPS is not present on the device
case notPresent // = 2
case UNRECOGNIZED(Int)
init() {
self = .disabled
}
init?(rawValue: Int) {
switch rawValue {
case 0: self = .disabled
case 1: self = .enabled
case 2: self = .notPresent
default: self = .UNRECOGNIZED(rawValue)
}
}
var rawValue: Int {
switch self {
case .disabled: return 0
case .enabled: return 1
case .notPresent: return 2
case .UNRECOGNIZED(let i): return i
}
}
}
init() {}
}
@ -1366,6 +1410,15 @@ extension Config.PositionConfig.PositionFlags: CaseIterable {
]
}
extension Config.PositionConfig.GpsMode: CaseIterable {
// The compiler won't synthesize support with the UNRECOGNIZED case.
static var allCases: [Config.PositionConfig.GpsMode] = [
.disabled,
.enabled,
.notPresent,
]
}
extension Config.NetworkConfig.AddressMode: CaseIterable {
// The compiler won't synthesize support with the UNRECOGNIZED case.
static var allCases: [Config.NetworkConfig.AddressMode] = [
@ -1471,6 +1524,7 @@ extension Config.DeviceConfig.Role: @unchecked Sendable {}
extension Config.DeviceConfig.RebroadcastMode: @unchecked Sendable {}
extension Config.PositionConfig: @unchecked Sendable {}
extension Config.PositionConfig.PositionFlags: @unchecked Sendable {}
extension Config.PositionConfig.GpsMode: @unchecked Sendable {}
extension Config.PowerConfig: @unchecked Sendable {}
extension Config.NetworkConfig: @unchecked Sendable {}
extension Config.NetworkConfig.AddressMode: @unchecked Sendable {}
@ -1776,6 +1830,7 @@ extension Config.PositionConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageIm
10: .standard(proto: "broadcast_smart_minimum_distance"),
11: .standard(proto: "broadcast_smart_minimum_interval_secs"),
12: .standard(proto: "gps_en_gpio"),
13: .standard(proto: "gps_mode"),
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
@ -1796,6 +1851,7 @@ extension Config.PositionConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageIm
case 10: try { try decoder.decodeSingularUInt32Field(value: &self.broadcastSmartMinimumDistance) }()
case 11: try { try decoder.decodeSingularUInt32Field(value: &self.broadcastSmartMinimumIntervalSecs) }()
case 12: try { try decoder.decodeSingularUInt32Field(value: &self.gpsEnGpio) }()
case 13: try { try decoder.decodeSingularEnumField(value: &self.gpsMode) }()
default: break
}
}
@ -1838,6 +1894,9 @@ extension Config.PositionConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageIm
if self.gpsEnGpio != 0 {
try visitor.visitSingularUInt32Field(value: self.gpsEnGpio, fieldNumber: 12)
}
if self.gpsMode != .disabled {
try visitor.visitSingularEnumField(value: self.gpsMode, fieldNumber: 13)
}
try unknownFields.traverse(visitor: &visitor)
}
@ -1854,6 +1913,7 @@ extension Config.PositionConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageIm
if lhs.broadcastSmartMinimumDistance != rhs.broadcastSmartMinimumDistance {return false}
if lhs.broadcastSmartMinimumIntervalSecs != rhs.broadcastSmartMinimumIntervalSecs {return false}
if lhs.gpsEnGpio != rhs.gpsEnGpio {return false}
if lhs.gpsMode != rhs.gpsMode {return false}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}
@ -1875,6 +1935,14 @@ extension Config.PositionConfig.PositionFlags: SwiftProtobuf._ProtoNameProviding
]
}
extension Config.PositionConfig.GpsMode: SwiftProtobuf._ProtoNameProviding {
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
0: .same(proto: "DISABLED"),
1: .same(proto: "ENABLED"),
2: .same(proto: "NOT_PRESENT"),
]
}
extension Config.PowerConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
static let protoMessageName: String = Config.protoMessageName + ".PowerConfig"
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [

View file

@ -126,6 +126,10 @@ enum HardwareModel: SwiftProtobuf.Enum {
/// Makerfabs SenseLoRA Industrial Monitor (ESP32-S3 + RFM96)
case senseloraS3 // = 28
///
/// Canary Radio Company - CanaryOne: https://canaryradio.io/products/canaryone
case canaryone // = 29
///
/// ---------------------------------------------------------------------------
/// Less common/prototype boards listed here (needs one more byte over the air)
@ -230,6 +234,14 @@ enum HardwareModel: SwiftProtobuf.Enum {
/// with one cut and one jumper Meshtastic works
case chatter2 // = 56
///
/// Heltec Wireless Paper, With ESP32-S3 CPU and E-Ink display
/// Older "V1.0" Variant, has no "version sticker"
/// E-Ink model is DEPG0213BNS800
/// Tab on the screen protector is RED
/// Flex connector marking is FPC-7528B
case heltecWirelessPaperV10 // = 57
///
/// ------------------------------------------------------------------------------------------------------------------------------------------
/// Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits.
@ -267,6 +279,7 @@ enum HardwareModel: SwiftProtobuf.Enum {
case 26: self = .rak11310
case 27: self = .senseloraRp2040
case 28: self = .senseloraS3
case 29: self = .canaryone
case 32: self = .loraRelayV1
case 33: self = .nrf52840Dk
case 34: self = .ppr
@ -292,6 +305,7 @@ enum HardwareModel: SwiftProtobuf.Enum {
case 54: self = .ebyteEsp32S3
case 55: self = .esp32S3Pico
case 56: self = .chatter2
case 57: self = .heltecWirelessPaperV10
case 255: self = .privateHw
default: self = .UNRECOGNIZED(rawValue)
}
@ -323,6 +337,7 @@ enum HardwareModel: SwiftProtobuf.Enum {
case .rak11310: return 26
case .senseloraRp2040: return 27
case .senseloraS3: return 28
case .canaryone: return 29
case .loraRelayV1: return 32
case .nrf52840Dk: return 33
case .ppr: return 34
@ -348,6 +363,7 @@ enum HardwareModel: SwiftProtobuf.Enum {
case .ebyteEsp32S3: return 54
case .esp32S3Pico: return 55
case .chatter2: return 56
case .heltecWirelessPaperV10: return 57
case .privateHw: return 255
case .UNRECOGNIZED(let i): return i
}
@ -384,6 +400,7 @@ extension HardwareModel: CaseIterable {
.rak11310,
.senseloraRp2040,
.senseloraS3,
.canaryone,
.loraRelayV1,
.nrf52840Dk,
.ppr,
@ -409,6 +426,7 @@ extension HardwareModel: CaseIterable {
.ebyteEsp32S3,
.esp32S3Pico,
.chatter2,
.heltecWirelessPaperV10,
.privateHw,
]
}
@ -2570,6 +2588,7 @@ extension HardwareModel: SwiftProtobuf._ProtoNameProviding {
26: .same(proto: "RAK11310"),
27: .same(proto: "SENSELORA_RP2040"),
28: .same(proto: "SENSELORA_S3"),
29: .same(proto: "CANARYONE"),
32: .same(proto: "LORA_RELAY_V1"),
33: .same(proto: "NRF52840DK"),
34: .same(proto: "PPR"),
@ -2595,6 +2614,7 @@ extension HardwareModel: SwiftProtobuf._ProtoNameProviding {
54: .same(proto: "EBYTE_ESP32_S3"),
55: .same(proto: "ESP32_S3_PICO"),
56: .same(proto: "CHATTER_2"),
57: .same(proto: "HELTEC_WIRELESS_PAPER_V1_0"),
255: .same(proto: "PRIVATE_HW"),
]
}

View file

@ -209,21 +209,18 @@ struct Connect: View {
}.padding([.bottom, .top])
}
}
.confirmationDialog("Connecting to a new radio will clear all local app data on the phone.", isPresented: $presentingSwitchPreferredPeripheral, titleVisibility: .visible) {
.confirmationDialog("Connecting to a new radio will clear all local app data on the phone. The app may close.", isPresented: $presentingSwitchPreferredPeripheral, titleVisibility: .visible) {
Button("Connect to new radio?", role: .destructive) {
bleManager.stopScanning()
bleManager.connectedPeripheral = nil
UserDefaults.preferredPeripheralId = ""
UserDefaults.preferredPeripheralId = selectedPeripherialId
if bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral.peripheral.state == CBPeripheralState.connected {
bleManager.disconnectPeripheral()
}
clearCoreDataDatabase(context: context)
let radio = bleManager.peripherals.first(where: { $0.peripheral.identifier.uuidString == selectedPeripherialId})
bleManager.connectTo(peripheral: radio!.peripheral)
presentingSwitchPreferredPeripheral = false
selectedPeripherialId = ""
PersistenceController.shared.clearDatabase()
let radio = bleManager.peripherals.first(where: { $0.peripheral.identifier.uuidString == selectedPeripherialId })
if radio != nil {
bleManager.connectTo(peripheral: radio!.peripheral)
}
}
}
.textCase(nil)

View file

@ -89,14 +89,21 @@ struct NodeListItem: View {
}
}
}
if node.channel > 0 {
HStack {
HStack {
if node.channel > 0 {
Image(systemName: "fibrechannel")
.font(.callout)
.symbolRenderingMode(.hierarchical)
Text("Channel: \(node.channel)")
.font(.callout)
}
if node.viaMqtt && connectedNode != node.num {
Image(systemName: "network")
.symbolRenderingMode(.hierarchical)
.font(.callout)
Text("Via MQTT")
.font(.callout)
}
}
if !connected {
HStack {

View file

@ -86,7 +86,7 @@ struct NodeMap: View {
Section(header: Text("Map Options")) {
Picker(selection: $selectedMapLayer, label: Text("")) {
ForEach(MapLayer.allCases, id: \.self) { layer in
if layer == MapLayer.offline && UserDefaults.enableOfflineMaps {
if layer == MapLayer.offline && enableOfflineMaps {
Text(layer.localized)
} else if layer != MapLayer.offline {
Text(layer.localized)

View file

@ -11,7 +11,7 @@ struct AppSettings: View {
@State var totalDownloadedTileSize = ""
@StateObject var locationHelper = LocationHelper()
@State var provideLocation: Bool = UserDefaults.provideLocation
@State var blockRangeTest: Bool = UserDefaults.blockRangeTest
@State var enableSmartPosition: Bool = UserDefaults.enableSmartPosition
@State var useLegacyMap: Bool = UserDefaults.mapUseLegacy
@State var provideLocationInterval: Int = UserDefaults.provideLocationInterval
@State private var isPresentingCoreDataResetConfirm = false
@ -20,12 +20,6 @@ struct AppSettings: View {
VStack {
Form {
Section(header: Text("options")) {
Toggle(isOn: $blockRangeTest) {
Label("range.test.blocked", systemImage: "x.circle")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
Toggle(isOn: $useLegacyMap) {
Label("map.use.legacy", systemImage: "map")
}
@ -71,10 +65,14 @@ struct AppSettings: View {
}
Section(header: Text("Location Settings")) {
Toggle(isOn: $provideLocation) {
Label("provide.location", systemImage: "location.circle.fill")
Label("appsettings.provide.location", systemImage: "location.circle.fill")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
if UserDefaults.provideLocation {
if provideLocation {
Toggle(isOn: $enableSmartPosition) {
Label("appsettings.smartposition", systemImage: "brain.fill")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
VStack {
Picker("update.interval", selection: $provideLocationInterval) {
ForEach(LocationUpdateInterval.allCases) { lu in
@ -106,8 +104,8 @@ struct AppSettings: View {
Button("Erase all app data?", role: .destructive) {
bleManager.disconnectPeripheral()
clearCoreDataDatabase(context: context)
context.refreshAllObjects()
UserDefaults.standard.reset()
UserDefaults.standard.synchronize()
}
}
}
@ -137,7 +135,7 @@ struct AppSettings: View {
totalDownloadedTileSize = tileManager.getAllDownloadedSize()
})
}
.navigationTitle("app.settings")
.navigationTitle("appsettings")
.navigationBarItems(trailing:
ZStack {
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
@ -151,9 +149,6 @@ struct AppSettings: View {
self.bleManager.context = context
}
}
.onChange(of: blockRangeTest) { newBlockRangeTest in
UserDefaults.blockRangeTest = newBlockRangeTest
}
.onChange(of: provideLocation) { newProvideLocation in
UserDefaults.provideLocation = newProvideLocation
if bleManager.connectedPeripheral != nil {

View file

@ -1,6 +1,6 @@
//
// ShareChannel.swift
// MeshtasticApple
// Channels.swift
// Meshtastic Apple
//
// Copyright(c) Garth Vander Houwen 4/8/22.
//
@ -25,12 +25,11 @@ struct Channels: View {
var node: NodeInfoEntity?
@State var hasChanges = false
@State var hasValidKey = false
@State private var isPresentingEditView = false
@State private var isPresentingSaveConfirm: Bool = false
@State private var channelIndex: Int32 = 0
@State private var channelName = ""
@State private var channelKeySize = 32
@State private var channelKeySize = 16
@State private var channelKey = "AQ=="
@State private var channelRole = 0
@State private var uplink = false
@ -90,7 +89,7 @@ struct Channels: View {
if node?.myInfo?.channels?.array.count ?? 0 < 8 && node != nil {
Button {
let key = generateChannelKey(size: 32)
let key = generateChannelKey(size: 16)
channelName = ""
channelIndex = Int32(node!.myInfo!.channels!.array.count)
channelRole = 2
@ -168,34 +167,16 @@ struct Channels: View {
HStack(alignment: .top) {
Text("Key")
Spacer()
TextField(
"Key",
text: $channelKey
)
.padding(4)
.disableAutocorrection(true)
.keyboardType(.alphabet)
.foregroundColor(Color.gray)
.textSelection(.enabled)
.background(
RoundedRectangle(cornerRadius: 25.0)
.stroke(
hasValidKey ?
Color.green :
Color.red
, lineWidth: 2.0)
)
.onChange(of: channelKey, perform: { _ in
let tempKey = Data(base64Encoded: channelKey) ?? Data()
if tempKey.count == channelKeySize || channelKeySize == -1{
hasValidKey = true
}
else {
hasValidKey = false
}
hasChanges = true
})
.disabled(channelKeySize <= 0)
Text(channelKey)
.foregroundColor(Color.gray)
.textSelection(.enabled)
// TextField(
// "",
// text: $channelKey,
// axis: .vertical
// )
// .foregroundColor(Color.gray)
// .disabled(true)
}
Picker("Channel Role", selection: $channelRole) {
if channelRole == 1 {
@ -275,7 +256,7 @@ struct Channels: View {
} label: {
Label("save", systemImage: "square.and.arrow.down")
}
.disabled(bleManager.connectedPeripheral == nil || !hasChanges || !hasValidKey)
.disabled(bleManager.connectedPeripheral == nil || !hasChanges)
.buttonStyle(.bordered)
.buttonBorderShape(.capsule)
.controlSize(.large)

View file

@ -176,7 +176,7 @@ struct LoRaConfig: View {
.scrollDismissesKeyboard(.immediately)
.focused($focusedField, equals: .channelNum)
}
Text("This determines the actual frequency you are transmitting on in the band.")
Text("This determines the actual frequency you are transmitting on in the band. If set to 0 this value will be calculated automatically based on the primary channel name.")
.font(.caption)
Toggle(isOn: $rxBoostedGain) {
Label("RX Boosted Gain", systemImage: "waveform.badge.plus")

View file

@ -30,6 +30,15 @@ struct MQTTConfig: View {
var body: some View {
VStack {
Form {
if node != nil && node?.loRaConfig != nil {
let rc = RegionCodes(rawValue: Int(node?.loRaConfig?.regionCode ?? 0))
if rc?.dutyCycle ?? 0 <= 10 {
Text("Your region has a \(rc?.dutyCycle ?? 0)% duty cycle. MQTT is not advised when you are duty cycle restricted, the extra traffice will quickly overwhelm your LoRa mesh.")
.font(.callout)
.foregroundColor(.red)
}
}
if node != nil && node?.metadata == nil && node?.num ?? 0 != bleManager.connectedPeripheral?.num ?? 0 {
Text("There has been no response to a request for device metadata over the admin channel for this node.")
.font(.callout)
@ -88,7 +97,7 @@ struct MQTTConfig: View {
Label("JSON Enabled", systemImage: "ellipsis.curlybraces")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
Text("JSON mode is a limited, unencrypted MQTT output.")
Text("JSON mode is a limited, unencrypted MQTT output that can crash your node it should not be enabled unless you are locally integrating with home assistant")
.font(.caption2)
Toggle(isOn: $tlsEnabled) {
@ -304,19 +313,11 @@ struct MQTTConfig: View {
.onChange(of: encryptionEnabled) { newEncryptionEnabled in
if node != nil && node?.mqttConfig != nil {
if newEncryptionEnabled != node!.mqttConfig!.encryptionEnabled { hasChanges = true }
if newEncryptionEnabled {
jsonEnabled = false
}
}
}
.onChange(of: jsonEnabled) { newJsonEnabled in
if node != nil && node?.mqttConfig != nil {
if newJsonEnabled != node!.mqttConfig!.jsonEnabled { hasChanges = true }
if newJsonEnabled {
encryptionEnabled = false
proxyToClientEnabled = false
}
}
}
.onChange(of: tlsEnabled) { newTlsEnabled in

View file

@ -36,14 +36,16 @@ struct PositionConfig: View {
@State var smartPositionEnabled = true
@State var deviceGpsEnabled = true
@State var gpsMode = 0
@State var rxGpio = 0
@State var txGpio = 0
@State var gpsEnGpio = 0
@State var fixedPosition = false
@State var gpsUpdateInterval = 0
@State var positionBroadcastSeconds = 0
@State var broadcastSmartMinimumDistance = 0
@State var broadcastSmartMinimumIntervalSecs = 0
@State var positionFlags = 3
@State var positionFlags = 811
/// Position Flags
/// Altitude value - 1
@ -143,6 +145,34 @@ struct PositionConfig: View {
.font(.caption)
}
}
Section(header: Text("Device GPS")) {
Picker("", selection: $gpsMode) {
ForEach(GpsMode.allCases, id: \.self) { at in
Text(at.description)
.tag(at.id)
}
}
.pickerStyle(SegmentedPickerStyle())
.padding(.top, 5)
.padding(.bottom, 5)
if gpsMode == 1 {
Picker("Update Interval", selection: $gpsUpdateInterval) {
ForEach(GpsUpdateIntervals.allCases) { ui in
Text(ui.description)
}
}
Text("How often should we try to get a GPS position.")
.font(.caption)
} else {
Toggle(isOn: $fixedPosition) {
Label("Fixed Position", systemImage: "location.square.fill")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
Text("If enabled your current phone location will be sent to the device and will broadcast over the mesh on the position interval. Fixed positon will always use the most recent position the device has.")
.font(.caption)
}
}
Section(header: Text("Position Flags")) {
Text("Optional fields to include when assembling position messages. the more fields are included, the larger the message will be - leading to longer airtime and a higher risk of packet loss")
@ -205,13 +235,9 @@ struct PositionConfig: View {
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
}
}
Section(header: Text("Device GPS")) {
Toggle(isOn: $deviceGpsEnabled) {
Label("Device GPS Enabled", systemImage: "location")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
if deviceGpsEnabled {
if gpsMode == 1 {
Section(header: Text("Advanced Device GPS")) {
Picker("GPS Receive GPIO", selection: $rxGpio) {
ForEach(0..<49) {
if $0 == 0 {
@ -244,13 +270,6 @@ struct PositionConfig: View {
.pickerStyle(DefaultPickerStyle())
Text("(Re)define PIN_GPS_EN for your board.")
.font(.caption)
} else {
Toggle(isOn: $fixedPosition) {
Label("Fixed Position", systemImage: "location.square.fill")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
Text("If enabled your current location will be set as a fixed position.")
.font(.caption)
}
}
}
@ -283,8 +302,10 @@ struct PositionConfig: View {
if connectedNode != nil {
var pc = Config.PositionConfig()
pc.positionBroadcastSmartEnabled = smartPositionEnabled
pc.gpsEnabled = deviceGpsEnabled
pc.gpsEnabled = gpsMode == 1
pc.gpsMode = Config.PositionConfig.GpsMode(rawValue: gpsMode) ?? Config.PositionConfig.GpsMode.notPresent
pc.fixedPosition = fixedPosition
pc.gpsUpdateInterval = UInt32(gpsUpdateInterval)
pc.positionBroadcastSecs = UInt32(positionBroadcastSeconds)
pc.broadcastSmartMinimumIntervalSecs = UInt32(broadcastSmartMinimumIntervalSecs)
pc.broadcastSmartMinimumDistance = UInt32(broadcastSmartMinimumDistance)
@ -342,6 +363,11 @@ struct PositionConfig: View {
if newDeviceGps != node!.positionConfig!.deviceGpsEnabled { hasChanges = true }
}
}
.onChange(of: gpsMode) { newGpsMode in
if node != nil && node!.positionConfig != nil {
if newGpsMode != node!.positionConfig!.gpsMode { hasChanges = true }
}
}
.onChange(of: rxGpio) { newRxGpio in
if node != nil && node!.positionConfig != nil {
if newRxGpio != node!.positionConfig!.rxGpio { hasChanges = true }
@ -382,6 +408,11 @@ struct PositionConfig: View {
if newBroadcastSmartMinimumDistance != node!.positionConfig!.broadcastSmartMinimumDistance { hasChanges = true }
}
}
.onChange(of: gpsUpdateInterval) { newGpsUpdateInterval in
if node != nil && node!.positionConfig != nil {
if newGpsUpdateInterval != node!.positionConfig!.gpsUpdateInterval { hasChanges = true }
}
}
.onChange(of: includeAltitude) { altFlag in
let pf = PositionFlags(rawValue: self.positionFlags)
let existingValue = pf.contains(.Altitude)
@ -440,11 +471,16 @@ struct PositionConfig: View {
}
func setPositionValues() {
self.smartPositionEnabled = node?.positionConfig?.smartPositionEnabled ?? true
self.deviceGpsEnabled = node?.positionConfig?.deviceGpsEnabled ?? true
self.deviceGpsEnabled = node?.positionConfig?.deviceGpsEnabled ?? false
self.gpsMode = Int(node?.positionConfig?.gpsMode ?? 0)
if node?.positionConfig?.deviceGpsEnabled ?? false && gpsMode != 1 {
self.gpsMode = 1
}
self.rxGpio = Int(node?.positionConfig?.rxGpio ?? 0)
self.txGpio = Int(node?.positionConfig?.txGpio ?? 0)
self.gpsEnGpio = Int(node?.positionConfig?.gpsEnGpio ?? 0)
self.fixedPosition = node?.positionConfig?.fixedPosition ?? false
self.gpsUpdateInterval = Int(node?.positionConfig?.gpsUpdateInterval ?? 30)
self.positionBroadcastSeconds = Int(node?.positionConfig?.positionBroadcastSeconds ?? 900)
self.broadcastSmartMinimumIntervalSecs = Int(node?.positionConfig?.broadcastSmartMinimumIntervalSecs ?? 30)
self.broadcastSmartMinimumDistance = Int(node?.positionConfig?.broadcastSmartMinimumDistance ?? 50)

View file

@ -57,7 +57,7 @@ struct Settings: View {
} label: {
Image(systemName: "gearshape")
.symbolRenderingMode(.hierarchical)
Text("app.settings")
Text("appsettings")
}
.tag(SettingsSidebar.appSettings)
if #available(iOS 17.0, macOS 14.0, *) {
@ -312,13 +312,15 @@ struct Settings: View {
}
}
.onAppear {
self.preferredNodeNum = UserDefaults.preferredPeripheralNum
if nodes.count > 1 {
if selectedNode == 0 {
if self.preferredNodeNum == 0 {
self.preferredNodeNum = UserDefaults.preferredPeripheralNum
if nodes.count > 1 {
if selectedNode == 0 {
self.selectedNode = Int(bleManager.connectedPeripheral != nil ? UserDefaults.preferredPeripheralNum : 0)
}
} else {
self.selectedNode = Int(bleManager.connectedPeripheral != nil ? UserDefaults.preferredPeripheralNum : 0)
}
} else {
self.selectedNode = Int(bleManager.connectedPeripheral != nil ? UserDefaults.preferredPeripheralNum : 0)
}
}
.listStyle(GroupedListStyle())

View file

@ -6,7 +6,7 @@
## Overview
SwiftUI client applicaitons for iOS, iPadOS and macOS.
SwiftUI client applications for iOS, iPadOS and macOS.
## OS Requirements

View file

@ -14,7 +14,9 @@
"always.on"="Immer an";
"ambient.lighting"="Ambient Lighting";
"ambient.lighting.config"="Ambient Lighting Config";
"app.settings"="App Einstellungen";
"appsettings"="App Einstellungen";
"appsettings.provide.location"="Standort im Mesh veröffentlichen";
"appsettings.smartposition"="Smart Position";
"are.you.sure"="Bist Du sicher?";
"ascii.capable"="ASCII fähig";
"available.radios"="Geräte in der Nähe";
@ -60,6 +62,7 @@
"current"="Current";
"default"="Standard";
"delete"="Löschen";
"detection.sensor"="Detection Sensor";
"device"="Gerät";
"device.config"="Gerätekonfiguration";
"device.metrics.delete"="Delete all device metrics?";
@ -222,7 +225,6 @@
"position"="Position";
"position.config"="Positionseinstellungen";
"preferred.radio"="Bevorzugtes Gerät";
"provide.location"="Standort im Mesh veröffentlichen";
"radio.configuration"="Geräteeinstellungen";
"range.test"="Entfernungstest";
"range.test.blocked"="Block Range Test";

View file

@ -14,7 +14,9 @@
"always.on"="Always On";
"ambient.lighting"="Ambient Lighting";
"ambient.lighting.config"="Ambient Lighting Config";
"app.settings"="App Settings";
"appsettings"="App Settings";
"appsettings.provide.location"="Share location";
"appsettings.smartposition"="Smart Position";
"are.you.sure"="Are you sure?";
"ascii.capable"="ASCII Capable";
"available.radios"="Available Radios";
@ -67,15 +69,16 @@
"device.config"="Device Config";
"device.metrics.delete"="Delete all device metrics?";
"device.metrics.log"="Device Metrics Log";
"device.role.client"="Client (default) - App connected client.";
"device.role.clienthidden"=" Used for nodes that \"only speak when spoken to\" Turns all of the routine broadcasts but allows for ad-hoc communication. Still rebroadcasts, but with local only rebroadcast mode (known meshes only). Can be used for private operation or to dramatically reduce airtime / power consumption.";
"device.role.clientmute"="Client Mute - Same as a client except packets will not hop over this node, does not contribute to routing packets for mesh.";
"device.role.lostandfound"="Used to automatically send a text message to the mesh with the current position of the device on a frequent interval: \"I'm lost! Position: lat / long\"";
"device.role.router"="Router - Mesh packets will prefer to be routed over this node. Assumes device will operate in a standalone manner while placed in a location with a coverage advantage. WARNING: The BLE/Wi-Fi radios and the OLED screen will be put to sleep.";
"device.role.routerclient"="Router Client - Hybrid of the Client and Router roles. Similar to Router, except the Router Client can be used as both a Router and an app connected Client. BLE/Wi-Fi and OLED screen will not be put to sleep.";
"device.role.repeater"="Repeater - Mesh packets will prefer to be routed over this node. This role eliminates unnecessary overhead such as NodeInfo, DeviceTelemetry, and any other mesh packet, resulting in the device not appearing as part of the network. Please see Rebroadcast Mode for additional settings specific to this role.";
"device.role.tracker"="Tracker - For use with devices intended as a GPS tracker. Position packets sent from this device will be higher priority, with position broadcasting every two minutes. Smart Position Broadcast will default to off.";
"device.role.tak"="Used for nodes dedicated for connection to an ATAK EUD. Turns off many of the routine broadcasts to favor CoT packet stream from the Meshtastic ATAK plugin -> IMeshService -> Node";
"device.role.client"="App connected or stand alone messaging client.";
"device.role.clientmute"="Client that does not forward packets from other devices.";
"device.role.clienthidden"="Client that only broadcasts as needed for stealth or power savings.";
"device.role.tracker"="Prioritizes broadcasting GPS position packets.";
"device.role.lostandfound"="Broadcasts location as message to default channel regularly for to assist with node recovery.";
"device.role.sensor"="Prioritizes broadcasting telemetry packets.";
"device.role.tak"="Optimized for ATAK system communication, reduces routine broadcasts.";
"device.role.repeater"="Infrastructure node for extending mesh network coverage by relaying messages with minimal overhead. Not visible in Nodes list. Best positioned in strategic locations to maximize the network's overall coverage. Device is not shown in topology.";
"device.role.router"="Infrastructure node for on extending mesh network coverage by relaying messages. Visible in Nodes list. Best positioned in strategic locations to maximize the network's overall coverage. Device is shown in topology.";
"device.role.routerclient"="Combination of both ROUTER and CLIENT. Not for mobile nodes.";
"direct.messages"="Direct Messages";
"dismiss.keyboard"="Dismiss";
"display"="Display (Device Screen)";
@ -100,6 +103,9 @@
"gpsformat.mgrs"="Military Grid Reference System";
"gpsformat.olc"="Open Location Code (aka Plus Codes)";
"gpsformat.osgr"="Ordnance Survey Grid Reference";
"gpsmode.disabled"="Disabled";
"gpsmode.enabled"="Enabled";
"gpsmode.notPresent"="Not Present";
"heard"="Heard";
"heard.last"="Last Heard";
"hybrid"="Hybrid";
@ -226,7 +232,6 @@
"position"="Position";
"position.config"="Position Config";
"preferred.radio"="Preferred Radio";
"provide.location"="Share location";
"radio.configuration"="Radio Configuration";
"range.test"="Range Test";
"range.test.blocked"="Block Range Test";

View file

@ -16,7 +16,9 @@
"always.on"="Zawsze włączone";
"ambient.lighting"="Ambient Lighting";
"ambient.lighting.config"="Ambient Lighting Config";
"app.settings"="Ustawienia aplikacji";
"appsettings"="Ustawienia aplikacji";
"appsettings.provide.location"="Udostępnij lokalizację";
"appsettings.smartposition"="Smart Position";
"are.you.sure"="Jesteś pewny?";
"ascii.capable"="Zgodny z ASCII";
"available.radios"="Dostępne radia";
@ -62,6 +64,7 @@
"current"="Bieżący";
"default"="Domyślny";
"delete"="Usuń";
"detection.sensor"="Detection Sensor";
"device"="Urządzenie";
"device.config"="Konfiguracja urządzenia";
"device.metrics.delete"="Usunąć wszystkie metryki urządzenia?";
@ -223,7 +226,6 @@
"position"="Pozycja";
"position.config"="Konfiguracja pozycji";
"preferred.radio"="Preferowane radio";
"provide.location"="Udostępnij lokalizację";
"radio.configuration"="Konfiguracja radia";
"range.test"="Test zasięgu";
"range.test.blocked"="Block Range Test";

View file

@ -14,7 +14,9 @@
"always.on"="常亮";
"ambient.lighting"="Ambient Lighting";
"ambient.lighting.config"="Ambient Lighting Config";
"app.settings"="通用设置";
"appsettings"="通用设置";
"appsettings.provide.location"="提供定位到 Mesh 网络";
"appsettings.smartposition"="Smart Position";
"are.you.sure"="是否确认?";
"ascii.capable"="ASCII Capable";
"available.radios"="可以连接的电台";
@ -60,6 +62,7 @@
"current"="当前";
"default"="默认";
"delete"="删除";
"detection.sensor"="Detection Sensor";
"device"="电台";
"device.config"="电台配置";
"device.metrics.delete"="删除所有电台指标?";
@ -222,7 +225,6 @@
"position"="定位";
"position.config"="定位配置";
"preferred.radio"="首选电台";
"provide.location"="提供定位到 Mesh 网络";
"radio.configuration"="电台配置";
"range.test"="拉距测试";
"range.test.blocked"="区块范围测试";