Merge pull request #584 from meshtastic/2.3.4_Working_Changes

2.3.4 working changes
This commit is contained in:
Garth Vander Houwen 2024-04-08 21:14:53 -07:00 committed by GitHub
commit f0109c9638
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
29 changed files with 1412 additions and 460 deletions

View file

@ -201,6 +201,8 @@
DDE5B4042B2279A700FCDD05 /* TraceRouteLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE5B4032B2279A700FCDD05 /* TraceRouteLog.swift */; };
DDE5B4062B227E3200FCDD05 /* TraceRouteEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE5B4052B227E3200FCDD05 /* TraceRouteEntityExtension.swift */; };
DDE9659C2B1C3B6A00531070 /* RouteRecorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE9659B2B1C3B6A00531070 /* RouteRecorder.swift */; };
DDF45C342BC1A48E005ED5F2 /* MQTTIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF45C332BC1A48E005ED5F2 /* MQTTIcon.swift */; };
DDF45C372BC46A5A005ED5F2 /* TimeZone.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF45C362BC46A5A005ED5F2 /* TimeZone.swift */; };
DDF6B2482A9AEBF500BA6931 /* StoreForwardConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF6B2472A9AEBF500BA6931 /* StoreForwardConfig.swift */; };
DDF924CA26FBB953009FE055 /* ConnectedDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF924C926FBB953009FE055 /* ConnectedDevice.swift */; };
DDFEB3BB29900C1200EE7472 /* CurrentConditionsCompact.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDFEB3BA29900C1200EE7472 /* CurrentConditionsCompact.swift */; };
@ -477,6 +479,10 @@
DDE5B4052B227E3200FCDD05 /* TraceRouteEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TraceRouteEntityExtension.swift; sourceTree = "<group>"; };
DDE9659B2B1C3B6A00531070 /* RouteRecorder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RouteRecorder.swift; sourceTree = "<group>"; };
DDEE03EC29544A1000FCAD57 /* MeshtasticDataModelV4.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV4.xcdatamodel; sourceTree = "<group>"; };
DDF45C332BC1A48E005ED5F2 /* MQTTIcon.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MQTTIcon.swift; sourceTree = "<group>"; };
DDF45C352BC465B2005ED5F2 /* se */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = se; path = se.lproj/Localizable.strings; sourceTree = "<group>"; };
DDF45C362BC46A5A005ED5F2 /* TimeZone.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeZone.swift; sourceTree = "<group>"; };
DDF45C382BC46B16005ED5F2 /* MeshtasticDataModelV33.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV33.xcdatamodel; sourceTree = "<group>"; };
DDF6B2462A9AEB9E00BA6931 /* MeshtasticDataModelV17.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV17.xcdatamodel; sourceTree = "<group>"; };
DDF6B2472A9AEBF500BA6931 /* StoreForwardConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreForwardConfig.swift; sourceTree = "<group>"; };
DDF6B24B2A9C2FC800BA6931 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Localizable.strings; sourceTree = "<group>"; };
@ -907,6 +913,7 @@
DDC2E18D26CE25CB0042C5E4 /* Helpers */ = {
isa = PBXGroup;
children = (
DDF45C332BC1A48E005ED5F2 /* MQTTIcon.swift */,
DD5E523D298F5A7D00D21B61 /* Weather */,
DD47E3D526F17ED900029299 /* CircleText.swift */,
DDF924C926FBB953009FE055 /* ConnectedDevice.swift */,
@ -990,6 +997,7 @@
DDB75A102A059258006ED576 /* Url.swift */,
DD1933772B084F4200771CD5 /* Measurement.swift */,
DDFFA7462B3A7F3C004730DB /* Bundle.swift */,
DDF45C362BC46A5A005ED5F2 /* TimeZone.swift */,
);
path = Extensions;
sourceTree = "<group>";
@ -1138,6 +1146,7 @@
he,
fr,
"zh-Hant-TW",
se,
);
mainGroup = DDC2E14B26CE248E0042C5E4;
packageReferences = (
@ -1283,6 +1292,7 @@
6DEDA55C2A9592F900321D2E /* MessageEntityExtension.swift in Sources */,
DDDB444229F8A88700EE2349 /* Double.swift in Sources */,
DD5E520F298EE33B00D21B61 /* cannedmessages.pb.swift in Sources */,
DDF45C342BC1A48E005ED5F2 /* MQTTIcon.swift in Sources */,
DDB75A232A13CDA9006ED576 /* BatteryLevelCompact.swift in Sources */,
DDB75A162A0594AD006ED576 /* TileOverlay.swift in Sources */,
DDF924CA26FBB953009FE055 /* ConnectedDevice.swift in Sources */,
@ -1291,6 +1301,7 @@
DD3619152B1EF9F900C41C8C /* LocationsHandler.swift in Sources */,
DDDB444A29F8AA3A00EE2349 /* CLLocationCoordinate2D.swift in Sources */,
DD41582628582E9B009B0E59 /* DeviceConfig.swift in Sources */,
DDF45C372BC46A5A005ED5F2 /* TimeZone.swift in Sources */,
DD007BAE2AA4E91200F5FA12 /* MyInfoEntityExtension.swift in Sources */,
DD33DB622B3D27C7003E1EA0 /* FirmwareApi.swift in Sources */,
DD3CC6B528E33FD100FA9159 /* ShareChannels.swift in Sources */,
@ -1461,6 +1472,7 @@
DD31EC492B7F18B7006A3995 /* he */,
DDDC22312BA76701002C44F1 /* fr */,
DDDC22322BA76961002C44F1 /* zh-Hant-TW */,
DDF45C352BC465B2005ED5F2 /* se */,
);
name = Localizable.strings;
sourceTree = "<group>";
@ -1611,7 +1623,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 2.3.3;
MARKETING_VERSION = 2.3.4;
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTS_MACCATALYST = YES;
@ -1645,7 +1657,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 2.3.3;
MARKETING_VERSION = 2.3.4;
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTS_MACCATALYST = YES;
@ -1767,7 +1779,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.3.3;
MARKETING_VERSION = 2.3.4;
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@ -1800,7 +1812,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.3.3;
MARKETING_VERSION = 2.3.4;
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@ -1911,6 +1923,7 @@
DD3CC6BA28E366DF00FA9159 /* Meshtastic.xcdatamodeld */ = {
isa = XCVersionGroup;
children = (
DDF45C382BC46B16005ED5F2 /* MeshtasticDataModelV33.xcdatamodel */,
DD9681A22BBB22BE00FD2C47 /* MeshtasticDataModelV32.xcdatamodel */,
DDDCD5712BB3246500BE6B60 /* MeshtasticDataModelV 31.xcdatamodel */,
DD9A1A912BA2D2D3001E602E /* MeshtasticDataModelV 30.xcdatamodel */,
@ -1944,7 +1957,7 @@
DD5D0A9A2931AD6B00F7EA61 /* MeshtasticDataModelV2.xcdatamodel */,
DD3CC6BB28E366DF00FA9159 /* MeshtasticDataModel.xcdatamodel */,
);
currentVersion = DD9681A22BBB22BE00FD2C47 /* MeshtasticDataModelV32.xcdatamodel */;
currentVersion = DDF45C382BC46B16005ED5F2 /* MeshtasticDataModelV33.xcdatamodel */;
name = Meshtastic.xcdatamodeld;
path = Meshtastic/Meshtastic.xcdatamodeld;
sourceTree = "<group>";

View file

@ -0,0 +1,72 @@
//
// TimeZone.swift
// Meshtastic
//
// Copyright(C) Garth Vander Houwen 4/8/24.
//
import Foundation
extension TimeZone {
var posixDescription: String {
if let nextDate = nextDaylightSavingTimeTransition, let afterDate = nextDaylightSavingTimeTransition(after: nextDate) {
// This timezone observes DST
// Get the transition dates to/from standard/DST
let stdDate: Date
let dstDate: Date
if isDaylightSavingTime(for: nextDate) {
stdDate = afterDate
dstDate = nextDate
} else {
stdDate = nextDate
dstDate = afterDate
}
// Append the standard abbreviation
var res = posixAbbreviation(for: stdDate)
// Append the standard offset
res += posixOffset(for: stdDate)
// Append the DST abbreviation
res += posixAbbreviation(for: dstDate)
// Append the DST offset if it's not 1 hour different
let diff = secondsFromGMT(for: stdDate) - secondsFromGMT(for: dstDate)
if abs(diff) != 3600 {
res += posixOffset(for: dstDate)
}
// Get month, weekday ordinal, weekday, hour, minutes, and second
// weekday gets returned as 1-based but we need 0-based
// The hour is based on the post-transition time but we need the pre-transition time
var cal = Calendar(identifier: .gregorian)
cal.timeZone = self
let stdcomps = cal.dateComponents([.month, .weekdayOrdinal, .weekday, .hour, .minute, .second], from: stdDate)
let dstcomps = cal.dateComponents([.month, .weekdayOrdinal, .weekday, .hour, .minute, .second], from: dstDate)
res += String(format: ",M%d.%d.%d/%d:%02d:%02d", dstcomps.month!, dstcomps.weekdayOrdinal!, dstcomps.weekday! - 1, dstcomps.hour! - 1, dstcomps.minute!, dstcomps.second!)
res += String(format: ",M%d.%d.%d/%d:%02d:%02d", stdcomps.month!, stdcomps.weekdayOrdinal!, stdcomps.weekday! - 1, stdcomps.hour! + 1, stdcomps.minute!, stdcomps.second!)
return res
} else {
// This timezone does not observe DST
return "\(posixAbbreviation())\(posixOffset())"
}
}
private func posixAbbreviation(for date: Date = Date()) -> String {
let abrev = abbreviation(for: date) ?? "<UNK>" // We never actually get "<UNK>" for any TimeZone identifier
// Many abbreviations come in the form "GMT+X" or "GMT-X"
return abrev.hasPrefix("GMT") ? "GMT" : abrev
}
private func posixOffset(for date: Date = Date()) -> String {
// The POSIX offset is the opposite of the GMT offset
let secs = 0 - secondsFromGMT(for: date)
let h = secs / 3600
let m = abs(secs) % 3600 / 60
let s = abs(secs) % 60
// Show the hour, only show the minutes and seconds if non-zero
return "\(h)\(m == 0 && s == 0 ? "" : ":\(String(format: "%02d", m))")\(s == 0 ? "" : ":\(String(format: "%02d", s))")"
}
}

View file

@ -27,6 +27,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
@Published var isSwitchedOn: Bool = false
@Published var automaticallyReconnect: Bool = true
@Published var mqttProxyConnected: Bool = false
@Published var mqttError: String = ""
@StateObject var appState = AppState.shared
public var minimumVersion = "2.0.0"
public var connectedVersion: String
@ -312,6 +313,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
// MARK: MqttClientProxyManagerDelegate Methods
func onMqttConnected() {
mqttProxyConnected = true
mqttError = ""
print("📲 Mqtt Client Proxy onMqttConnected now subscribing to \(mqttManager.topic).")
mqttManager.mqttClientProxy?.subscribe(mqttManager.topic)
}
@ -344,6 +346,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
func onMqttError(message: String) {
mqttProxyConnected = false
mqttError = message
print("📲 Mqtt Client Proxy onMqttError: \(message)")
}
@ -415,7 +418,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
// Grab the most recent postion, within the last hour
if connectedNode?.positions?.count ?? 0 > 0 {
let mostRecent = connectedNode?.positions?.lastObject as! PositionEntity
if mostRecent.time! >= Calendar.current.date(byAdding: .minute, value: -60, to: Date())! {
if mostRecent.time! >= Calendar.current.date(byAdding: .hour, value: -24, to: Date())! {
traceRoute.altitude = mostRecent.altitude
traceRoute.latitudeI = mostRecent.latitudeI
traceRoute.longitudeI = mostRecent.longitudeI

View file

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

View file

@ -0,0 +1,462 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="22757" systemVersion="23E224" 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" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="id" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="index" attributeType="Integer 32" minValueString="0" maxValueString="13" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="mute" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="name" optional="YES" attributeType="String"/>
<attribute name="positionPrecision" optional="YES" attributeType="Integer 32" defaultValueString="32" usesScalarValueType="YES"/>
<attribute name="psk" optional="YES" attributeType="Binary"/>
<attribute name="role" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="uplinkEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<relationship name="myInfoChannel" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MyInfoEntity" inverseName="channels" inverseEntity="MyInfoEntity"/>
<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"/>
<attribute name="tzdef" optional="YES" attributeType="String"/>
<relationship name="deviceConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="deviceConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="DeviceHardwareEntity" representedClassName="DeviceHardwareEntity" syncable="YES" codeGenerationType="class">
<attribute name="activelySupported" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="architecture" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="displayName" optional="YES" attributeType="String"/>
<attribute name="hwModel" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="hwModelSlug" optional="YES" attributeType="String"/>
<attribute name="platformioTarget" optional="YES" attributeType="String"/>
</entity>
<entity name="DeviceMetadataEntity" representedClassName="DeviceMetadataEntity" syncable="YES" codeGenerationType="class">
<attribute name="canShutdown" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="deviceStateVersion" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="firmwareVersion" optional="YES" attributeType="String"/>
<attribute name="hasBluetooth" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="hasEthernet" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="hasWifi" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="hwModel" optional="YES" attributeType="String"/>
<attribute name="positionFlags" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="role" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<relationship name="metadataNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="metadata" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="DisplayConfigEntity" representedClassName="DisplayConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="compassNorthTop" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="displayMode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="flipScreen" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="gpsFormat" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="headingBold" optional="YES" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
<attribute name="oledType" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="screenCarouselInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="screenOnSeconds" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="units" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="wakeOnTapOrMotion" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<relationship name="displayConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="displayConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="ExternalNotificationConfigEntity" representedClassName="ExternalNotificationConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="active" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="alertBell" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="alertBellBuzzer" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="alertBellVibra" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="alertMessage" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="alertMessageBuzzer" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="alertMessageVibra" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="nagTimeout" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="output" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="outputBuzzer" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="outputMilliseconds" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="outputVibra" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="useI2SAsBuzzer" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="usePWM" optional="YES" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
<relationship name="externalNotificationConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="externalNotificationConfig" inverseEntity="NodeInfoEntity"/>
<fetchedProperty name="fetchedProperty" optional="YES">
<fetchRequest name="fetchedPropertyFetchRequest" entity="ExternalNotificationConfigEntity"/>
</fetchedProperty>
</entity>
<entity name="LocationEntity" representedClassName="LocationEntity" syncable="YES" codeGenerationType="class">
<attribute name="altitude" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="heading" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="id" optional="YES" attributeType="Integer 32" usesScalarValueType="YES"/>
<attribute name="latitudeI" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="longitudeI" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="speed" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<relationship name="routeLocation" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="RouteEntity" inverseName="locations" inverseEntity="RouteEntity"/>
</entity>
<entity name="LoRaConfigEntity" representedClassName="LoRaConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="bandwidth" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="channelNum" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="codingRate" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="frequencyOffset" optional="YES" attributeType="Float" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="hopLimit" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="ignoreMqtt" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="modemPreset" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="overrideDutyCycle" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="overrideFrequency" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="regionCode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="spreadFactor" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="sx126xRxBoostedGain" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="txEnabled" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
<attribute name="txPower" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="usePreset" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
<relationship name="loRaConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="loRaConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="MessageEntity" representedClassName="MessageEntity" syncable="YES" codeGenerationType="class">
<attribute name="ackError" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="ackSNR" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="ackTimestamp" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="admin" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="adminDescription" optional="YES" attributeType="String"/>
<attribute name="channel" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="isEmoji" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="messageId" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="messagePayload" optional="YES" attributeType="String" defaultValueString=""/>
<attribute name="messagePayloadMarkdown" optional="YES" attributeType="String"/>
<attribute name="messageTimestamp" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="portNum" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="read" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="realACK" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="receivedACK" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="receivedTimestamp" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="replyID" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="rssi" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<relationship name="fromUser" optional="YES" maxCount="1" deletionRule="Nullify" ordered="YES" destinationEntity="UserEntity" inverseName="sentMessages" inverseEntity="UserEntity"/>
<relationship name="toUser" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="UserEntity" inverseName="receivedMessages" inverseEntity="UserEntity"/>
<fetchedProperty name="tapbacks" optional="YES">
<fetchRequest name="fetchedPropertyFetchRequest" entity="MessageEntity" predicateString="replyID == $FETCH_SOURCE.messageId AND isEmoji == true"/>
</fetchedProperty>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="messageId"/>
</uniquenessConstraint>
</uniquenessConstraints>
</entity>
<entity name="MQTTConfigEntity" representedClassName="MQTTConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="address" optional="YES" attributeType="String" maxValueString="30"/>
<attribute name="enabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="encryptionEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="jsonEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="mapPositionPrecision" optional="YES" attributeType="Integer 32" defaultValueString="13" usesScalarValueType="YES"/>
<attribute name="mapPublishIntervalSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="mapReportingEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="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="favorite" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="hopsAway" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="id" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="lastHeard" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="num" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="peripheralId" optional="YES" attributeType="String"/>
<attribute name="rssi" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="viaMqtt" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<relationship name="ambientLightingConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="AmbientLightingConfigEntity" inverseName="ambientLightingConfigNode" inverseEntity="AmbientLightingConfigEntity"/>
<relationship name="bluetoothConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="BluetoothConfigEntity" inverseName="bluetoothConfigNode" inverseEntity="BluetoothConfigEntity"/>
<relationship name="cannedMessageConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="CannedMessageConfigEntity" inverseName="cannedMessagesConfigNode" inverseEntity="CannedMessageConfigEntity"/>
<relationship name="detectionSensorConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="DetectionSensorConfigEntity" inverseName="detectionSensorConfigNode" inverseEntity="DetectionSensorConfigEntity"/>
<relationship name="deviceConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="DeviceConfigEntity" inverseName="deviceConfigNode" inverseEntity="DeviceConfigEntity"/>
<relationship name="displayConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="DisplayConfigEntity" inverseName="displayConfigNode" inverseEntity="DisplayConfigEntity"/>
<relationship name="externalNotificationConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="ExternalNotificationConfigEntity" inverseName="externalNotificationConfigNode" inverseEntity="ExternalNotificationConfigEntity"/>
<relationship name="loRaConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="LoRaConfigEntity" inverseName="loRaConfigNode" inverseEntity="LoRaConfigEntity"/>
<relationship name="metadata" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="DeviceMetadataEntity" inverseName="metadataNode" inverseEntity="DeviceMetadataEntity"/>
<relationship name="mqttConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MQTTConfigEntity" inverseName="mqttConfigNode" inverseEntity="MQTTConfigEntity"/>
<relationship name="myInfo" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MyInfoEntity" inverseName="myInfoNode" inverseEntity="MyInfoEntity"/>
<relationship name="networkConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NetworkConfigEntity" inverseName="networkConfigNode" inverseEntity="NetworkConfigEntity"/>
<relationship name="pax" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="PaxCounterEntity" inverseName="paxNode" inverseEntity="PaxCounterEntity"/>
<relationship name="paxCounterConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="PaxCounterConfigEntity" inverseName="paxCounterConfigNode" inverseEntity="PaxCounterConfigEntity"/>
<relationship name="positionConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="PositionConfigEntity" inverseName="positionConfigNode" inverseEntity="PositionConfigEntity"/>
<relationship name="positions" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="PositionEntity" inverseName="nodePosition" inverseEntity="PositionEntity"/>
<relationship name="powerConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="PowerConfigEntity" inverseName="powerConfigNode" inverseEntity="PowerConfigEntity"/>
<relationship name="rangeTestConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="RangeTestConfigEntity" inverseName="rangeTestConfigNode" inverseEntity="RangeTestConfigEntity"/>
<relationship name="rtttlConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="RTTTLConfigEntity" inverseName="rtttlConfigNode" inverseEntity="RTTTLConfigEntity"/>
<relationship name="serialConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="SerialConfigEntity" inverseName="serialConfigNode" inverseEntity="SerialConfigEntity"/>
<relationship name="storeForwardConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="StoreForwardConfigEntity" inverseName="storeForwardConfigNode" inverseEntity="StoreForwardConfigEntity"/>
<relationship name="telemetries" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="TelemetryEntity" inverseName="nodeTelemetry" inverseEntity="TelemetryEntity"/>
<relationship name="telemetryConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="TelemetryConfigEntity" inverseName="telemetryConfigNode" inverseEntity="TelemetryConfigEntity"/>
<relationship name="traceRoutes" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="TraceRouteEntity" inverseName="node" inverseEntity="TraceRouteEntity"/>
<relationship name="user" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="UserEntity" inverseName="userNode" inverseEntity="UserEntity"/>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="num"/>
</uniquenessConstraint>
</uniquenessConstraints>
</entity>
<entity name="PaxCounterConfigEntity" representedClassName="PaxCounterConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="enabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="paxcounterUpdateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<relationship name="paxCounterConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="paxCounterConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="PaxCounterEntity" representedClassName="PaxCounterEntity" syncable="YES" codeGenerationType="class">
<attribute name="ble" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="time" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="uptime" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="wifi" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<relationship name="paxNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="pax" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="PositionConfigEntity" representedClassName="PositionConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="broadcastSmartMinimumDistance" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="broadcastSmartMinimumIntervalSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="deviceGpsEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="fixedPosition" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="gpsAttemptTime" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="gpsEnGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="gpsMode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="gpsUpdateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="positionBroadcastSeconds" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="positionFlags" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="rxGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="smartPositionEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="txGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<relationship name="positionConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="positionConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="PositionEntity" representedClassName="PositionEntity" syncable="YES" codeGenerationType="class">
<attribute name="altitude" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="heading" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="latest" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="latitudeI" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="longitudeI" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="precisionBits" optional="YES" attributeType="Integer 32" defaultValueString="32" usesScalarValueType="YES"/>
<attribute name="rssi" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="satsInView" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="seqNo" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="speed" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="time" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<relationship name="nodePosition" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="positions" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="PowerConfigEntity" representedClassName="PowerConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="adcMultiplierOverride" optional="YES" attributeType="Float" usesScalarValueType="YES"/>
<attribute name="deviceBatteryInaAddress" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="isPowerSaving" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="lsSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="minWakeSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="onBatteryShutdownAfterSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="waitBluetoothSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<relationship name="powerConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="powerConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="RangeTestConfigEntity" representedClassName="RangeTestConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="save" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="sender" optional="YES" attributeType="Integer 32" usesScalarValueType="YES"/>
<relationship name="rangeTestConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="rangeTestConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="RouteEntity" representedClassName="RouteEntity" syncable="YES" codeGenerationType="class">
<attribute name="color" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="date" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="enabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="id" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="name" optional="YES" attributeType="String"/>
<attribute name="notes" optional="YES" attributeType="String"/>
<relationship name="locations" optional="YES" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="LocationEntity" inverseName="routeLocation" inverseEntity="LocationEntity"/>
</entity>
<entity name="RTTTLConfigEntity" representedClassName="RTTTLConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="ringtone" optional="YES" attributeType="String" maxValueString="228" defaultValueString=""/>
<relationship name="rtttlConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="rtttlConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="SerialConfigEntity" representedClassName="SerialConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="baudRate" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="echo" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="mode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="overrideConsoleSerialPort" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="rxd" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="timeout" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="txd" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<relationship name="serialConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="serialConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="StoreForwardConfigEntity" representedClassName="StoreForwardConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="enabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="heartbeat" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
<attribute name="historyReturnMax" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="historyReturnWindow" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="isRouter" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="lastHeartbeat" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="lastRequest" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="records" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<relationship name="storeForwardConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="storeForwardConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="TelemetryConfigEntity" representedClassName="TelemetryConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="deviceUpdateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="environmentDisplayFahrenheit" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="environmentMeasurementEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="environmentScreenEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="environmentUpdateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="powerMeasurementEnabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="powerScreenEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="powerUpdateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<relationship name="telemetryConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="telemetryConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="TelemetryEntity" representedClassName="TelemetryEntity" syncable="YES" 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="numString" optional="YES" attributeType="String"/>
<attribute name="role" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="shortName" attributeType="String"/>
<attribute name="userId" attributeType="String"/>
<relationship name="receivedMessages" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="MessageEntity" inverseName="toUser" inverseEntity="MessageEntity"/>
<relationship name="sentMessages" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="MessageEntity" inverseName="fromUser" inverseEntity="MessageEntity"/>
<relationship name="userNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="user" inverseEntity="NodeInfoEntity"/>
<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"/>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="id"/>
</uniquenessConstraint>
</uniquenessConstraints>
</entity>
</model>

View file

@ -437,6 +437,7 @@ func upsertDeviceConfigPacket(config: Meshtastic.Config.DeviceConfig, nodeNum: I
newDeviceConfig.nodeInfoBroadcastSecs = Int32(truncating: config.nodeInfoBroadcastSecs as NSNumber)
newDeviceConfig.doubleTapAsButtonPress = config.doubleTapAsButtonPress
newDeviceConfig.isManaged = config.isManaged
newDeviceConfig.tzdef = config.tzdef
fetchedNode[0].deviceConfig = newDeviceConfig
} else {
fetchedNode[0].deviceConfig?.role = Int32(config.role.rawValue)
@ -448,6 +449,7 @@ func upsertDeviceConfigPacket(config: Meshtastic.Config.DeviceConfig, nodeNum: I
fetchedNode[0].deviceConfig?.nodeInfoBroadcastSecs = Int32(truncating: config.nodeInfoBroadcastSecs as NSNumber)
fetchedNode[0].deviceConfig?.doubleTapAsButtonPress = config.doubleTapAsButtonPress
fetchedNode[0].deviceConfig?.isManaged = config.isManaged
fetchedNode[0].deviceConfig?.tzdef = config.tzdef
}
do {
try context.save()

View file

@ -190,6 +190,10 @@ struct Config {
/// Disables the triple-press of user button to enable or disable GPS
var disableTripleClick: Bool = false
///
/// POSIX Timezone definition string from https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv.
var tzdef: String = String()
var unknownFields = SwiftProtobuf.UnknownStorage()
///
@ -1734,6 +1738,7 @@ extension Config.DeviceConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImpl
8: .standard(proto: "double_tap_as_button_press"),
9: .standard(proto: "is_managed"),
10: .standard(proto: "disable_triple_click"),
11: .same(proto: "tzdef"),
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
@ -1752,6 +1757,7 @@ extension Config.DeviceConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImpl
case 8: try { try decoder.decodeSingularBoolField(value: &self.doubleTapAsButtonPress) }()
case 9: try { try decoder.decodeSingularBoolField(value: &self.isManaged) }()
case 10: try { try decoder.decodeSingularBoolField(value: &self.disableTripleClick) }()
case 11: try { try decoder.decodeSingularStringField(value: &self.tzdef) }()
default: break
}
}
@ -1788,6 +1794,9 @@ extension Config.DeviceConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImpl
if self.disableTripleClick != false {
try visitor.visitSingularBoolField(value: self.disableTripleClick, fieldNumber: 10)
}
if !self.tzdef.isEmpty {
try visitor.visitSingularStringField(value: self.tzdef, fieldNumber: 11)
}
try unknownFields.traverse(visitor: &visitor)
}
@ -1802,6 +1811,7 @@ extension Config.DeviceConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImpl
if lhs.doubleTapAsButtonPress != rhs.doubleTapAsButtonPress {return false}
if lhs.isManaged != rhs.isManaged {return false}
if lhs.disableTripleClick != rhs.disableTripleClick {return false}
if lhs.tzdef != rhs.tzdef {return false}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}

View file

@ -21,7 +21,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP
}
///
/// TODO: REPLACE
/// Font sizes for the device screen
enum ScreenFonts: SwiftProtobuf.Enum {
typealias RawValue = Int
@ -75,6 +75,140 @@ extension ScreenFonts: CaseIterable {
#endif // swift(>=4.2)
///
/// Position with static location information only for NodeDBLite
struct PositionLite {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
///
/// The new preferred location encoding, multiply by 1e-7 to get degrees
/// in floating point
var latitudeI: Int32 = 0
///
/// TODO: REPLACE
var longitudeI: Int32 = 0
///
/// In meters above MSL (but see issue #359)
var altitude: Int32 = 0
///
/// This is usually not sent over the mesh (to save space), but it is sent
/// from the phone so that the local device can set its RTC If it is sent over
/// the mesh (because there are devices on the mesh without GPS), it will only
/// be sent by devices which has a hardware GPS clock.
/// seconds since 1970
var time: UInt32 = 0
///
/// TODO: REPLACE
var locationSource: Position.LocSource = .locUnset
var unknownFields = SwiftProtobuf.UnknownStorage()
init() {}
}
struct NodeInfoLite {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
///
/// The node number
var num: UInt32 {
get {return _storage._num}
set {_uniqueStorage()._num = newValue}
}
///
/// The user info for this node
var user: User {
get {return _storage._user ?? User()}
set {_uniqueStorage()._user = newValue}
}
/// Returns true if `user` has been explicitly set.
var hasUser: Bool {return _storage._user != nil}
/// Clears the value of `user`. Subsequent reads from it will return its default value.
mutating func clearUser() {_uniqueStorage()._user = nil}
///
/// This position data. Note: before 1.2.14 we would also store the last time we've heard from this node in position.time, that is no longer true.
/// Position.time now indicates the last time we received a POSITION from that node.
var position: PositionLite {
get {return _storage._position ?? PositionLite()}
set {_uniqueStorage()._position = newValue}
}
/// Returns true if `position` has been explicitly set.
var hasPosition: Bool {return _storage._position != nil}
/// Clears the value of `position`. Subsequent reads from it will return its default value.
mutating func clearPosition() {_uniqueStorage()._position = nil}
///
/// Returns the Signal-to-noise ratio (SNR) of the last received message,
/// as measured by the receiver. Return SNR of the last received message in dB
var snr: Float {
get {return _storage._snr}
set {_uniqueStorage()._snr = newValue}
}
///
/// Set to indicate the last time we received a packet from this node
var lastHeard: UInt32 {
get {return _storage._lastHeard}
set {_uniqueStorage()._lastHeard = newValue}
}
///
/// The latest device metrics for the node.
var deviceMetrics: DeviceMetrics {
get {return _storage._deviceMetrics ?? DeviceMetrics()}
set {_uniqueStorage()._deviceMetrics = newValue}
}
/// Returns true if `deviceMetrics` has been explicitly set.
var hasDeviceMetrics: Bool {return _storage._deviceMetrics != nil}
/// Clears the value of `deviceMetrics`. Subsequent reads from it will return its default value.
mutating func clearDeviceMetrics() {_uniqueStorage()._deviceMetrics = nil}
///
/// local channel index we heard that node on. Only populated if its not the default channel.
var channel: UInt32 {
get {return _storage._channel}
set {_uniqueStorage()._channel = newValue}
}
///
/// True if we witnessed the node over MQTT instead of LoRA transport
var viaMqtt: Bool {
get {return _storage._viaMqtt}
set {_uniqueStorage()._viaMqtt = newValue}
}
///
/// Number of hops away from us this node is (0 if adjacent)
var hopsAway: UInt32 {
get {return _storage._hopsAway}
set {_uniqueStorage()._hopsAway = newValue}
}
///
/// True if node is in our favorites list
/// Persists between NodeDB internal clean ups
var isFavorite: Bool {
get {return _storage._isFavorite}
set {_uniqueStorage()._isFavorite = newValue}
}
var unknownFields = SwiftProtobuf.UnknownStorage()
init() {}
fileprivate var _storage = _StorageClass.defaultInstance
}
///
/// This message is never sent over the wire, but it is used for serializing DB
/// state to flash in the device code
@ -187,140 +321,6 @@ struct DeviceState {
fileprivate var _storage = _StorageClass.defaultInstance
}
struct NodeInfoLite {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
///
/// The node number
var num: UInt32 {
get {return _storage._num}
set {_uniqueStorage()._num = newValue}
}
///
/// The user info for this node
var user: User {
get {return _storage._user ?? User()}
set {_uniqueStorage()._user = newValue}
}
/// Returns true if `user` has been explicitly set.
var hasUser: Bool {return _storage._user != nil}
/// Clears the value of `user`. Subsequent reads from it will return its default value.
mutating func clearUser() {_uniqueStorage()._user = nil}
///
/// This position data. Note: before 1.2.14 we would also store the last time we've heard from this node in position.time, that is no longer true.
/// Position.time now indicates the last time we received a POSITION from that node.
var position: PositionLite {
get {return _storage._position ?? PositionLite()}
set {_uniqueStorage()._position = newValue}
}
/// Returns true if `position` has been explicitly set.
var hasPosition: Bool {return _storage._position != nil}
/// Clears the value of `position`. Subsequent reads from it will return its default value.
mutating func clearPosition() {_uniqueStorage()._position = nil}
///
/// Returns the Signal-to-noise ratio (SNR) of the last received message,
/// as measured by the receiver. Return SNR of the last received message in dB
var snr: Float {
get {return _storage._snr}
set {_uniqueStorage()._snr = newValue}
}
///
/// Set to indicate the last time we received a packet from this node
var lastHeard: UInt32 {
get {return _storage._lastHeard}
set {_uniqueStorage()._lastHeard = newValue}
}
///
/// The latest device metrics for the node.
var deviceMetrics: DeviceMetrics {
get {return _storage._deviceMetrics ?? DeviceMetrics()}
set {_uniqueStorage()._deviceMetrics = newValue}
}
/// Returns true if `deviceMetrics` has been explicitly set.
var hasDeviceMetrics: Bool {return _storage._deviceMetrics != nil}
/// Clears the value of `deviceMetrics`. Subsequent reads from it will return its default value.
mutating func clearDeviceMetrics() {_uniqueStorage()._deviceMetrics = nil}
///
/// local channel index we heard that node on. Only populated if its not the default channel.
var channel: UInt32 {
get {return _storage._channel}
set {_uniqueStorage()._channel = newValue}
}
///
/// True if we witnessed the node over MQTT instead of LoRA transport
var viaMqtt: Bool {
get {return _storage._viaMqtt}
set {_uniqueStorage()._viaMqtt = newValue}
}
///
/// Number of hops away from us this node is (0 if adjacent)
var hopsAway: UInt32 {
get {return _storage._hopsAway}
set {_uniqueStorage()._hopsAway = newValue}
}
///
/// True if node is in our favorites list
/// Persists between NodeDB internal clean ups
var isFavorite: Bool {
get {return _storage._isFavorite}
set {_uniqueStorage()._isFavorite = newValue}
}
var unknownFields = SwiftProtobuf.UnknownStorage()
init() {}
fileprivate var _storage = _StorageClass.defaultInstance
}
///
/// Position with static location information only for NodeDBLite
struct PositionLite {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
///
/// The new preferred location encoding, multiply by 1e-7 to get degrees
/// in floating point
var latitudeI: Int32 = 0
///
/// TODO: REPLACE
var longitudeI: Int32 = 0
///
/// In meters above MSL (but see issue #359)
var altitude: Int32 = 0
///
/// This is usually not sent over the mesh (to save space), but it is sent
/// from the phone so that the local device can set its RTC If it is sent over
/// the mesh (because there are devices on the mesh without GPS), it will only
/// be sent by devices which has a hardware GPS clock.
/// seconds since 1970
var time: UInt32 = 0
///
/// TODO: REPLACE
var locationSource: Position.LocSource = .locUnset
var unknownFields = SwiftProtobuf.UnknownStorage()
init() {}
}
///
/// The on-disk saved channels
struct ChannelFile {
@ -407,9 +407,9 @@ struct OEMStore {
#if swift(>=5.5) && canImport(_Concurrency)
extension ScreenFonts: @unchecked Sendable {}
extension DeviceState: @unchecked Sendable {}
extension NodeInfoLite: @unchecked Sendable {}
extension PositionLite: @unchecked Sendable {}
extension NodeInfoLite: @unchecked Sendable {}
extension DeviceState: @unchecked Sendable {}
extension ChannelFile: @unchecked Sendable {}
extension OEMStore: @unchecked Sendable {}
#endif // swift(>=5.5) && canImport(_Concurrency)
@ -426,141 +426,57 @@ extension ScreenFonts: SwiftProtobuf._ProtoNameProviding {
]
}
extension DeviceState: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
static let protoMessageName: String = _protobuf_package + ".DeviceState"
extension PositionLite: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
static let protoMessageName: String = _protobuf_package + ".PositionLite"
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
2: .standard(proto: "my_node"),
3: .same(proto: "owner"),
5: .standard(proto: "receive_queue"),
8: .same(proto: "version"),
7: .standard(proto: "rx_text_message"),
9: .standard(proto: "no_save"),
11: .standard(proto: "did_gps_reset"),
12: .standard(proto: "rx_waypoint"),
13: .standard(proto: "node_remote_hardware_pins"),
14: .standard(proto: "node_db_lite"),
1: .standard(proto: "latitude_i"),
2: .standard(proto: "longitude_i"),
3: .same(proto: "altitude"),
4: .same(proto: "time"),
5: .standard(proto: "location_source"),
]
fileprivate class _StorageClass {
var _myNode: MyNodeInfo? = nil
var _owner: User? = nil
var _receiveQueue: [MeshPacket] = []
var _version: UInt32 = 0
var _rxTextMessage: MeshPacket? = nil
var _noSave: Bool = false
var _didGpsReset: Bool = false
var _rxWaypoint: MeshPacket? = nil
var _nodeRemoteHardwarePins: [NodeRemoteHardwarePin] = []
var _nodeDbLite: [NodeInfoLite] = []
static let defaultInstance = _StorageClass()
private init() {}
init(copying source: _StorageClass) {
_myNode = source._myNode
_owner = source._owner
_receiveQueue = source._receiveQueue
_version = source._version
_rxTextMessage = source._rxTextMessage
_noSave = source._noSave
_didGpsReset = source._didGpsReset
_rxWaypoint = source._rxWaypoint
_nodeRemoteHardwarePins = source._nodeRemoteHardwarePins
_nodeDbLite = source._nodeDbLite
}
}
fileprivate mutating func _uniqueStorage() -> _StorageClass {
if !isKnownUniquelyReferenced(&_storage) {
_storage = _StorageClass(copying: _storage)
}
return _storage
}
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
_ = _uniqueStorage()
try withExtendedLifetime(_storage) { (_storage: _StorageClass) in
while let fieldNumber = try decoder.nextFieldNumber() {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every case branch when no optimizations are
// enabled. https://github.com/apple/swift-protobuf/issues/1034
switch fieldNumber {
case 2: try { try decoder.decodeSingularMessageField(value: &_storage._myNode) }()
case 3: try { try decoder.decodeSingularMessageField(value: &_storage._owner) }()
case 5: try { try decoder.decodeRepeatedMessageField(value: &_storage._receiveQueue) }()
case 7: try { try decoder.decodeSingularMessageField(value: &_storage._rxTextMessage) }()
case 8: try { try decoder.decodeSingularUInt32Field(value: &_storage._version) }()
case 9: try { try decoder.decodeSingularBoolField(value: &_storage._noSave) }()
case 11: try { try decoder.decodeSingularBoolField(value: &_storage._didGpsReset) }()
case 12: try { try decoder.decodeSingularMessageField(value: &_storage._rxWaypoint) }()
case 13: try { try decoder.decodeRepeatedMessageField(value: &_storage._nodeRemoteHardwarePins) }()
case 14: try { try decoder.decodeRepeatedMessageField(value: &_storage._nodeDbLite) }()
default: break
}
while let fieldNumber = try decoder.nextFieldNumber() {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every case branch when no optimizations are
// enabled. https://github.com/apple/swift-protobuf/issues/1034
switch fieldNumber {
case 1: try { try decoder.decodeSingularSFixed32Field(value: &self.latitudeI) }()
case 2: try { try decoder.decodeSingularSFixed32Field(value: &self.longitudeI) }()
case 3: try { try decoder.decodeSingularInt32Field(value: &self.altitude) }()
case 4: try { try decoder.decodeSingularFixed32Field(value: &self.time) }()
case 5: try { try decoder.decodeSingularEnumField(value: &self.locationSource) }()
default: break
}
}
}
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
try withExtendedLifetime(_storage) { (_storage: _StorageClass) in
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every if/case branch local when no optimizations
// are enabled. https://github.com/apple/swift-protobuf/issues/1034 and
// https://github.com/apple/swift-protobuf/issues/1182
try { if let v = _storage._myNode {
try visitor.visitSingularMessageField(value: v, fieldNumber: 2)
} }()
try { if let v = _storage._owner {
try visitor.visitSingularMessageField(value: v, fieldNumber: 3)
} }()
if !_storage._receiveQueue.isEmpty {
try visitor.visitRepeatedMessageField(value: _storage._receiveQueue, fieldNumber: 5)
}
try { if let v = _storage._rxTextMessage {
try visitor.visitSingularMessageField(value: v, fieldNumber: 7)
} }()
if _storage._version != 0 {
try visitor.visitSingularUInt32Field(value: _storage._version, fieldNumber: 8)
}
if _storage._noSave != false {
try visitor.visitSingularBoolField(value: _storage._noSave, fieldNumber: 9)
}
if _storage._didGpsReset != false {
try visitor.visitSingularBoolField(value: _storage._didGpsReset, fieldNumber: 11)
}
try { if let v = _storage._rxWaypoint {
try visitor.visitSingularMessageField(value: v, fieldNumber: 12)
} }()
if !_storage._nodeRemoteHardwarePins.isEmpty {
try visitor.visitRepeatedMessageField(value: _storage._nodeRemoteHardwarePins, fieldNumber: 13)
}
if !_storage._nodeDbLite.isEmpty {
try visitor.visitRepeatedMessageField(value: _storage._nodeDbLite, fieldNumber: 14)
}
if self.latitudeI != 0 {
try visitor.visitSingularSFixed32Field(value: self.latitudeI, fieldNumber: 1)
}
if self.longitudeI != 0 {
try visitor.visitSingularSFixed32Field(value: self.longitudeI, fieldNumber: 2)
}
if self.altitude != 0 {
try visitor.visitSingularInt32Field(value: self.altitude, fieldNumber: 3)
}
if self.time != 0 {
try visitor.visitSingularFixed32Field(value: self.time, fieldNumber: 4)
}
if self.locationSource != .locUnset {
try visitor.visitSingularEnumField(value: self.locationSource, fieldNumber: 5)
}
try unknownFields.traverse(visitor: &visitor)
}
static func ==(lhs: DeviceState, rhs: DeviceState) -> Bool {
if lhs._storage !== rhs._storage {
let storagesAreEqual: Bool = withExtendedLifetime((lhs._storage, rhs._storage)) { (_args: (_StorageClass, _StorageClass)) in
let _storage = _args.0
let rhs_storage = _args.1
if _storage._myNode != rhs_storage._myNode {return false}
if _storage._owner != rhs_storage._owner {return false}
if _storage._receiveQueue != rhs_storage._receiveQueue {return false}
if _storage._version != rhs_storage._version {return false}
if _storage._rxTextMessage != rhs_storage._rxTextMessage {return false}
if _storage._noSave != rhs_storage._noSave {return false}
if _storage._didGpsReset != rhs_storage._didGpsReset {return false}
if _storage._rxWaypoint != rhs_storage._rxWaypoint {return false}
if _storage._nodeRemoteHardwarePins != rhs_storage._nodeRemoteHardwarePins {return false}
if _storage._nodeDbLite != rhs_storage._nodeDbLite {return false}
return true
}
if !storagesAreEqual {return false}
}
static func ==(lhs: PositionLite, rhs: PositionLite) -> Bool {
if lhs.latitudeI != rhs.latitudeI {return false}
if lhs.longitudeI != rhs.longitudeI {return false}
if lhs.altitude != rhs.altitude {return false}
if lhs.time != rhs.time {return false}
if lhs.locationSource != rhs.locationSource {return false}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}
@ -706,57 +622,141 @@ extension NodeInfoLite: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat
}
}
extension PositionLite: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
static let protoMessageName: String = _protobuf_package + ".PositionLite"
extension DeviceState: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
static let protoMessageName: String = _protobuf_package + ".DeviceState"
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .standard(proto: "latitude_i"),
2: .standard(proto: "longitude_i"),
3: .same(proto: "altitude"),
4: .same(proto: "time"),
5: .standard(proto: "location_source"),
2: .standard(proto: "my_node"),
3: .same(proto: "owner"),
5: .standard(proto: "receive_queue"),
8: .same(proto: "version"),
7: .standard(proto: "rx_text_message"),
9: .standard(proto: "no_save"),
11: .standard(proto: "did_gps_reset"),
12: .standard(proto: "rx_waypoint"),
13: .standard(proto: "node_remote_hardware_pins"),
14: .standard(proto: "node_db_lite"),
]
fileprivate class _StorageClass {
var _myNode: MyNodeInfo? = nil
var _owner: User? = nil
var _receiveQueue: [MeshPacket] = []
var _version: UInt32 = 0
var _rxTextMessage: MeshPacket? = nil
var _noSave: Bool = false
var _didGpsReset: Bool = false
var _rxWaypoint: MeshPacket? = nil
var _nodeRemoteHardwarePins: [NodeRemoteHardwarePin] = []
var _nodeDbLite: [NodeInfoLite] = []
static let defaultInstance = _StorageClass()
private init() {}
init(copying source: _StorageClass) {
_myNode = source._myNode
_owner = source._owner
_receiveQueue = source._receiveQueue
_version = source._version
_rxTextMessage = source._rxTextMessage
_noSave = source._noSave
_didGpsReset = source._didGpsReset
_rxWaypoint = source._rxWaypoint
_nodeRemoteHardwarePins = source._nodeRemoteHardwarePins
_nodeDbLite = source._nodeDbLite
}
}
fileprivate mutating func _uniqueStorage() -> _StorageClass {
if !isKnownUniquelyReferenced(&_storage) {
_storage = _StorageClass(copying: _storage)
}
return _storage
}
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
while let fieldNumber = try decoder.nextFieldNumber() {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every case branch when no optimizations are
// enabled. https://github.com/apple/swift-protobuf/issues/1034
switch fieldNumber {
case 1: try { try decoder.decodeSingularSFixed32Field(value: &self.latitudeI) }()
case 2: try { try decoder.decodeSingularSFixed32Field(value: &self.longitudeI) }()
case 3: try { try decoder.decodeSingularInt32Field(value: &self.altitude) }()
case 4: try { try decoder.decodeSingularFixed32Field(value: &self.time) }()
case 5: try { try decoder.decodeSingularEnumField(value: &self.locationSource) }()
default: break
_ = _uniqueStorage()
try withExtendedLifetime(_storage) { (_storage: _StorageClass) in
while let fieldNumber = try decoder.nextFieldNumber() {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every case branch when no optimizations are
// enabled. https://github.com/apple/swift-protobuf/issues/1034
switch fieldNumber {
case 2: try { try decoder.decodeSingularMessageField(value: &_storage._myNode) }()
case 3: try { try decoder.decodeSingularMessageField(value: &_storage._owner) }()
case 5: try { try decoder.decodeRepeatedMessageField(value: &_storage._receiveQueue) }()
case 7: try { try decoder.decodeSingularMessageField(value: &_storage._rxTextMessage) }()
case 8: try { try decoder.decodeSingularUInt32Field(value: &_storage._version) }()
case 9: try { try decoder.decodeSingularBoolField(value: &_storage._noSave) }()
case 11: try { try decoder.decodeSingularBoolField(value: &_storage._didGpsReset) }()
case 12: try { try decoder.decodeSingularMessageField(value: &_storage._rxWaypoint) }()
case 13: try { try decoder.decodeRepeatedMessageField(value: &_storage._nodeRemoteHardwarePins) }()
case 14: try { try decoder.decodeRepeatedMessageField(value: &_storage._nodeDbLite) }()
default: break
}
}
}
}
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
if self.latitudeI != 0 {
try visitor.visitSingularSFixed32Field(value: self.latitudeI, fieldNumber: 1)
}
if self.longitudeI != 0 {
try visitor.visitSingularSFixed32Field(value: self.longitudeI, fieldNumber: 2)
}
if self.altitude != 0 {
try visitor.visitSingularInt32Field(value: self.altitude, fieldNumber: 3)
}
if self.time != 0 {
try visitor.visitSingularFixed32Field(value: self.time, fieldNumber: 4)
}
if self.locationSource != .locUnset {
try visitor.visitSingularEnumField(value: self.locationSource, fieldNumber: 5)
try withExtendedLifetime(_storage) { (_storage: _StorageClass) in
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every if/case branch local when no optimizations
// are enabled. https://github.com/apple/swift-protobuf/issues/1034 and
// https://github.com/apple/swift-protobuf/issues/1182
try { if let v = _storage._myNode {
try visitor.visitSingularMessageField(value: v, fieldNumber: 2)
} }()
try { if let v = _storage._owner {
try visitor.visitSingularMessageField(value: v, fieldNumber: 3)
} }()
if !_storage._receiveQueue.isEmpty {
try visitor.visitRepeatedMessageField(value: _storage._receiveQueue, fieldNumber: 5)
}
try { if let v = _storage._rxTextMessage {
try visitor.visitSingularMessageField(value: v, fieldNumber: 7)
} }()
if _storage._version != 0 {
try visitor.visitSingularUInt32Field(value: _storage._version, fieldNumber: 8)
}
if _storage._noSave != false {
try visitor.visitSingularBoolField(value: _storage._noSave, fieldNumber: 9)
}
if _storage._didGpsReset != false {
try visitor.visitSingularBoolField(value: _storage._didGpsReset, fieldNumber: 11)
}
try { if let v = _storage._rxWaypoint {
try visitor.visitSingularMessageField(value: v, fieldNumber: 12)
} }()
if !_storage._nodeRemoteHardwarePins.isEmpty {
try visitor.visitRepeatedMessageField(value: _storage._nodeRemoteHardwarePins, fieldNumber: 13)
}
if !_storage._nodeDbLite.isEmpty {
try visitor.visitRepeatedMessageField(value: _storage._nodeDbLite, fieldNumber: 14)
}
}
try unknownFields.traverse(visitor: &visitor)
}
static func ==(lhs: PositionLite, rhs: PositionLite) -> Bool {
if lhs.latitudeI != rhs.latitudeI {return false}
if lhs.longitudeI != rhs.longitudeI {return false}
if lhs.altitude != rhs.altitude {return false}
if lhs.time != rhs.time {return false}
if lhs.locationSource != rhs.locationSource {return false}
static func ==(lhs: DeviceState, rhs: DeviceState) -> Bool {
if lhs._storage !== rhs._storage {
let storagesAreEqual: Bool = withExtendedLifetime((lhs._storage, rhs._storage)) { (_args: (_StorageClass, _StorageClass)) in
let _storage = _args.0
let rhs_storage = _args.1
if _storage._myNode != rhs_storage._myNode {return false}
if _storage._owner != rhs_storage._owner {return false}
if _storage._receiveQueue != rhs_storage._receiveQueue {return false}
if _storage._version != rhs_storage._version {return false}
if _storage._rxTextMessage != rhs_storage._rxTextMessage {return false}
if _storage._noSave != rhs_storage._noSave {return false}
if _storage._didGpsReset != rhs_storage._didGpsReset {return false}
if _storage._rxWaypoint != rhs_storage._rxWaypoint {return false}
if _storage._nodeRemoteHardwarePins != rhs_storage._nodeRemoteHardwarePins {return false}
if _storage._nodeDbLite != rhs_storage._nodeDbLite {return false}
return true
}
if !storagesAreEqual {return false}
}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}

View file

@ -256,6 +256,15 @@ enum HardwareModel: SwiftProtobuf.Enum {
/// Older "V1.0" Variant
case heltecWirelessTrackerV10 // = 58
///
/// unPhone with ESP32-S3, TFT touchscreen, LSM6DS3TR-C accelerometer and gyroscope
case unphone // = 59
///
/// Teledatics TD-LORAC NRF52840 based M.2 LoRA module
/// Compatible with the TD-WRLS development board
case tdLorac // = 60
///
/// ------------------------------------------------------------------------------------------------------------------------------------------
/// 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.
@ -323,6 +332,8 @@ enum HardwareModel: SwiftProtobuf.Enum {
case 56: self = .chatter2
case 57: self = .heltecWirelessPaperV10
case 58: self = .heltecWirelessTrackerV10
case 59: self = .unphone
case 60: self = .tdLorac
case 255: self = .privateHw
default: self = .UNRECOGNIZED(rawValue)
}
@ -384,6 +395,8 @@ enum HardwareModel: SwiftProtobuf.Enum {
case .chatter2: return 56
case .heltecWirelessPaperV10: return 57
case .heltecWirelessTrackerV10: return 58
case .unphone: return 59
case .tdLorac: return 60
case .privateHw: return 255
case .UNRECOGNIZED(let i): return i
}
@ -450,6 +463,8 @@ extension HardwareModel: CaseIterable {
.chatter2,
.heltecWirelessPaperV10,
.heltecWirelessTrackerV10,
.unphone,
.tdLorac,
.privateHw,
]
}
@ -2745,6 +2760,8 @@ extension HardwareModel: SwiftProtobuf._ProtoNameProviding {
56: .same(proto: "CHATTER_2"),
57: .same(proto: "HELTEC_WIRELESS_PAPER_V1_0"),
58: .same(proto: "HELTEC_WIRELESS_TRACKER_V1_0"),
59: .same(proto: "UNPHONE"),
60: .same(proto: "TD_LORAC"),
255: .same(proto: "PRIVATE_HW"),
]
}

View file

@ -209,7 +209,7 @@ struct Connect: View {
}.padding([.bottom, .top])
}
}
.confirmationDialog("Connecting to a new radio will clear all local app data on the phone and will reset all app specific settings.", isPresented: $presentingSwitchPreferredPeripheral, titleVisibility: .visible) {
.confirmationDialog("Connecting to a new radio will clear all local app data on the phone.", isPresented: $presentingSwitchPreferredPeripheral, titleVisibility: .visible) {
Button("Connect to new radio?", role: .destructive) {
UserDefaults.preferredPeripheralId = selectedPeripherialId
@ -218,7 +218,6 @@ struct Connect: View {
bleManager.disconnectPeripheral()
}
clearCoreDataDatabase(context: context)
UserDefaults.standard.reset()
let radio = bleManager.peripherals.first(where: { $0.peripheral.identifier.uuidString == selectedPeripherialId })
if radio != nil {
@ -260,7 +259,7 @@ struct Connect: View {
.navigationTitle("bluetooth")
.navigationBarItems(leading: MeshtasticLogo(), trailing:
ZStack {
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?", mqttProxyConnected: bleManager.mqttProxyConnected)
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?", mqttProxyConnected: bleManager.mqttProxyConnected, mqttTopic: bleManager.mqttManager.topic)
})
}
.sheet(isPresented: $invalidFirmwareVersion, onDismiss: didDismissSheet) {

View file

@ -5,56 +5,58 @@ A view draws the indicator used in the upper right corner for views using BLE
import SwiftUI
struct ConnectedDevice: View {
var bluetoothOn: Bool
var deviceConnected: Bool
var name: String
var mqttProxyEnabled: Bool = false
var mqttProxyConnected: Bool = false
var phoneOnly: Bool = false
var mqttProxyConnected: Bool = false
var mqttUplinkEnabled: Bool = false
var mqttDownlinkEnabled: Bool = false
var mqttTopic: String = ""
var phoneOnly: Bool = false
var body: some View {
HStack {
if (phoneOnly && UIDevice.current.userInterfaceIdiom == .phone) || !phoneOnly {
if bluetoothOn {
if deviceConnected && (mqttProxyEnabled || mqttProxyConnected) {
if (mqttProxyConnected || mqttProxyEnabled) {
Image(systemName: "iphone.gen3.radiowaves.left.and.right.circle.fill")
.imageScale(.large)
.foregroundColor(mqttProxyConnected ? .green : .gray)
.symbolRenderingMode(.hierarchical)
}
}
if deviceConnected {
Image(systemName: "antenna.radiowaves.left.and.right.circle.fill")
.imageScale(.large)
.foregroundColor(.green)
.symbolRenderingMode(.hierarchical)
Text(name).font(name.isEmoji() ? .title : .callout).foregroundColor(.gray)
} else {
Image(systemName: "antenna.radiowaves.left.and.right.slash")
.imageScale(.medium)
.foregroundColor(.red)
.symbolRenderingMode(.hierarchical)
}
} else {
Text("bluetooth.off").font(.subheadline).foregroundColor(.red)
}
}
if (phoneOnly && UIDevice.current.userInterfaceIdiom == .phone) || !phoneOnly {
if bluetoothOn {
if deviceConnected {
MQTTIcon(connected: mqttProxyConnected, uplink: mqttUplinkEnabled, downlink: mqttDownlinkEnabled, topic: mqttTopic)
Image(systemName: "antenna.radiowaves.left.and.right.circle.fill")
.imageScale(.large)
.foregroundColor(.green)
.symbolRenderingMode(.hierarchical)
Text(name).font(name.isEmoji() ? .title : .callout).foregroundColor(.gray)
} else {
Image(systemName: "antenna.radiowaves.left.and.right.slash")
.imageScale(.medium)
.foregroundColor(.red)
.symbolRenderingMode(.hierarchical)
}
} else {
Text("bluetooth.off").font(.subheadline).foregroundColor(.red)
}
}
}
}
}
struct ConnectedDevice_Previews: PreviewProvider {
static var previews: some View {
ConnectedDevice(bluetoothOn: true, deviceConnected: true, name: "MEMO", mqttProxyConnected: true)
.previewLayout(.fixed(width: 80, height: 70))
ConnectedDevice(bluetoothOn: true, deviceConnected: false, name: "86D4", mqttProxyConnected: false)
.previewLayout(.fixed(width: 80, height: 70))
}
VStack (alignment: .trailing) {
ConnectedDevice(bluetoothOn: true, deviceConnected: true, name: "MEMO", mqttProxyConnected: true)
ConnectedDevice(bluetoothOn: true, deviceConnected: true, name: "MEMO", mqttProxyConnected: false, mqttUplinkEnabled: true, mqttDownlinkEnabled: true)
ConnectedDevice(bluetoothOn: true, deviceConnected: true, name: "MEMO", mqttProxyConnected: true, mqttUplinkEnabled: true, mqttDownlinkEnabled: true, mqttTopic: "msh/US/2/e/#")
ConnectedDevice(bluetoothOn: true, deviceConnected: true, name: "MEMO", mqttProxyConnected: false, mqttUplinkEnabled: true, mqttDownlinkEnabled: false)
ConnectedDevice(bluetoothOn: true, deviceConnected: true, name: "MEMO", mqttProxyConnected: true, mqttUplinkEnabled: true, mqttDownlinkEnabled: false)
ConnectedDevice(bluetoothOn: true, deviceConnected: true, name: "MEMO", mqttProxyConnected: false, mqttUplinkEnabled: false, mqttDownlinkEnabled: true)
ConnectedDevice(bluetoothOn: true, deviceConnected: true, name: "MEMO", mqttProxyConnected: true, mqttUplinkEnabled: false, mqttDownlinkEnabled: true)
ConnectedDevice(bluetoothOn: true, deviceConnected: true, name: "MEMO", mqttProxyConnected: true)
ConnectedDevice(bluetoothOn: true, deviceConnected: false, name: "MEMO", mqttProxyConnected: false)
}.previewLayout(.fixed(width: 150, height: 275))
}
}

View file

@ -0,0 +1,54 @@
//
// MQTTIcon.swift
// Meshtastic
//
// Created by Matthew Davies on 4/1/24.
//
import Foundation
import SwiftUI
struct MQTTIcon: View {
var connected: Bool = false
var uplink: Bool = false
var downlink: Bool = false
var topic: String = ""
@State var isPopoverOpen = false
var body: some View {
Button( action: {
if(topic.length > 0) {self.isPopoverOpen.toggle()}
} ) {
// the last one defaults to just showing up/down if it isn't specified b/c on the mqtt config screen, there's no information about uplink/downlink and no good alternative icon
Image(systemName: uplink && downlink ? "arrow.up.arrow.down.circle.fill" : uplink ? "arrow.up.circle.fill" : downlink ? "arrow.down.circle.fill" : "arrow.up.arrow.down.circle.fill")
.imageScale(.large)
.foregroundColor(connected ? .green : .gray)
.symbolRenderingMode(.hierarchical)
}.popover(isPresented: self.$isPopoverOpen, content: {
VStack(spacing: 0.5) {
Text("Subscribed to topic: " + topic)
.padding(20)
Button("Close", action: { self.isPopoverOpen = false }).padding([.bottom], 20)
}
})
}
}
struct MQTTIcon_Previews: PreviewProvider {
static var previews: some View {
VStack {
MQTTIcon(connected: true)
MQTTIcon(connected: false)
MQTTIcon(connected: true, uplink: true, downlink: true)
MQTTIcon(connected: false, uplink: true, downlink: true)
MQTTIcon(connected: true, uplink: true)
MQTTIcon(connected: false, uplink: true)
MQTTIcon(connected: true, downlink: true)
MQTTIcon(connected: false, downlink: true)
}.previewLayout(.fixed(width: 25, height: 220))
}
}

View file

@ -53,11 +53,7 @@ struct WaypointFormMapKit: View {
let totalBytes = name.utf8.count
// Only mess with the value if it is too big
if totalBytes > 30 {
let firstNBytes = Data(name.utf8.prefix(30))
if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) {
// Set the name back to the last place where it was the right size
name = maxBytesString
}
name = String(name.dropLast())
}
})
}
@ -74,11 +70,7 @@ struct WaypointFormMapKit: View {
let totalBytes = description.utf8.count
// Only mess with the value if it is too big
if totalBytes > 100 {
let firstNBytes = Data(description.utf8.prefix(100))
if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) {
// Set the name back to the last place where it was the right size
description = maxBytesString
}
description = String(description.dropLast())
}
})
}

View file

@ -21,7 +21,7 @@ struct ChannelList: View {
@State private var isPresentingTraceRouteSentAlert = false
var restrictedChannels = ["admin", "gpio", "mqtt", "serial"]
var restrictedChannels = ["gpio", "mqtt", "serial"]
var body: some View {
let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMdd", options: 0, locale: Locale.current)

View file

@ -157,8 +157,12 @@ struct ChannelMessageList: View {
bluetoothOn: bleManager.isSwitchedOn,
deviceConnected: bleManager.connectedPeripheral != nil,
name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?",
mqttProxyEnabled: channel.uplinkEnabled || channel.downlinkEnabled,
mqttProxyConnected: channel.uplinkEnabled || channel.downlinkEnabled ? bleManager.mqttProxyConnected : false
// mqttProxyConnected defaults to false, so if it's not enabled it will still be false
mqttProxyConnected: bleManager.mqttProxyConnected && (channel.uplinkEnabled || channel.downlinkEnabled),
mqttUplinkEnabled: channel.uplinkEnabled,
mqttDownlinkEnabled: channel.downlinkEnabled,
mqttTopic: bleManager.mqttManager.topic
)
}
}

View file

@ -33,13 +33,7 @@ struct TextMessageField: View {
totalBytes = value.utf8.count
// Only mess with the value if it is too big
if totalBytes > Self.maxbytes {
let firstNBytes = Data(typingMessage.utf8.prefix(Self.maxbytes))
if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) {
// Set the message back to the last place where it was the right size
typingMessage = maxBytesString
} else {
print("not a valid UTF-8 sequence")
}
typingMessage = String(typingMessage.dropLast())
}
})
.keyboardType(.default)

View file

@ -63,11 +63,7 @@ struct WaypointForm: View {
let totalBytes = name.utf8.count
// Only mess with the value if it is too big
if totalBytes > 30 {
let firstNBytes = Data(name.utf8.prefix(30))
if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) {
// Set the name back to the last place where it was the right size
name = maxBytesString
}
name = String(name.dropLast())
}
})
}
@ -84,11 +80,7 @@ struct WaypointForm: View {
let totalBytes = description.utf8.count
// Only mess with the value if it is too big
if totalBytes > 100 {
let firstNBytes = Data(description.utf8.prefix(100))
if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) {
// Set the name back to the last place where it was the right size
description = maxBytesString
}
description = String(description.dropLast())
}
})
}
@ -203,8 +195,8 @@ struct WaypointForm: View {
newWaypoint.id = UInt32(waypoint.id)
newWaypoint.name = name.count > 0 ? name : "Dropped Pin"
newWaypoint.description_p = description
newWaypoint.latitudeI = waypoint.longitudeI
newWaypoint.longitudeI = waypoint.latitudeI
newWaypoint.latitudeI = waypoint.latitudeI
newWaypoint.longitudeI = waypoint.longitudeI
// Unicode scalar value for the icon emoji string
let unicodeScalers = icon.unicodeScalars
// First element as an UInt32
@ -217,7 +209,7 @@ struct WaypointForm: View {
newWaypoint.lockedTo = UInt32(lockedTo)
}
}
newWaypoint.expire = UInt32(expire.timeIntervalSince1970)
newWaypoint.expire = UInt32(1)
if bleManager.sendWaypoint(waypoint: newWaypoint) {
bleManager.context!.delete(waypoint)

View file

@ -47,11 +47,7 @@ struct ChannelForm: View {
let totalBytes = channelName.utf8.count
// Only mess with the value if it is too big
if totalBytes > 11 {
let firstNBytes = Data(channelName.utf8.prefix(11))
if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) {
// Set the channelName back to the last place where it was the right size
channelName = maxBytesString
}
channelName = String(channelName.dropLast())
}
hasChanges = true
})

View file

@ -26,6 +26,7 @@ struct DeviceConfig: View {
@State var nodeInfoBroadcastSecs = 10800
@State var doubleTapAsButtonPress = false
@State var isManaged = false
@State var tzdef = ""
var body: some View {
VStack {
@ -86,6 +87,26 @@ struct DeviceConfig: View {
Label("Debug Log", systemImage: "ant.fill")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
VStack(alignment: .leading) {
HStack {
Label("Time Zone", systemImage: "clock.badge.exclamationmark")
TextField("Time Zone", text: $tzdef)
.foregroundColor(.gray)
.onChange(of: tzdef, perform: { _ in
let totalBytes = tzdef.utf8.count
// Only mess with the value if it is too big
if totalBytes > 63 {
tzdef = String(tzdef.dropLast())
}
})
.foregroundColor(.gray)
}
.keyboardType(.default)
.disableAutocorrection(true)
Text("Time zone for dates on the device screen and log.")
.foregroundColor(.gray)
.font(.callout)
}
}
Section(header: Text("GPIO")) {
Picker("Button GPIO", selection: $buttonGPIO) {
@ -179,6 +200,7 @@ struct DeviceConfig: View {
dc.nodeInfoBroadcastSecs = UInt32(nodeInfoBroadcastSecs)
dc.doubleTapAsButtonPress = doubleTapAsButtonPress
dc.isManaged = isManaged
dc.tzdef = tzdef
if isManaged {
serialEnabled = false
debugLogEnabled = false
@ -259,6 +281,11 @@ struct DeviceConfig: View {
if newIsManaged != node!.deviceConfig!.isManaged { hasChanges = true }
}
}
.onChange(of: tzdef) { newTzdef in
if node != nil && node?.deviceConfig != nil {
if newTzdef != node!.deviceConfig!.tzdef { hasChanges = true }
}
}
}
func setDeviceValues() {
self.deviceRole = Int(node?.deviceConfig?.role ?? 0)
@ -273,6 +300,11 @@ struct DeviceConfig: View {
}
self.doubleTapAsButtonPress = node?.deviceConfig?.doubleTapAsButtonPress ?? false
self.isManaged = node?.deviceConfig?.isManaged ?? false
self.hasChanges = false
if self.tzdef.isEmpty {
self.tzdef = TimeZone.current.posixDescription
self.hasChanges = true
} else {
self.hasChanges = false
}
}
}

View file

@ -74,12 +74,7 @@ struct CannedMessagesConfig: View {
let totalBytes = messages.utf8.count
// Only mess with the value if it is too big
if totalBytes > 198 {
let firstNBytes = Data(messages.utf8.prefix(198))
if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) {
// Set the shortName back to the last place where it was the right size
messages = maxBytesString
}
messages = String(messages.dropLast())
}
hasMessagesChanges = true
})

View file

@ -94,12 +94,7 @@ struct DetectionSensorConfig: View {
let totalBytes = name.utf8.count
// Only mess with the value if it is too big
if totalBytes > 20 {
let firstNBytes = Data(name.utf8.prefix(20))
if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) {
// Set the shortName back to the last place where it was the right size
name = maxBytesString
}
name = String(name.dropLast())
}
})
}

View file

@ -67,6 +67,12 @@ struct MQTTConfig: View {
if enabled && proxyToClientEnabled && node!.mqttConfig!.proxyToClientEnabled == true {
Toggle(isOn: $mqttConnected) {
Label(mqttConnected ? "mqtt.disconnect".localized : "mqtt.connect".localized, systemImage: "server.rack")
if bleManager.mqttError.count > 0 {
Text(bleManager.mqttError)
.fixedSize(horizontal: false, vertical: true)
.foregroundColor(.red)
}
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
}
@ -139,11 +145,7 @@ struct MQTTConfig: View {
let totalBytes = root.utf8.count
// Only mess with the value if it is too big
if totalBytes > 30 {
let firstNBytes = Data(root.utf8.prefix(30))
if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) {
// Set the shortName back to the last place where it was the right size
root = maxBytesString
}
root = String(root.dropLast())
}
})
.foregroundColor(.gray)
@ -181,11 +183,7 @@ struct MQTTConfig: View {
let totalBytes = address.utf8.count
// Only mess with the value if it is too big
if totalBytes > 62 {
let firstNBytes = Data(username.utf8.prefix(62))
if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) {
// Set the shortName back to the last place where it was the right size
address = maxBytesString
}
address = String(address.dropLast())
}
hasChanges = true
})
@ -205,14 +203,7 @@ struct MQTTConfig: View {
// Only mess with the value if it is too big
if totalBytes > 62 {
let firstNBytes = Data(username.utf8.prefix(62))
if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) {
// Set the shortName back to the last place where it was the right size
username = maxBytesString
}
username = String(username.dropLast())
}
hasChanges = true
})
@ -229,17 +220,9 @@ struct MQTTConfig: View {
.onChange(of: password, perform: { _ in
let totalBytes = password.utf8.count
// Only mess with the value if it is too big
if totalBytes > 62 {
let firstNBytes = Data(password.utf8.prefix(62))
if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) {
// Set the shortName back to the last place where it was the right size
password = maxBytesString
}
password = String(password.dropLast())
}
hasChanges = true
})
@ -289,7 +272,7 @@ struct MQTTConfig: View {
.navigationTitle("mqtt.config")
.navigationBarItems(trailing:
ZStack {
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?", mqttProxyEnabled: self.enabled, mqttProxyConnected: bleManager.mqttProxyConnected)
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?", mqttProxyConnected: bleManager.mqttProxyConnected)
})
.onChange(of: address) { newAddress in
if node != nil && node?.mqttConfig != nil {

View file

@ -35,12 +35,7 @@ struct RtttlConfig: View {
let totalBytes = ringtone.utf8.count
// Only mess with the value if it is too big
if totalBytes > 228 {
let firstNBytes = Data(ringtone.utf8.prefix(228))
if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) {
// Set the ringtone back to the last place where it was the right size
ringtone = maxBytesString
}
ringtone = String(ringtone.dropLast())
}
})
.foregroundColor(.gray)

View file

@ -48,11 +48,7 @@ struct NetworkConfig: View {
let totalBytes = wifiSsid.utf8.count
// Only mess with the value if it is too big
if totalBytes > 32 {
let firstNBytes = Data(wifiSsid.utf8.prefix(32))
if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) {
// Set the shortName back to the last place where it was the right size
wifiSsid = maxBytesString
}
wifiSsid = String(wifiSsid.dropLast())
}
hasChanges = true
})
@ -69,11 +65,7 @@ struct NetworkConfig: View {
let totalBytes = wifiPsk.utf8.count
// Only mess with the value if it is too big
if totalBytes > 63 {
let firstNBytes = Data(wifiPsk.utf8.prefix(63))
if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) {
// Set the shortName back to the last place where it was the right size
wifiPsk = maxBytesString
}
wifiPsk = String(wifiPsk.dropLast())
}
hasChanges = true
})

View file

@ -54,9 +54,9 @@ struct ShareChannels: View {
var body: some View {
if #available(iOS 17.0, macOS 14.0, *) {
// VStack {
// TipView(ShareChannelsTip(), arrowEdge: .bottom)
// }
VStack {
TipView(ShareChannelsTip(), arrowEdge: .bottom)
}
}
GeometryReader { bounds in
let smallest = min(bounds.size.width, bounds.size.height)
@ -219,10 +219,10 @@ struct ShareChannels: View {
.resizable()
.scaledToFit()
.frame(
minWidth: smallest * (UIDevice.current.userInterfaceIdiom == .phone ? 0.9 : 0.6),
maxWidth: smallest * (UIDevice.current.userInterfaceIdiom == .phone ? 0.9 : 0.6),
minHeight: smallest * (UIDevice.current.userInterfaceIdiom == .phone ? 0.9 : 0.6),
maxHeight: smallest * (UIDevice.current.userInterfaceIdiom == .phone ? 0.9 : 0.6),
minWidth: smallest * (UIDevice.current.userInterfaceIdiom == .phone ? 0.8 : 0.6),
maxWidth: smallest * (UIDevice.current.userInterfaceIdiom == .phone ? 0.8 : 0.6),
minHeight: smallest * (UIDevice.current.userInterfaceIdiom == .phone ? 0.8 : 0.6),
maxHeight: smallest * (UIDevice.current.userInterfaceIdiom == .phone ? 0.8 : 0.6),
alignment: .top
)
}

View file

@ -52,11 +52,7 @@ struct UserConfig: View {
let totalBytes = longName.utf8.count
// Only mess with the value if it is too big
if totalBytes > (isLicensed ? 6 : 36) {
let firstNBytes = Data(longName.utf8.prefix(isLicensed ? 6 : 36))
if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) {
// Set the longName back to the last place where it was the right size
longName = maxBytesString
}
longName = String(longName.dropLast())
}
})
}
@ -80,11 +76,7 @@ struct UserConfig: View {
let totalBytes = shortName.utf8.count
// Only mess with the value if it is too big
if totalBytes > 4 {
let firstNBytes = Data(shortName.utf8.prefix(4))
if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) {
// Set the shortName back to the last place where it was the right size
shortName = maxBytesString
}
shortName = String(shortName.dropLast())
}
})
.foregroundColor(.gray)

View file

@ -341,7 +341,7 @@
"tip.channels.create.title"="Manage Channels";
"tip.channels.create.message"="Most data on your mesh is sent over the primary channel. You can set up secondary channels to create additional messaging groups secured by their own key. [Channel config tips](https://meshtastic.org/docs/configuration/tips/)";
"tip.channels.share.title"="Sharing Meshtastic Channels";
"tip.channels.share.message"="A Meshtastic QR code contains the LoRa config and channel values needed to communicate. Most mesh activity takes place on the required Primary channel. If you don't share your primary channel your first shared channel becomes the primary channel on the other network. Other channels are for private groups, each with its own key.";
"tip.channels.share.message"="A Meshtastic QR code contains the LoRa config and channel values needed for radios to communicate. You can share a complete channel configuration using the Replace Channels option, if you choose Add Channels your shared channels will be added to the channels on the receiving radio.";
"tip.messages.title"="Messages";
"tip.messages.message"="You can send and receive channel (group chats) and direct messages. From any message you can long press to see available actions like copy, reply, tapback and delete as well as delivery details.";
"twitter"="Twitter";

@ -1 +1 @@
Subproject commit e6b4c590e7c489306c9c44e3ad1fcf62a3efd288
Subproject commit 68720ed8dbcb2c055e3d1ecd4f78d60692f59493

View file

@ -0,0 +1,356 @@
/*
Localizable.strings
Meshtastic
Copyright(c) Garth Vander Houwen on 12/12/22.
*/
"about"="Om";
"about.meshtastic"="Om Meshtastic";
"admin"="Administratör";
"admin.log"="Administratörsmeddelandelogg";
"ago"="sedan";
"airtime"="Sändningstid";
"always.on"="Alltid på";
"ambient.lighting"="Omgivningsbelysning";
"ambient.lighting.config"="Konfiguration av omgivningsbelysning";
"appsettings"="Appinställningar";
"appsettings.provide.location"="Dela plats";
"appsettings.smartposition"="Smart position";
"are.you.sure"="Är du säker?";
"ascii.capable"="ASCII-kompatibel";
"available.radios"="Tillgängliga radioapparater";
"automatic.detection"="Automatisk upptäckt";
"battery.level"="Batterinivå";
"ble.name"="BLE-namn";
"ble.connection.timeout %d %@"="Anslutningen misslyckades efter %d försök att ansluta till %@. Du kan behöva glömma din enhet under Inställningar > Bluetooth.";
"ble.errorcode.6 %@"="%@ Appen kommer automatiskt att återansluta till den föredragna radion om den kommer inom räckhåll igen.";
"ble.errorcode.14 %@"="%@ Detta fel kan vanligtvis inte åtgärdas utan att glömma enheten under Inställningar > Bluetooth och återansluta till radion.";
"ble.errorcode.pin %@"="%@ Försök att ansluta igen och kontrollera PIN-koden noggrant.";
"bluetooth"="Bluetooth";
"bluetooth.off"="Bluetooth är avstängt";
"bluetooth.config"="Bluetooth-konfiguration";
"bluetooth.mode.randompin"="Slumpmässig PIN";
"bluetooth.mode.fixedpin"="Fast PIN";
"bluetooth.mode.nopin"="Ingen PIN (Bara fungerar)";
"bluetooth.pairingmode"="Parläge";
"bluetooth.pin.validation"="BLE-PIN måste vara 6 siffror lång.";
"bytes"="Bytes";
"cancel"="Avbryt";
"canned.messages"="Fördefinierade meddelanden";
"canned.messages.config"="Konfiguration av fördefinierade meddelanden";
"canned.messages.preset.manual"="Manuell konfiguration";
"canned.messages.preset.rakrotary"="RAK Rotary Encoder-modul";
"canned.messages.preset.cardkb"="M5 Stack Card KB / RAK Keypad";
"channel"="Kanal";
"channel.role.disabled"="Inaktiverad";
"channel.role.primary"="Primär";
"channel.role.secondary"="Sekundär";
"channel.utilization"="Kanalutnyttjande";
"channels"="Kanaler";
"clear.app.data"="Rensa appdata";
"clear.log"="Rensa";
"close"="Stäng";
"config.power.settings"="Ström";
"config.power.title"="Strömkonfiguration";
"config.power.section.battery"="Batteri";
"config.power.section.sleep"="Sömn";
"config.power.adc.override"="ADC-överskrivning";
"config.power.adc.multiplier"="Multiplikator";
"config.power.ls.secs"="Intervall för Ljussömn";
"config.power.min.wake.secs"="Minsta Väckningsintervall";
"config.power.saving"="Strömsparläge";
"config.power.saving.description"="Sätter allt i viloläge så mycket som möjligt, för spårnings- och sensorläge kommer detta också inkludera LoRa-radion. Använd inte denna inställning om du vill använda din enhet med mobilappar eller använder en enhet utan en användarknapp.";
"config.power.shutdown.on.power.loss"="Stäng av vid Strömförlust";
"config.power.shutdown.after.secs"="Efter";
"config.power.wait.bluetooth.secs"="Bluetooth Stängs Av Efter";
"config.ringtone"="RTTTL Ringsignal";
"config.ringtone.title"="Ringsignalskonfiguration";
"config.ringtone.label"="Språk för Överföring av Ringsignal";
"config.ringtone.description"="Ringsignalöverföringsspråk (RTTTL) Ringsignalsträng som används av stödda buzzers i externa notifikationer.";
"config.module.paxcounter.settings"="PAX Räknare";
"config.module.paxcounter.title"="PAX Räknare Konfiguration";
"config.module.paxcounter.enabled.description"="När aktiverad räknar PAX-räknarmodulen antalet personer som passerar med WiFi och Bluetooth. Både WiFi och Bluetooth måste vara aktiverade för att PAX-räknaren ska fungera.";
"config.module.paxcounter.updateinterval"="Uppdateringsintervall";
"config.module.paxcounter.updateinterval.description"="Hur ofta vi kan skicka ett meddelande till mesh-nätverket när personer upptäcks.";
"config.save.confirm"="Efter att konfigurationsvärdena sparats kommer noden att starta om.";
"communicating"="Kommunicerar med enheten...";
"connected.radio"="Ansluten Radio";
"connected"="Bluetooth Ansluten";
"connecting"="Ansluter...";
"contacts"="Kontakter";
"contacts %@"="Kontakter (%@)";
"copy"="Kopiera";
"current"="Aktuell";
"default"="Standard";
"delete"="Ta bort";
"detection.sensor"="Detektionssensor";
"detection.sensor.config"="Konfiguration av Detektionssensor";
"detection.sensor.log"="Logg för Detektionssensor";
"device"="Enhet";
"device.config"="Enhetskonfiguration";
"device.configuration"="Enhetsinställningar";
"device.metrics.delete"="Ta bort alla enhetsmätvärden?";
"device.metrics.log"="Logg för Enhetsmätvärden";
"device.role.client"="Appansluten eller fristående meddelandeenhet.";
"device.role.clientmute"="Enhet som inte vidarebefordrar paket från andra enheter.";
"device.role.clienthidden"="Enhet som endast sänder ut när det behövs för stealth eller energibesparing.";
"device.role.tracker"="Sänder ut GPS-positionspaket som prioritet.";
"device.role.lostandfound"="Sänder regelbundet ut plats som meddelande till standardkanalen för att underlätta återhämtning av enheten.";
"device.role.sensor"="Sänder ut telemetripaket som prioritet.";
"device.role.tak"="Optimerad för kommunikation med ATAK-systemet, minskar rutinutsändningar.";
"device.role.taktracker"="Aktiverar automatiska TAK PLI-utsändningar och minskar rutinutsändningar.";
"device.role.repeater"="Infrastrukturnod för att utöka nätverkstäckningen genom att vidarebefordra meddelanden med minimal overhead. Syns inte i Noder-listan.";
"device.role.router"="Infrastrukturnod för att utöka nätverkstäckningen genom att vidarebefordra meddelanden. Synlig i Noder-listan.";
"device.role.routerclient"="Kombination av både ROUTER och CLIENT. Inte för mobila enheter.";
"direct.messages"="Direktmeddelanden";
"dismiss.keyboard"="Stäng";
"display"="Skärm";
"display.config"="Skärmkonfiguration";
"distance"="Distans";
"disconnect"="Koppla från";
"echo"="Eko";
"email.address"="E-postadress";
"enabled"="Aktiverad";
"encrypted"="Krypterad";
"external.notification"="Extern Notifikation";
"external.notification.config"="Konfiguration av Extern Notifikation";
"finish"="Avsluta";
"firmware.version"="Firmwareversion";
"firmware.version.unsupported"="Okänd Firmwareversion upptäckt, kan inte ansluta till enheten.";
"gas"="Gas";
"gas.resistance"="Gasmotstånd";
"generate.qr.code"="Generera QR-kod";
"gpsformat.dec"="Decimalgrader";
"gpsformat.dms"="Grader Minuter Sekunder";
"gpsformat.utm"="Universal Transversal Mercator";
"gpsformat.mgrs"="Militärt rutnätsreferenssystem";
"gpsformat.olc"="Öppen Platskod (även känd som Pluskoder)";
"gpsformat.osgr"="Ordnance Survey Rutnätsreferens";
"gpsmode.disabled"="Inaktiverad";
"gpsmode.enabled"="Aktiverad";
"gpsmode.notPresent"="Inte närvarande";
"heard"="Hörd";
"heard.last"="Senast Hörd";
"hybrid"="Hybrid";
"hybrid.flyover"="Hybrid Flygöversikt";
"include"="Inkludera";
"inputevent.none"="Ingen";
"inputevent.up"="Upp";
"inputevent.down"="Ner";
"inputevent.left"="Vänster";
"inputevent.right"="Höger";
"inputevent.select"="Välj";
"inputevent.back"="Bakåt";
"inputevent.cancel"="Avbryt";
"interval.one.second"="En Sekund";
"interval.two.seconds"="Två Sekunder";
"interval.three.seconds"="Tre Sekunder";
"interval.four.seconds"="Fyra Sekunder";
"interval.five.seconds"="Fem Sekunder";
"interval.ten.seconds"="Tio Sekunder";
"interval.fifteen.seconds"="Femton Sekunder";
"interval.twenty.seconds"="Tjugo Sekunder";
"interval.twentyfive.seconds"="Tjugofem Sekunder";
"interval.thirty.seconds"="Trettio Sekunder";
"interval.fortyfive.seconds"="Fyrtiofem Sekunder";
"interval.one.minute"="En Minut";
"interval.two.minutes"="Två Minuter";
"interval.five.minutes"="Fem Minuter";
"interval.ten.minutes"="Tio Minuter";
"interval.fifteen.minutes"="Femton Minuter";
"interval.thirty.minutes"="Trettio Minuter";
"interval.one.hour"="En Timme";
"interval.two.hours"="Två Timmar";
"interval.three.hours"="Tre Timmar";
"interval.four.hours"="Fyra Timmar";
"interval.five.hours"="Fem Timmar";
"interval.six.hours"="Sex Timmar";
"interval.twelve.hours"="Tolv Timmar";
"interval.eighteen.hours"="Arton Timmar";
"interval.twentyfour.hours"="Tjugofyra Timmar";
"interval.thirtysix.hours"="Trettiosex Timmar";
"interval.fortyeight.hours"="Fyrtioåtta Timmar";
"interval.seventytwo.hours"="Sjuttiotvå Timmar";
"keyboard.type"="Tangentbordstyp";
"logging"="Loggning";
"lora"="LoRa";
"lora.config"="LoRa Konfiguration";
"map"="Mesh Karta";
"map.type"="Standardtyp";
"map.centering"="Centreringsläge";
"map.tiles.delete"="Radera Alla Kartplattor";
"map.recentering"="Automatisk Centrering";
"map.use.legacy"="Använd Äldre Mesh Karta";
"map.usertrackingmode"="Spårningsläge för användare";
"map.usertrackingmode.follow"="Följ";
"map.usertrackingmode.followwithheading"="Följ med riktning";
"map.usertrackingmode.none"="Ingen";
"mesh.live.activity"="Mesh Live Aktivitet";
"mesh.log"="Mesh-logg";
"mesh.log.ambientlighting.config %@"="Konfiguration för omgivningsbelysningsmodulen mottagen: %@";
"mesh.log.bluetooth.config %@"="Bluetooth-konfiguration mottagen: %@";
"mesh.log.cannedmessage.config %@"="Konfiguration för modulen med fördefinierade meddelanden mottagen: %@";
"mesh.log.cannedmessages.messages.get %@"="Begärda meddelanden för modulen med fördefinierade meddelanden för nod: %@";
"mesh.log.cannedmessages.messages.received %@"="Mottagna meddelanden för fördefinierade meddelanden För: %@";
"mesh.log.channel.sent %@ %d"="Skickade en kanal för: %@ Kanalindex %d";
"mesh.log.channel.received %d %@"="Kanal %d mottagen från: %@";
"mesh.log.device.config %@"="Enhetskonfiguration mottagen: %@";
"mesh.log.display.config %@"="Skärmkonfiguration mottagen: %@";
"mesh.log.devicemetadata %@"="Begär metadata för enhet för %@";
"mesh.log.device.metadata.received %@"="Metadata för enhet mottagen från: %@";
"mesh.log.detectionsensor.config %@"="Konfiguration för detektionssensormodulen mottagen: %@";
"mesh.log.externalnotification.config %@"="Konfiguration för modulen för externa notifikationer mottagen: %@";
"mesh.log.lora.config %@"="LoRa-konfiguration mottagen: %@";
"mesh.log.lora.config.sent %@"="Skickade en LoRa.Konfiguration för: %@";
"mesh.log.mqtt.config %@"="MQTT-modulkonfiguration mottagen: %@";
"mesh.log.myinfo %@"="Min info mottagen: %@";
"mesh.log.network.config %@"="Nätverkskonfiguration mottagen: %@";
"mesh.log.nodeinfo.received %@"="Nodinformation mottagen för: %@";
"mesh.log.paxcounter %@"="PAX-räknarmeddelande mottaget från: %@";
"mesh.log.paxcounter.config %@"="PAX-räknarkonfiguration mottagen: %@";
"mesh.log.position.config %@"="Positionskonfiguration mottagen: %@";
"mesh.log.position.received %@"="Positionspaket mottaget från nod: %@";
"mesh.log.power.config %@"="Strömkonfiguration mottagen: %@";
"mesh.log.rangetest.config %@"="Konfiguration för räckviddstestmodulen mottagen: %@";
"mesh.log.ringtone.config %@"="Konfiguration för RTTTL-ringsignal mottagen: %@";
"mesh.log.routing.message %@ %@"="Routing mottagen för RequestID: %@ Ack Status: %@";
"mesh.log.serial.config %@"="Seriekonfigurationsmodul mottagen: %@";
"mesh.log.sharelocation %@"="Skickade ett positionspaket från Apple-enhetens GPS till nod: %@";
"mesh.log.storeforward.config %@"="Konfiguration för Store & Forward-modulen mottagen: %@";
"mesh.log.telemetry.config %@"="Telemetrimodulkonfiguration mottagen: %@";
"mesh.log.telemetry.received %@"="Telemetri mottagen för: %@";
"mesh.log.textmessage.received"="Meddelande mottaget från textmeddelandeappen.";
"mesh.log.textmessage.send.failed %@"="Misslyckades med att skicka meddelande, inte korrekt ansluten till %@";
"mesh.log.textmessage.sent %@ %@ %@"="Skickade meddelande %@ från %@ till %@";
"mesh.log.traceroute.received.direct %@"="Spårruttförfrågan skickad till nod: %@ mottogs direkt.";
"mesh.log.traceroute.received.route %@"="Spårruttförfrågan returnerade: %@";
"mesh.log.traceroute.sent %@"="Skickade en spårruttförfrågan till nod: %@";
"mesh.log.wantconfig %@"="Utfärdar Want Config till %@";
"mesh.log.waypoint.sent %@"="Skickade en vägpunktspaket från: %@";
"mesh.log.waypoint.received %@"="Vägpunktspaket mottaget från nod: %@";
"message"="Meddelande";
"message.details"="Meddelandedetaljer";
"messages"="Meddelanden";
"mode"="Läge";
"module.configuration"="Modulkonfiguration";
"mqtt"="MQTT";
"mqtt.connect"="Anslut till MQTT";
"mqtt.config"="MQTT-konfiguration";
"mqtt.clientproxy"="MQTT-klientproxy";
"mqtt.disconnect"="Koppla från MQTT";
"mqtt.username"="Användarnamn";
"name"="Namn";
"network"="Nätverk";
"network.config"="Nätverkskonfiguration";
"nodes"="Noder";
"nodes %@"="Noder (%@)";
"nodelist.filter.distance %@"="upp till %@ bort";
"save.config %@"="Spara konfiguration för %@";
"no.nodes"="Inga Meshtastic-noder hittades";
"not.connected"="Ingen enhet ansluten";
"numbers.punctuation"="Siffror och skiljetecken";
"off"="Av";
"offline"="Offline";
"on.boot"="Endast vid uppstart";
"options"="Alternativ";
"password"="Lösenord";
"pause"="Pausa";
"paxcounter.ble"="BLE";
"paxcounter.delete"="Radera all paxdata?";
"paxcounter.wifi"="WiFi";
"paxcounter.uptime"="Drifttid";
"paxcounter.content.unavailable"="Inga loggar för PAX-räknare";
"paxcounter.log"="PAX-räknarens logg";
"paxcounter.total"="Totalt PAX";
"phone.gps"="Telefon-GPS";
"phone.gps.interval.description"="Hur ofta din telefon skickar din plats till enheten, platsuppdateringar till mesh-nätverket hanteras av enheten.";
"position"="Position";
"position.config"="Positionskonfiguration";
"position.precision %@"="Inom %@";
"preferred.radio"="Föredragen Radio";
"radio.configuration"="Radioinställningar";
"range.test"="Räckviddstest";
"range.test.blocked"="Blockera räckviddstest";
"range.test.config"="Konfiguration av räckviddstest";
"reply"="Svara";
"reboot"="Starta om";
"reboot.node"="Starta om nod?";
"received.ack"="Mottaget kvitto";
"received.ack.real"="Mottagarkvitto";
"resume"="Återuppta";
"ringtone"="Ringsignal";
"ringtone.config"="Ringsignalsinställningar";
"route.recorder"="Ruttinspelare";
"routes"="Rutter";
"routing.acknowledged"="Bekräftad";
"routing.noroute"="Ingen rutt";
"routing.gotnak"="Mottog ett negativt kvitto";
"routing.timeout"="Tidsgräns överskriden";
"routing.nointerface"="Inget gränssnitt";
"routing.maxretransmit"="Max antal omsändningar nått";
"routing.nochannel"="Ingen kanal";
"routing.toolarge"="Paketet är för stort";
"routing.noresponse"="Inget svar";
"routing.dutycyclelimit"="Regionala sändningsgränsen nådd";
"routing.badRequest"="Felaktig begäran";
"routing.notauthorized"="Inte auktoriserad";
"satellite"="Satellit";
"satellite.flyover"="Satellitöverflygning";
"save"="Spara";
"save.config %@"="Spara konfiguration för %@";
"serial"="Serie";
"serial.config"="Seriekonfiguration";
"serial.mode.default"="Standard";
"serial.mode.simple"="Enkel";
"serial.mode.proto"="Protobufs";
"serial.mode.txtmsg"="Textmeddelande";
"serial.mode.nmea"="NMEA-positioner";
"settings"="Inställningar";
"share.channels"="Dela QR-kod";
"share.position"="Dela position";
"subscribed"="Prenumererar på mesh";
"select.contact"="Välj en kontakt";
"select.node"="Välj en nod";
"select.menu.item"="Välj ett alternativ från menyn";
"set.region"="Ställ in LoRa-region";
"standard"="Standard";
"standard.muted"="Standard Muted";
"start"="Start";
"storeforward"="Lagra & Videresänd";
"storeforward.config"="Konfiguration för Lagra & Videresänd";
"storeforward.heartbeat"="Skicka hjärtslag";
"ssid"="SSID";
"tapback"="Svarsreaktion";
"tapback.heart"="Hjärta";
"tapback.thumbsup"="Tummen upp";
"tapback.thumbsdown"="Tummen ner";
"tapback.haha"="HaHa";
"tapback.exclamation"="Utropstecken";
"tapback.question"="Frågetecken";
"tapback.poop"="Bajs";
"tapback.wave"="Vinka";
"telemetry"="Telemetri (Sensorer)";
"telemetry.config"="Telemetriinställningar";
"timeout"="Tidsgräns överskriden";
"timestamp"="Tidsstämpel";
"tip.bluetooth.connect.title"="Ansluten Radio";
"tip.bluetooth.connect.message"="Visar information för LoRa-radion ansluten via bluetooth. Du kan svepa åt vänster för att koppla från radion och långtryck för att visa statistik eller starta liveaktivitet.";
"tip.channel.admin.title"="Administratörskanal";
"tip.channel.admin.message"="Administratörskanal upptäckt: Välj en nod från rullgardinsmenyn för att hantera anslutna eller fjärranslutna enheter.";
"tip.channels.create.title"="Hantera Kanaler";
"tip.channels.create.message"="De flesta data i ditt mesh-nätverk skickas över primärkanalen. Du kan ställa in sekundära kanaler för att skapa ytterligare meddelandegrupper skyddade av sin egen nyckel. Tips för kanalkonfiguration";
"tip.channels.share.title"="Dela Meshtastic-kanaler";
"tip.channels.share.message"="En Meshtastic QR-kod innehåller LoRa-konfigurationen och kanalvärden som behövs för kommunikation. De flesta aktiviteter i mesh-nätverket sker på den obligatoriska primärkanalen. Om du inte delar din primärkanal blir din första delade kanal primärkanalen på det andra nätverket. Andra kanaler är för privata grupper, varje med sin egen nyckel.";
"tip.messages.title"="Meddelanden";
"tip.messages.message"="Du kan skicka och ta emot kanalmeddelanden (gruppchatt) och direkta meddelanden. Från alla meddelanden kan du långtrycka för att se tillgängliga åtgärder som kopiera, svara, tapback och radera samt leveransdetaljer.";
"twitter"="Twitter";
"unknown"="Okänd";
"unknown.age"="Okänd ålder";
"unset"="Återställ";
"update.firmware"="Uppdatera din firmware";
"update.interval"="Uppdateringsintervall";
"user"="Användare";
"user.details"="Användaruppgifter";
"voltage"="Spänning";
"waiting"="Väntar...";