mirror of
https://github.com/meshtastic/Meshtastic-Apple.git
synced 2026-04-20 22:13:56 +00:00
Merge branch 'meshtastic:main' into main
This commit is contained in:
commit
86efade80a
63 changed files with 5112 additions and 1388 deletions
|
|
@ -110,6 +110,8 @@
|
|||
DD8ED9C8289CE4B900B3B0AB /* RoutingError.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8ED9C7289CE4B900B3B0AB /* RoutingError.swift */; };
|
||||
DD90860E26F69BAE00DC5189 /* NodeMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD90860D26F69BAE00DC5189 /* NodeMap.swift */; };
|
||||
DD913639270DFF4C00D7ACF3 /* LocalNotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD913638270DFF4C00D7ACF3 /* LocalNotificationManager.swift */; };
|
||||
DD93800B2BA3F968008BEC06 /* NodeMapContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD93800A2BA3F968008BEC06 /* NodeMapContent.swift */; };
|
||||
DD93800E2BA74D0C008BEC06 /* ChannelForm.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD93800D2BA74D0C008BEC06 /* ChannelForm.swift */; };
|
||||
DD94B7402ACCE3BE00DCD1D1 /* MapSettingsForm.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD94B73F2ACCE3BE00DCD1D1 /* MapSettingsForm.swift */; };
|
||||
DD964FBD296E6B01007C176F /* EmojiOnlyTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD964FBC296E6B01007C176F /* EmojiOnlyTextField.swift */; };
|
||||
DD964FBF296E76EF007C176F /* WaypointFormMapKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD964FBE296E76EF007C176F /* WaypointFormMapKit.swift */; };
|
||||
|
|
@ -183,6 +185,9 @@
|
|||
DDDB445029F8AC9C00EE2349 /* UIImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDDB444F29F8AC9C00EE2349 /* UIImage.swift */; };
|
||||
DDDB445229F8ACF900EE2349 /* Date.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDDB445129F8ACF900EE2349 /* Date.swift */; };
|
||||
DDDB445429F8AD1600EE2349 /* Data.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDDB445329F8AD1600EE2349 /* Data.swift */; };
|
||||
DDDC22382BA92344002C44F1 /* MeshMapContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDDC22372BA92344002C44F1 /* MeshMapContent.swift */; };
|
||||
DDDCD5702BB26F5C00BE6B60 /* NodeListFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDDCD56F2BB26F5C00BE6B60 /* NodeListFilter.swift */; };
|
||||
DDDCD5722BB3E46400BE6B60 /* BLEManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDAF8C5226EB1DF10058C060 /* BLEManager.swift */; };
|
||||
DDDE59F529AF163D00490C6C /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DD41A61C29AE7E8E003C5A37 /* WidgetKit.framework */; };
|
||||
DDDE59F629AF163D00490C6C /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DD41A61E29AE7E8F003C5A37 /* SwiftUI.framework */; };
|
||||
DDDE59F929AF163D00490C6C /* WidgetsBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDDE59F829AF163D00490C6C /* WidgetsBundle.swift */; };
|
||||
|
|
@ -294,6 +299,7 @@
|
|||
DD3501882852FC3B000FC853 /* Settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Settings.swift; sourceTree = "<group>"; };
|
||||
DD3619132B1EE20700C41C8C /* MeshtasticDataModelV21.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV21.xcdatamodel; sourceTree = "<group>"; };
|
||||
DD3619142B1EF9F900C41C8C /* LocationsHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationsHandler.swift; sourceTree = "<group>"; };
|
||||
DD398EBD2B93F640002B4C51 /* MeshtasticDataModelV 29.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 29.xcdatamodel"; sourceTree = "<group>"; };
|
||||
DD3CC6B428E33FD100FA9159 /* ShareChannels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareChannels.swift; sourceTree = "<group>"; };
|
||||
DD3CC6BB28E366DF00FA9159 /* MeshtasticDataModel.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModel.xcdatamodel; sourceTree = "<group>"; };
|
||||
DD3CC6BD28E4CD9800FA9159 /* BatteryGauge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryGauge.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -359,6 +365,8 @@
|
|||
DD90860A26F645B700DC5189 /* Meshtastic.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Meshtastic.entitlements; sourceTree = "<group>"; };
|
||||
DD90860D26F69BAE00DC5189 /* NodeMap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeMap.swift; sourceTree = "<group>"; };
|
||||
DD913638270DFF4C00D7ACF3 /* LocalNotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalNotificationManager.swift; sourceTree = "<group>"; };
|
||||
DD93800A2BA3F968008BEC06 /* NodeMapContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeMapContent.swift; sourceTree = "<group>"; };
|
||||
DD93800D2BA74D0C008BEC06 /* ChannelForm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelForm.swift; sourceTree = "<group>"; };
|
||||
DD94B73F2ACCE3BE00DCD1D1 /* MapSettingsForm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapSettingsForm.swift; sourceTree = "<group>"; };
|
||||
DD964FBC296E6B01007C176F /* EmojiOnlyTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiOnlyTextField.swift; sourceTree = "<group>"; };
|
||||
DD964FBE296E76EF007C176F /* WaypointFormMapKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaypointFormMapKit.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -369,6 +377,7 @@
|
|||
DD97E96528EFD9820056DDA4 /* MeshtasticLogo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshtasticLogo.swift; sourceTree = "<group>"; };
|
||||
DD97E96728EFE9A00056DDA4 /* About.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = About.swift; sourceTree = "<group>"; };
|
||||
DD994B68295F88B60013760A /* IntervalEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntervalEnums.swift; sourceTree = "<group>"; };
|
||||
DD9A1A912BA2D2D3001E602E /* MeshtasticDataModelV 30.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 30.xcdatamodel"; sourceTree = "<group>"; };
|
||||
DDA0B6B1294CDC55001356EC /* Channels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Channels.swift; sourceTree = "<group>"; };
|
||||
DDA1C48D28DB49D3009933EC /* ChannelRoles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelRoles.swift; sourceTree = "<group>"; };
|
||||
DDA6B2E828419CF2003E8C16 /* MeshPackets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshPackets.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -448,6 +457,11 @@
|
|||
DDDB444F29F8AC9C00EE2349 /* UIImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIImage.swift; sourceTree = "<group>"; };
|
||||
DDDB445129F8ACF900EE2349 /* Date.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Date.swift; sourceTree = "<group>"; };
|
||||
DDDB445329F8AD1600EE2349 /* Data.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Data.swift; sourceTree = "<group>"; };
|
||||
DDDC22312BA76701002C44F1 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
DDDC22322BA76961002C44F1 /* zh-Hant-TW */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant-TW"; path = "zh-Hant-TW.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
||||
DDDC22372BA92344002C44F1 /* MeshMapContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshMapContent.swift; sourceTree = "<group>"; };
|
||||
DDDCD56F2BB26F5C00BE6B60 /* NodeListFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeListFilter.swift; sourceTree = "<group>"; };
|
||||
DDDCD5712BB3246500BE6B60 /* MeshtasticDataModelV 31.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 31.xcdatamodel"; sourceTree = "<group>"; };
|
||||
DDDD527729B5B83F0045BC3C /* MeshtasticDataModelV9.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV9.xcdatamodel; sourceTree = "<group>"; };
|
||||
DDDE59F429AF163D00490C6C /* WidgetsExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = WidgetsExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
DDDE59F829AF163D00490C6C /* WidgetsBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetsBundle.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -556,21 +570,6 @@
|
|||
path = CoreData;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DD2100802B0E676E00F2F116 /* Routes */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DD2100832B0E67AD00F2F116 /* RouteMap */,
|
||||
);
|
||||
path = Routes;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DD2100832B0E67AD00F2F116 /* RouteMap */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
);
|
||||
path = RouteMap;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DD47E3CA26F0E50300029299 /* Nodes */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
|
@ -600,7 +599,7 @@
|
|||
DD4A911C2708C57100501B7E /* Settings */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DD2100802B0E676E00F2F116 /* Routes */,
|
||||
DD93800C2BA74CE3008BEC06 /* Channels */,
|
||||
DD97E96728EFE9A00056DDA4 /* About.swift */,
|
||||
DD0F791A28713C8A00A6FDAD /* AdminMessageList.swift */,
|
||||
DD4A911D2708C65400501B7E /* AppSettings.swift */,
|
||||
|
|
@ -743,9 +742,18 @@
|
|||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DD93800C2BA74CE3008BEC06 /* Channels */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DD93800D2BA74D0C008BEC06 /* ChannelForm.swift */,
|
||||
);
|
||||
path = Channels;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DDAD49EB2AFAE82500B4425D /* Map */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DDDC22362BA9232C002C44F1 /* MapContent */,
|
||||
DD94B73F2ACCE3BE00DCD1D1 /* MapSettingsForm.swift */,
|
||||
DDB6CCFA2AAF805100945AF6 /* NodeMapSwiftUI.swift */,
|
||||
DD13AA482AB73BF400BA0C98 /* PositionPopover.swift */,
|
||||
|
|
@ -956,6 +964,7 @@
|
|||
DDDB26432AAC0206003AFCB7 /* NodeDetail.swift */,
|
||||
DDDB26452AACC0B7003AFCB7 /* NodeInfoItem.swift */,
|
||||
DDDB26412AABF655003AFCB7 /* NodeListItem.swift */,
|
||||
DDDCD56F2BB26F5C00BE6B60 /* NodeListFilter.swift */,
|
||||
);
|
||||
path = Helpers;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -984,6 +993,15 @@
|
|||
path = Extensions;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DDDC22362BA9232C002C44F1 /* MapContent */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DDDC22372BA92344002C44F1 /* MeshMapContent.swift */,
|
||||
DD93800A2BA3F968008BEC06 /* NodeMapContent.swift */,
|
||||
);
|
||||
path = MapContent;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DDDE59F729AF163D00490C6C /* Widgets */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
|
@ -1117,6 +1135,8 @@
|
|||
"zh-Hans",
|
||||
pl,
|
||||
he,
|
||||
fr,
|
||||
"zh-Hant-TW",
|
||||
);
|
||||
mainGroup = DDC2E14B26CE248E0042C5E4;
|
||||
packageReferences = (
|
||||
|
|
@ -1210,6 +1230,7 @@
|
|||
DD5E523F298F5A9E00D21B61 /* AirQualityIndexCompact.swift in Sources */,
|
||||
DD964FBF296E76EF007C176F /* WaypointFormMapKit.swift in Sources */,
|
||||
DD3501892852FC3B000FC853 /* Settings.swift in Sources */,
|
||||
DDDC22382BA92344002C44F1 /* MeshMapContent.swift in Sources */,
|
||||
DDDB443629F6287000EE2349 /* MapButtons.swift in Sources */,
|
||||
DD5D0A9C2931B9F200F7EA61 /* EthernetModes.swift in Sources */,
|
||||
6DEDA55A2A957B8E00321D2E /* DetectionSensorLog.swift in Sources */,
|
||||
|
|
@ -1224,6 +1245,7 @@
|
|||
DDB75A0F2A05920E006ED576 /* FileManager.swift in Sources */,
|
||||
DD1933782B084F4200771CD5 /* Measurement.swift in Sources */,
|
||||
DD4F23CD28779A3C001D37CB /* EnvironmentMetricsLog.swift in Sources */,
|
||||
DD93800E2BA74D0C008BEC06 /* ChannelForm.swift in Sources */,
|
||||
DD41A61529AB0035003C5A37 /* NodeWeatherForecast.swift in Sources */,
|
||||
DDB6ABD628AE742000384BA1 /* BluetoothConfig.swift in Sources */,
|
||||
DD4640202AFF10F4002A5ECB /* WaypointForm.swift in Sources */,
|
||||
|
|
@ -1338,6 +1360,7 @@
|
|||
DDB6ABE228B13FB500384BA1 /* PositionConfigEnums.swift in Sources */,
|
||||
DD5E520E298EE33B00D21B61 /* mqtt.pb.swift in Sources */,
|
||||
DD994B69295F88B60013760A /* IntervalEnums.swift in Sources */,
|
||||
DDDCD5702BB26F5C00BE6B60 /* NodeListFilter.swift in Sources */,
|
||||
DD1933762B0835D500771CD5 /* PositionAltitudeChart.swift in Sources */,
|
||||
DD415828285859C4009B0E59 /* TelemetryConfig.swift in Sources */,
|
||||
DDDB443D29F6592F00EE2349 /* NetworkManager.swift in Sources */,
|
||||
|
|
@ -1363,6 +1386,7 @@
|
|||
DD5E5205298EE33B00D21B61 /* mesh.pb.swift in Sources */,
|
||||
DDF6B2482A9AEBF500BA6931 /* StoreForwardConfig.swift in Sources */,
|
||||
DD8169F9271F1A6100F4AB02 /* MeshLogger.swift in Sources */,
|
||||
DD93800B2BA3F968008BEC06 /* NodeMapContent.swift in Sources */,
|
||||
DD41582A28585C32009B0E59 /* RangeTestConfig.swift in Sources */,
|
||||
DD1925B728CDA5A400720036 /* CannedMessagesConfigEnums.swift in Sources */,
|
||||
DDDB444429F8A8DD00EE2349 /* Float.swift in Sources */,
|
||||
|
|
@ -1381,6 +1405,7 @@
|
|||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
DDD3BBD5292D763200D609B3 /* MeshtasticTests.swift in Sources */,
|
||||
DDDCD5722BB3E46400BE6B60 /* BLEManager.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
|
@ -1433,6 +1458,8 @@
|
|||
A65FA974296876BF00A97686 /* zh-Hans */,
|
||||
DDF6B24B2A9C2FC800BA6931 /* pl */,
|
||||
DD31EC492B7F18B7006A3995 /* he */,
|
||||
DDDC22312BA76701002C44F1 /* fr */,
|
||||
DDDC22322BA76961002C44F1 /* zh-Hant-TW */,
|
||||
);
|
||||
name = Localizable.strings;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -1583,7 +1610,7 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.2.25;
|
||||
MARKETING_VERSION = 2.3.2;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
|
|
@ -1617,7 +1644,7 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.2.25;
|
||||
MARKETING_VERSION = 2.3.2;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
|
|
@ -1739,7 +1766,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.2.25;
|
||||
MARKETING_VERSION = 2.3.2;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
|
|
@ -1772,7 +1799,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.2.25;
|
||||
MARKETING_VERSION = 2.3.2;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
|
|
@ -1883,6 +1910,9 @@
|
|||
DD3CC6BA28E366DF00FA9159 /* Meshtastic.xcdatamodeld */ = {
|
||||
isa = XCVersionGroup;
|
||||
children = (
|
||||
DDDCD5712BB3246500BE6B60 /* MeshtasticDataModelV 31.xcdatamodel */,
|
||||
DD9A1A912BA2D2D3001E602E /* MeshtasticDataModelV 30.xcdatamodel */,
|
||||
DD398EBD2B93F640002B4C51 /* MeshtasticDataModelV 29.xcdatamodel */,
|
||||
DD0E20FF2B892E1300F2D100 /* MeshtasticDataModelV 28.xcdatamodel */,
|
||||
D93069062B81D8900066FBC8 /* MeshtasticDataModelV 27.xcdatamodel */,
|
||||
DD05296F2B77F454008E44CD /* MeshtasticDataModelV 26.xcdatamodel */,
|
||||
|
|
@ -1912,7 +1942,7 @@
|
|||
DD5D0A9A2931AD6B00F7EA61 /* MeshtasticDataModelV2.xcdatamodel */,
|
||||
DD3CC6BB28E366DF00FA9159 /* MeshtasticDataModel.xcdatamodel */,
|
||||
);
|
||||
currentVersion = DD0E20FF2B892E1300F2D100 /* MeshtasticDataModelV 28.xcdatamodel */;
|
||||
currentVersion = DDDCD5712BB3246500BE6B60 /* MeshtasticDataModelV 31.xcdatamodel */;
|
||||
name = Meshtastic.xcdatamodeld;
|
||||
path = Meshtastic/Meshtastic.xcdatamodeld;
|
||||
sourceTree = "<group>";
|
||||
|
|
|
|||
|
|
@ -50,6 +50,20 @@ enum MeshMapTypes: Int, CaseIterable, Identifiable {
|
|||
}
|
||||
}
|
||||
|
||||
enum MeshMapDistances: Double, CaseIterable, Identifiable {
|
||||
case fiftyMiles = 80467.2
|
||||
case oneHundredMiles = 160934
|
||||
case twoHundredMiles = 321869
|
||||
case fiveHundredMiles = 804672
|
||||
case oneThousandMiles = 1609000
|
||||
case twentyFiveHundredMiles = 4023360
|
||||
var id: Double { self.rawValue }
|
||||
var description: String {
|
||||
let distanceFormatter = MKDistanceFormatter()
|
||||
return "up to \(distanceFormatter.string(fromDistance: Double(self.rawValue))) away"
|
||||
}
|
||||
}
|
||||
|
||||
enum UserTrackingModes: Int, CaseIterable, Identifiable {
|
||||
case none = 0
|
||||
case follow = 1
|
||||
|
|
@ -116,7 +130,7 @@ enum LocationUpdateInterval: Int, CaseIterable, Identifiable {
|
|||
}
|
||||
}
|
||||
|
||||
enum MapLayer: String, CaseIterable, Equatable {
|
||||
enum MapLayer: String, CaseIterable, Equatable, Decodable {
|
||||
case standard
|
||||
case hybrid
|
||||
case satellite
|
||||
|
|
@ -124,7 +138,7 @@ enum MapLayer: String, CaseIterable, Equatable {
|
|||
var localized: String { self.rawValue.localized }
|
||||
}
|
||||
|
||||
enum MapTileServer: String, CaseIterable, Identifiable {
|
||||
enum MapTileServer: String, CaseIterable, Identifiable, Decodable {
|
||||
case openStreetMap
|
||||
case openStreetMapDE
|
||||
case openStreetMapFR
|
||||
|
|
@ -259,7 +273,7 @@ enum OverlayType: String, CaseIterable, Equatable {
|
|||
var localized: String { self.rawValue.localized }
|
||||
}
|
||||
|
||||
enum MapOverlayServer: String, CaseIterable, Identifiable {
|
||||
enum MapOverlayServer: String, CaseIterable, Identifiable, Decodable {
|
||||
case baseReReflectivityCurrent
|
||||
case baseReReflectivityOneHourAgo
|
||||
case echoTopsEetCurrent
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ enum DeviceRoles: Int, CaseIterable, Identifiable {
|
|||
case lostAndFound = 9
|
||||
case sensor = 6
|
||||
case tak = 7
|
||||
case takTracker = 10
|
||||
case repeater = 4
|
||||
case router = 2
|
||||
case routerClient = 3
|
||||
|
|
@ -40,11 +41,14 @@ enum DeviceRoles: Int, CaseIterable, Identifiable {
|
|||
return "Sensor"
|
||||
case .tak:
|
||||
return "TAK"
|
||||
case .takTracker:
|
||||
return "TAK Tracker"
|
||||
case .clientHidden:
|
||||
return "Client Hidden"
|
||||
case .lostAndFound:
|
||||
return "Lost and Found"
|
||||
}
|
||||
|
||||
}
|
||||
var description: String {
|
||||
switch self {
|
||||
|
|
@ -64,6 +68,8 @@ enum DeviceRoles: Int, CaseIterable, Identifiable {
|
|||
return "device.role.sensor".localized
|
||||
case .tak:
|
||||
return "device.role.tak".localized
|
||||
case .takTracker:
|
||||
return "device.role.taktracker".localized
|
||||
case .clientHidden:
|
||||
return "device.role.clienthidden".localized
|
||||
case .lostAndFound:
|
||||
|
|
@ -74,17 +80,21 @@ enum DeviceRoles: Int, CaseIterable, Identifiable {
|
|||
var systemName: String {
|
||||
switch self {
|
||||
case .client:
|
||||
return "iphone.gen3.radiowaves.left.and.right"
|
||||
return "apps.iphone"
|
||||
case .clientMute:
|
||||
return "speaker.slash"
|
||||
case .router, .routerClient, .repeater:
|
||||
case .router, .routerClient:
|
||||
return "wifi.router"
|
||||
case .repeater:
|
||||
return "repeat"
|
||||
case .tracker:
|
||||
return "mappin.and.ellipse.circle"
|
||||
case .sensor:
|
||||
return "sensor"
|
||||
case .tak:
|
||||
return "shield.checkered"
|
||||
case .takTracker:
|
||||
return "dog"
|
||||
case .clientHidden:
|
||||
return "eye.slash"
|
||||
case .lostAndFound:
|
||||
|
|
@ -110,6 +120,8 @@ enum DeviceRoles: Int, CaseIterable, Identifiable {
|
|||
return Config.DeviceConfig.Role.sensor
|
||||
case .tak:
|
||||
return Config.DeviceConfig.Role.tak
|
||||
case .takTracker:
|
||||
return Config.DeviceConfig.Role.takTracker
|
||||
case .clientHidden:
|
||||
return Config.DeviceConfig.Role.clientHidden
|
||||
case .lostAndFound:
|
||||
|
|
|
|||
|
|
@ -28,7 +28,47 @@ enum RegionCodes: Int, CaseIterable, Identifiable {
|
|||
case my_919 = 17
|
||||
case sg_923 = 18
|
||||
case lora24 = 13
|
||||
|
||||
var topic: String {
|
||||
switch self {
|
||||
case .unset:
|
||||
"UNSET"
|
||||
case .us:
|
||||
"US"
|
||||
case .eu433:
|
||||
"EU_433"
|
||||
case .eu868:
|
||||
"EU_868"
|
||||
case .cn:
|
||||
"CN"
|
||||
case .jp:
|
||||
"JP"
|
||||
case .anz:
|
||||
"ANZ"
|
||||
case .kr:
|
||||
"KR"
|
||||
case .tw:
|
||||
"TW"
|
||||
case .ru:
|
||||
"RU"
|
||||
case .in:
|
||||
"IN"
|
||||
case .nz865:
|
||||
"NZ_865"
|
||||
case .th:
|
||||
"TH"
|
||||
case .ua433:
|
||||
"UA_433"
|
||||
case .ua868:
|
||||
"UA_868"
|
||||
case .my_433:
|
||||
"MY_433"
|
||||
case .my_919:
|
||||
"MY_919"
|
||||
case .sg_923:
|
||||
"SG_923"
|
||||
case .lora24:
|
||||
"LORA_24"
|
||||
} }
|
||||
var id: Int { self.rawValue }
|
||||
var description: String {
|
||||
switch self {
|
||||
|
|
|
|||
|
|
@ -13,17 +13,20 @@ enum BubblePosition {
|
|||
|
||||
enum Tapbacks: Int, CaseIterable, Identifiable {
|
||||
|
||||
case heart = 0
|
||||
case thumbsUp = 1
|
||||
case thumbsDown = 2
|
||||
case haHa = 3
|
||||
case exclamation = 4
|
||||
case question = 5
|
||||
case poop = 6
|
||||
case wave = 0
|
||||
case heart = 1
|
||||
case thumbsUp = 2
|
||||
case thumbsDown = 3
|
||||
case haHa = 4
|
||||
case exclamation = 5
|
||||
case question = 6
|
||||
case poop = 7
|
||||
|
||||
var id: Int { self.rawValue }
|
||||
var emojiString: String {
|
||||
switch self {
|
||||
case .wave:
|
||||
return "👋"
|
||||
case .heart:
|
||||
return "❤️"
|
||||
case .thumbsUp:
|
||||
|
|
@ -42,6 +45,8 @@ enum Tapbacks: Int, CaseIterable, Identifiable {
|
|||
}
|
||||
var description: String {
|
||||
switch self {
|
||||
case .wave:
|
||||
return "tapback.wave".localized
|
||||
case .heart:
|
||||
return "tapback.heart".localized
|
||||
case .thumbsUp:
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ enum GpsUpdateIntervals: Int, CaseIterable, Identifiable {
|
|||
|
||||
case thirtySeconds = 30
|
||||
case oneMinute = 60
|
||||
case twoMinutes = 120
|
||||
case fiveMinutes = 300
|
||||
case tenMinutes = 600
|
||||
case fifteenMinutes = 900
|
||||
|
|
@ -74,6 +75,8 @@ enum GpsUpdateIntervals: Int, CaseIterable, Identifiable {
|
|||
return "interval.thirty.seconds".localized
|
||||
case .oneMinute:
|
||||
return "interval.one.minute".localized
|
||||
case .twoMinutes:
|
||||
return "interval.two.minutes".localized
|
||||
case .fiveMinutes:
|
||||
return "interval.five.minutes".localized
|
||||
case .tenMinutes:
|
||||
|
|
|
|||
|
|
@ -11,6 +11,36 @@ import MapKit
|
|||
import SwiftUI
|
||||
|
||||
extension PositionEntity {
|
||||
|
||||
static func allPositionsFetchRequest() -> NSFetchRequest<PositionEntity> {
|
||||
let request: NSFetchRequest<PositionEntity> = PositionEntity.fetchRequest()
|
||||
request.fetchLimit = 200
|
||||
//request.fetchBatchSize = 1
|
||||
request.returnsObjectsAsFaults = false
|
||||
request.includesSubentities = true
|
||||
request.returnsDistinctResults = true
|
||||
request.sortDescriptors = [NSSortDescriptor(key: "time", ascending: false)]
|
||||
let positionPredicate = NSPredicate(format: "nodePosition != nil && (nodePosition.user.shortName != nil || nodePosition.user.shortName != '') && latest == true && time >= %@", Calendar.current.date(byAdding: .day, value: -2, to: Date())! as NSDate)
|
||||
|
||||
let pointOfInterest = LocationHelper.currentLocation
|
||||
|
||||
if pointOfInterest.latitude != LocationHelper.DefaultLocation.latitude && pointOfInterest.longitude != LocationHelper.DefaultLocation.longitude {
|
||||
let D: Double = UserDefaults.meshMapDistance * 1.1
|
||||
let R: Double = 6371009
|
||||
let meanLatitidue = pointOfInterest.latitude * .pi / 180
|
||||
let deltaLatitude = D / R * 180 / .pi
|
||||
let deltaLongitude = D / (R * cos(meanLatitidue)) * 180 / .pi
|
||||
let minLatitude: Double = pointOfInterest.latitude - deltaLatitude
|
||||
let maxLatitude: Double = pointOfInterest.latitude + deltaLatitude
|
||||
let minLongitude: Double = pointOfInterest.longitude - deltaLongitude
|
||||
let maxLongitude: Double = pointOfInterest.longitude + deltaLongitude
|
||||
let distancePredicate = NSPredicate(format: "(%lf <= (longitudeI / 1e7)) AND ((longitudeI / 1e7) <= %lf) AND (%lf <= (latitudeI / 1e7)) AND ((latitudeI / 1e7) <= %lf)", minLongitude, maxLongitude,minLatitude, maxLatitude)
|
||||
request.predicate = NSCompoundPredicate(type: .and, subpredicates: [positionPredicate, distancePredicate])
|
||||
} else {
|
||||
request.predicate = positionPredicate
|
||||
}
|
||||
return request
|
||||
}
|
||||
|
||||
var latitude: Double? {
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,18 @@ import MapKit
|
|||
import SwiftUI
|
||||
|
||||
extension WaypointEntity {
|
||||
|
||||
static func allWaypointssFetchRequest() -> NSFetchRequest<WaypointEntity> {
|
||||
let request: NSFetchRequest<WaypointEntity> = WaypointEntity.fetchRequest()
|
||||
request.fetchLimit = 50
|
||||
//request.fetchBatchSize = 1
|
||||
//request.returnsObjectsAsFaults = false
|
||||
//request.includesSubentities = true
|
||||
request.returnsDistinctResults = true
|
||||
request.sortDescriptors = [NSSortDescriptor(key: "name", ascending: false)]
|
||||
request.predicate = NSPredicate(format: "expire == nil || expire >= %@", Date() as NSDate)
|
||||
return request
|
||||
}
|
||||
|
||||
var latitude: Double? {
|
||||
|
||||
|
|
|
|||
|
|
@ -67,4 +67,29 @@ extension String {
|
|||
: $0 + String($1)
|
||||
}
|
||||
}
|
||||
|
||||
var length: Int {
|
||||
return count
|
||||
}
|
||||
|
||||
subscript (i: Int) -> String {
|
||||
return self[i ..< i + 1]
|
||||
}
|
||||
|
||||
func substring(fromIndex: Int) -> String {
|
||||
return self[min(fromIndex, length) ..< length]
|
||||
}
|
||||
|
||||
func substring(toIndex: Int) -> String {
|
||||
return self[0 ..< max(0, toIndex)]
|
||||
}
|
||||
|
||||
subscript (r: Range<Int>) -> String {
|
||||
let range = Range(uncheckedBounds: (lower: max(0, min(length, r.lowerBound)),
|
||||
upper: min(length, max(0, r.upperBound))))
|
||||
let start = index(startIndex, offsetBy: range.lowerBound)
|
||||
let end = index(start, offsetBy: range.upperBound - range.lowerBound)
|
||||
return String(self[start ..< end])
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,37 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
@propertyWrapper
|
||||
struct UserDefault<T: Decodable> {
|
||||
let key: UserDefaults.Keys
|
||||
let defaultValue: T
|
||||
|
||||
init(_ key: UserDefaults.Keys, defaultValue: T) {
|
||||
self.key = key
|
||||
self.defaultValue = defaultValue
|
||||
}
|
||||
|
||||
var wrappedValue: T {
|
||||
get {
|
||||
if defaultValue as? any RawRepresentable != nil {
|
||||
let storedValue = UserDefaults.standard.object(forKey: key.rawValue)
|
||||
|
||||
guard let storedValue,
|
||||
let jsonString = (storedValue as? String != nil) ? "\"\(storedValue)\"" : "\(storedValue)",
|
||||
let data = jsonString.data(using: .utf8),
|
||||
let value = (try? JSONDecoder().decode(T.self, from: data)) else { return defaultValue }
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
return UserDefaults.standard.object(forKey: key.rawValue) as? T ?? defaultValue
|
||||
}
|
||||
set {
|
||||
UserDefaults.standard.set((newValue as? any RawRepresentable)?.rawValue ?? newValue, forKey: key.rawValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension UserDefaults {
|
||||
enum Keys: String, CaseIterable {
|
||||
case preferredPeripheralId
|
||||
|
|
@ -14,192 +45,117 @@ extension UserDefaults {
|
|||
case provideLocation
|
||||
case provideLocationInterval
|
||||
case mapLayer
|
||||
case meshMapDistance
|
||||
case enableMapWaypoints
|
||||
case meshMapRecentering
|
||||
case meshMapShowNodeHistory
|
||||
case meshMapShowRouteLines
|
||||
case enableMapConvexHull
|
||||
case enableMapRecentering
|
||||
case enableMapNodeHistoryPins
|
||||
case enableMapRouteLines
|
||||
case enableMapTraffic
|
||||
case enableMapPointsOfInterest
|
||||
case enableOfflineMaps
|
||||
case enableOfflineMapsMBTiles
|
||||
case mapTileServer
|
||||
case enableOverlayServer
|
||||
case mapOverlayServer
|
||||
case mapTilesAboveLabels
|
||||
case mapUseLegacy
|
||||
case enableDetectionNotifications
|
||||
case detectionSensorRole
|
||||
case enableSmartPosition
|
||||
case modemPreset
|
||||
case firmwareVersion
|
||||
case testIntEnum
|
||||
}
|
||||
|
||||
func reset() {
|
||||
Keys.allCases.forEach { removeObject(forKey: $0.rawValue) }
|
||||
}
|
||||
static var preferredPeripheralId: String {
|
||||
get {
|
||||
UserDefaults.standard.string(forKey: "preferredPeripheralId") ?? ""
|
||||
}
|
||||
set {
|
||||
UserDefaults.standard.set(newValue, forKey: "preferredPeripheralId")
|
||||
}
|
||||
}
|
||||
static var preferredPeripheralNum: Int {
|
||||
get {
|
||||
UserDefaults.standard.integer(forKey: "preferredPeripheralNum")
|
||||
}
|
||||
set {
|
||||
UserDefaults.standard.set(newValue, forKey: "preferredPeripheralNum")
|
||||
}
|
||||
}
|
||||
static var provideLocation: Bool {
|
||||
get {
|
||||
UserDefaults.standard.bool(forKey: "provideLocation")
|
||||
} set {
|
||||
UserDefaults.standard.set(newValue, forKey: "provideLocation")
|
||||
}
|
||||
}
|
||||
static var provideLocationInterval: Int {
|
||||
get {
|
||||
UserDefaults.standard.integer(forKey: "provideLocationInterval")
|
||||
}
|
||||
set {
|
||||
UserDefaults.standard.set(newValue, forKey: "provideLocationInterval")
|
||||
}
|
||||
}
|
||||
static var mapLayer: MapLayer {
|
||||
get {
|
||||
MapLayer(rawValue: UserDefaults.standard.string(forKey: "mapLayer") ?? MapLayer.standard.rawValue) ?? MapLayer.standard
|
||||
}
|
||||
set {
|
||||
UserDefaults.standard.set(newValue.rawValue, forKey: "mapLayer")
|
||||
}
|
||||
}
|
||||
static var enableMapRecentering: Bool {
|
||||
get {
|
||||
UserDefaults.standard.bool(forKey: "meshMapRecentering")
|
||||
}
|
||||
set {
|
||||
UserDefaults.standard.set(newValue, forKey: "meshMapRecentering")
|
||||
}
|
||||
}
|
||||
static var enableMapNodeHistoryPins: Bool {
|
||||
get {
|
||||
UserDefaults.standard.bool(forKey: "meshMapShowNodeHistory")
|
||||
}
|
||||
set {
|
||||
UserDefaults.standard.set(newValue, forKey: "meshMapShowNodeHistory")
|
||||
}
|
||||
}
|
||||
static var enableMapRouteLines: Bool {
|
||||
get {
|
||||
UserDefaults.standard.bool(forKey: "meshMapShowRouteLines")
|
||||
}
|
||||
set {
|
||||
UserDefaults.standard.set(newValue, forKey: "meshMapShowRouteLines")
|
||||
}
|
||||
}
|
||||
static var enableMapConvexHull: Bool {
|
||||
get {
|
||||
UserDefaults.standard.bool(forKey: "enableMapConvexHull")
|
||||
}
|
||||
set {
|
||||
UserDefaults.standard.set(newValue, forKey: "enableMapConvexHull")
|
||||
}
|
||||
}
|
||||
static var enableMapTraffic: Bool {
|
||||
get {
|
||||
UserDefaults.standard.bool(forKey: "enableMapTraffic")
|
||||
}
|
||||
set {
|
||||
UserDefaults.standard.set(newValue, forKey: "enableMapTraffic")
|
||||
}
|
||||
}
|
||||
static var enableMapPointsOfInterest: Bool {
|
||||
get {
|
||||
UserDefaults.standard.bool(forKey: "enableMapPointsOfInterest")
|
||||
}
|
||||
set {
|
||||
UserDefaults.standard.set(newValue, forKey: "enableMapPointsOfInterest")
|
||||
}
|
||||
}
|
||||
static var enableOfflineMaps: Bool {
|
||||
get {
|
||||
UserDefaults.standard.bool(forKey: "enableOfflineMaps")
|
||||
}
|
||||
set {
|
||||
UserDefaults.standard.set(newValue, forKey: "enableOfflineMaps")
|
||||
}
|
||||
}
|
||||
static var enableOfflineMapsMBTiles: Bool {
|
||||
get {
|
||||
UserDefaults.standard.bool(forKey: "enableOfflineMapsMBTiles")
|
||||
}
|
||||
set {
|
||||
UserDefaults.standard.set(newValue, forKey: "enableOfflineMapsMBTiles")
|
||||
}
|
||||
}
|
||||
static var mapTileServer: MapTileServer {
|
||||
get {
|
||||
MapTileServer(rawValue: UserDefaults.standard.string(forKey: "mapTileServer") ?? MapTileServer.openStreetMap.rawValue) ?? MapTileServer.openStreetMap
|
||||
}
|
||||
set {
|
||||
UserDefaults.standard.set(newValue.rawValue, forKey: "mapTileServer")
|
||||
}
|
||||
}
|
||||
static var enableOverlayServer: Bool {
|
||||
get {
|
||||
UserDefaults.standard.bool(forKey: "enableOverlayServer")
|
||||
}
|
||||
set {
|
||||
UserDefaults.standard.set(newValue, forKey: "enableOverlayServer")
|
||||
}
|
||||
}
|
||||
static var mapOverlayServer: MapOverlayServer {
|
||||
get {
|
||||
MapOverlayServer(rawValue: UserDefaults.standard.string(forKey: "mapOverlayServer") ?? MapOverlayServer.baseReReflectivityCurrent.rawValue) ?? MapOverlayServer.baseReReflectivityCurrent
|
||||
}
|
||||
set {
|
||||
UserDefaults.standard.set(newValue.rawValue, forKey: "mapOverlayServer")
|
||||
}
|
||||
}
|
||||
static var mapTilesAboveLabels: Bool {
|
||||
get {
|
||||
UserDefaults.standard.bool(forKey: "mapTilesAboveLabels")
|
||||
}
|
||||
set {
|
||||
UserDefaults.standard.set(newValue, forKey: "mapTilesAboveLabels")
|
||||
}
|
||||
}
|
||||
|
||||
@UserDefault(.preferredPeripheralId, defaultValue: "")
|
||||
static var preferredPeripheralId: String
|
||||
|
||||
@UserDefault(.preferredPeripheralNum, defaultValue: 0)
|
||||
static var preferredPeripheralNum: Int
|
||||
|
||||
@UserDefault(.provideLocation, defaultValue: false)
|
||||
static var provideLocation: Bool
|
||||
|
||||
@UserDefault(.provideLocationInterval, defaultValue: 0)
|
||||
static var provideLocationInterval: Int
|
||||
|
||||
@UserDefault(.mapLayer, defaultValue: .standard)
|
||||
static var mapLayer: MapLayer
|
||||
|
||||
@UserDefault(.meshMapDistance, defaultValue: 800000)
|
||||
static var meshMapDistance: Double
|
||||
|
||||
static var mapUseLegacy: Bool {
|
||||
get {
|
||||
UserDefaults.standard.bool(forKey: "mapUseLegacy")
|
||||
}
|
||||
set {
|
||||
UserDefaults.standard.set(newValue, forKey: "mapUseLegacy")
|
||||
}
|
||||
}
|
||||
@UserDefault(.enableMapWaypoints, defaultValue: false)
|
||||
static var enableMapWaypoints: Bool
|
||||
|
||||
static var enableDetectionNotifications: Bool {
|
||||
get {
|
||||
UserDefaults.standard.bool(forKey: "enableDetectionNotifications")
|
||||
}
|
||||
set {
|
||||
UserDefaults.standard.set(newValue, forKey: "enableDetectionNotifications")
|
||||
}
|
||||
}
|
||||
|
||||
static var detectionSensorRole: DetectionSensorRole {
|
||||
get {
|
||||
DetectionSensorRole(rawValue: UserDefaults.standard.string(forKey: "detectionSensorRole") ?? DetectionSensorRole.sensor.rawValue) ?? DetectionSensorRole.sensor
|
||||
}
|
||||
set {
|
||||
UserDefaults.standard.set(newValue.rawValue, forKey: "detectionSensorRole")
|
||||
}
|
||||
}
|
||||
static var enableSmartPosition: Bool {
|
||||
get {
|
||||
UserDefaults.standard.bool(forKey: "enableSmartPosition")
|
||||
}
|
||||
set {
|
||||
UserDefaults.standard.set(newValue, forKey: "enableSmartPosition")
|
||||
}
|
||||
}
|
||||
@UserDefault(.enableMapRecentering, defaultValue: false)
|
||||
static var enableMapRecentering: Bool
|
||||
|
||||
@UserDefault(.enableMapNodeHistoryPins, defaultValue: false)
|
||||
static var enableMapNodeHistoryPins: Bool
|
||||
|
||||
@UserDefault(.enableMapRouteLines, defaultValue: false)
|
||||
static var enableMapRouteLines: Bool
|
||||
|
||||
@UserDefault(.enableMapConvexHull, defaultValue: false)
|
||||
static var enableMapConvexHull: Bool
|
||||
|
||||
@UserDefault(.enableMapTraffic, defaultValue: false)
|
||||
static var enableMapTraffic: Bool
|
||||
|
||||
@UserDefault(.enableMapPointsOfInterest, defaultValue: false)
|
||||
static var enableMapPointsOfInterest: Bool
|
||||
|
||||
@UserDefault(.enableOfflineMaps, defaultValue: false)
|
||||
static var enableOfflineMaps: Bool
|
||||
|
||||
@UserDefault(.enableOfflineMapsMBTiles, defaultValue: false)
|
||||
static var enableOfflineMapsMBTiles: Bool
|
||||
|
||||
@UserDefault(.mapTileServer, defaultValue: .openStreetMap)
|
||||
static var mapTileServer: MapTileServer
|
||||
|
||||
@UserDefault(.enableOverlayServer, defaultValue: false)
|
||||
static var enableOverlayServer: Bool
|
||||
|
||||
@UserDefault(.mapOverlayServer, defaultValue: .baseReReflectivityCurrent)
|
||||
static var mapOverlayServer: MapOverlayServer
|
||||
|
||||
@UserDefault(.mapTilesAboveLabels, defaultValue: false)
|
||||
static var mapTilesAboveLabels: Bool
|
||||
|
||||
@UserDefault(.mapUseLegacy, defaultValue: false)
|
||||
static var mapUseLegacy: Bool
|
||||
|
||||
@UserDefault(.enableDetectionNotifications, defaultValue: false)
|
||||
static var enableDetectionNotifications: Bool
|
||||
|
||||
@UserDefault(.detectionSensorRole, defaultValue: .sensor)
|
||||
static var detectionSensorRole: DetectionSensorRole
|
||||
|
||||
@UserDefault(.enableSmartPosition, defaultValue: false)
|
||||
static var enableSmartPosition: Bool
|
||||
|
||||
@UserDefault(.modemPreset, defaultValue: 0)
|
||||
static var modemPreset: Int
|
||||
|
||||
@UserDefault(.firmwareVersion, defaultValue: "0.0.0")
|
||||
static var firmwareVersion: String
|
||||
|
||||
@UserDefault(.testIntEnum, defaultValue: .one)
|
||||
static var testIntEnum: TestIntEnum
|
||||
}
|
||||
|
||||
enum TestIntEnum: Int, Decodable {
|
||||
case one = 1
|
||||
case two
|
||||
case three
|
||||
}
|
||||
|
|
|
|||
|
|
@ -582,6 +582,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
nowKnown = true
|
||||
connectedVersion = String(version.dropLast())
|
||||
appState.firmwareVersion = connectedVersion
|
||||
UserDefaults.firmwareVersion = connectedVersion
|
||||
}
|
||||
let supportedVersion = connectedVersion == "0.0.0" || self.minimumVersion.compare(connectedVersion, options: .numeric) == .orderedAscending || minimumVersion.compare(connectedVersion, options: .numeric) == .orderedSame
|
||||
if !supportedVersion {
|
||||
|
|
@ -607,11 +608,14 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
case .adminApp:
|
||||
adminAppPacket(packet: decodedInfo.packet, context: context!)
|
||||
case .replyApp:
|
||||
MeshLogger.log("🕸️ MESH PACKET received for Reply App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")")
|
||||
MeshLogger.log("🕸️ MESH PACKET received for Reply App handling as a text message")
|
||||
textMessageAppPacket(packet: decodedInfo.packet, wantRangeTestPackets: wantRangeTestPackets, connectedNode: (self.connectedPeripheral != nil ? connectedPeripheral.num : 0), context: context!)
|
||||
case .ipTunnelApp:
|
||||
MeshLogger.log("🕸️ MESH PACKET received for IP Tunnel App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")")
|
||||
//MeshLogger.log("🕸️ MESH PACKET received for IP Tunnel App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")")
|
||||
MeshLogger.log("🕸️ MESH PACKET received for IP Tunnel App UNHANDLED UNHANDLED")
|
||||
case .serialApp:
|
||||
MeshLogger.log("🕸️ MESH PACKET received for Serial App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")")
|
||||
//MeshLogger.log("🕸️ MESH PACKET received for Serial App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")")
|
||||
MeshLogger.log("🕸️ MESH PACKET received for Serial App UNHANDLED UNHANDLED")
|
||||
case .storeForwardApp:
|
||||
if wantStoreAndForwardPackets {
|
||||
storeAndForwardPacket(packet: decodedInfo.packet, connectedNodeNum: (self.connectedPeripheral != nil ? connectedPeripheral.num : 0), context: context!)
|
||||
|
|
@ -628,17 +632,23 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
case .telemetryApp:
|
||||
if !invalidVersion { telemetryPacket(packet: decodedInfo.packet, connectedNode: (self.connectedPeripheral != nil ? connectedPeripheral.num : 0), context: context!) }
|
||||
case .textMessageCompressedApp:
|
||||
MeshLogger.log("🕸️ MESH PACKET received for Text Message Compressed App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")")
|
||||
//MeshLogger.log("🕸️ MESH PACKET received for Text Message Compressed App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")")
|
||||
MeshLogger.log("🕸️ MESH PACKET received for Text Message Compressed App UNHANDLED")
|
||||
case .zpsApp:
|
||||
MeshLogger.log("🕸️ MESH PACKET received for ZPS App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")")
|
||||
// MeshLogger.log("🕸️ MESH PACKET received for Zero Positioning System App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")")
|
||||
MeshLogger.log("🕸️ MESH PACKET received for Zero Positioning System App UNHANDLED")
|
||||
case .privateApp:
|
||||
MeshLogger.log("🕸️ MESH PACKET received for Private App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")")
|
||||
//MeshLogger.log("🕸️ MESH PACKET received for Private App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")")
|
||||
MeshLogger.log("🕸️ MESH PACKET received for Private App UNHANDLED UNHANDLED")
|
||||
case .atakForwarder:
|
||||
MeshLogger.log("🕸️ MESH PACKET received for ATAK Forwarder App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")")
|
||||
//MeshLogger.log("🕸️ MESH PACKET received for ATAK Forwarder App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")")
|
||||
MeshLogger.log("🕸️ MESH PACKET received for ATAK Forwarder App UNHANDLED UNHANDLED")
|
||||
case .simulatorApp:
|
||||
MeshLogger.log("🕸️ MESH PACKET received for Simulator App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")")
|
||||
//MeshLogger.log("🕸️ MESH PACKET received for Simulator App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")")
|
||||
MeshLogger.log("🕸️ MESH PACKET received for Simulator App UNHANDLED UNHANDLED")
|
||||
case .audioApp:
|
||||
MeshLogger.log("🕸️ MESH PACKET received for Audio App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")")
|
||||
//MeshLogger.log("🕸️ MESH PACKET received for Audio App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")")
|
||||
MeshLogger.log("🕸️ MESH PACKET received for Audio App UNHANDLED UNHANDLED")
|
||||
case .tracerouteApp:
|
||||
if let routingMessage = try? RouteDiscovery(serializedData: decodedInfo.packet.decoded.payload) {
|
||||
let traceRoute = getTraceRoute(id: Int64(decodedInfo.packet.decoded.requestID), context: context!)
|
||||
|
|
@ -700,12 +710,15 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
}
|
||||
case .paxcounterApp:
|
||||
paxCounterPacket(packet: decodedInfo.packet, context: context!)
|
||||
case .mapReportApp:
|
||||
MeshLogger.log("🕸️ MESH PACKET received for Map Report App UNHANDLED\(try! decodedInfo.packet.jsonString())")
|
||||
case .UNRECOGNIZED:
|
||||
MeshLogger.log("🕸️ MESH PACKET received for Other App UNHANDLED \(try! decodedInfo.packet.jsonString())")
|
||||
case .max:
|
||||
print("MAX PORT NUM OF 511")
|
||||
case .atakPlugin:
|
||||
MeshLogger.log("🕸️ MESH PACKET received for ATAK Plugin App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")")
|
||||
|
||||
}
|
||||
|
||||
if decodedInfo.configCompleteID != 0 && decodedInfo.configCompleteID == configNonce {
|
||||
|
|
@ -970,9 +983,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
return success
|
||||
}
|
||||
|
||||
public func sendPosition(channel: Int32, destNum: Int64, wantResponse: Bool) -> Bool {
|
||||
var success = false
|
||||
let fromNodeNum = connectedPeripheral.num
|
||||
public func getPositionFromPhoneGPS(channel: Int32, destNum: Int64) -> Position? {
|
||||
var positionPacket = Position()
|
||||
|
||||
let fetchChannelRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "ChannelEntity")
|
||||
|
|
@ -980,7 +991,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
|
||||
do {
|
||||
guard let fetchedChannel = try context!.fetch(fetchChannelRequest) as? [ChannelEntity] else {
|
||||
return false
|
||||
return nil
|
||||
}
|
||||
if #available(iOS 17.0, macOS 14.0, *) {
|
||||
|
||||
|
|
@ -1006,8 +1017,8 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
}
|
||||
|
||||
} else {
|
||||
if fromNodeNum <= 0 || LocationHelper.currentLocation.distance(from: LocationHelper.DefaultLocation) == 0.0 {
|
||||
return false
|
||||
if destNum <= 0 || LocationHelper.currentLocation.distance(from: LocationHelper.DefaultLocation) == 0.0 {
|
||||
return nil
|
||||
}
|
||||
positionPacket.latitudeI = Int32(LocationHelper.currentLocation.latitude * 1e7)
|
||||
positionPacket.longitudeI = Int32(LocationHelper.currentLocation.longitude * 1e7)
|
||||
|
|
@ -1028,11 +1039,63 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
}
|
||||
|
||||
} catch {
|
||||
return nil
|
||||
}
|
||||
|
||||
return positionPacket
|
||||
}
|
||||
|
||||
public func setFixedPosition(fromUser: UserEntity, channel: Int32) -> Bool {
|
||||
var adminPacket = AdminMessage()
|
||||
guard let positionPacket = getPositionFromPhoneGPS(channel: channel, destNum: fromUser.num) else {
|
||||
return false
|
||||
}
|
||||
adminPacket.setFixedPosition = positionPacket
|
||||
var meshPacket: MeshPacket = MeshPacket()
|
||||
meshPacket.to = UInt32(fromUser.num)
|
||||
meshPacket.from = UInt32(fromUser.num)
|
||||
meshPacket.id = UInt32.random(in: UInt32(UInt8.max)..<UInt32.max)
|
||||
meshPacket.priority = MeshPacket.Priority.reliable
|
||||
meshPacket.wantAck = true
|
||||
meshPacket.channel = UInt32(channel)
|
||||
var dataMessage = DataMessage()
|
||||
dataMessage.payload = try! adminPacket.serializedData()
|
||||
dataMessage.portnum = PortNum.adminApp
|
||||
meshPacket.decoded = dataMessage
|
||||
let messageDescription = "🚀 Sent Set Fixed Postion Admin Message to: \(fromUser.longName ?? "unknown".localized) from: \(fromUser.longName ?? "unknown".localized)"
|
||||
if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: fromUser) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
||||
|
||||
}
|
||||
|
||||
public func removeFixedPosition(fromUser: UserEntity, channel: Int32) -> Bool {
|
||||
var adminPacket = AdminMessage()
|
||||
adminPacket.removeFixedPosition = true
|
||||
var meshPacket: MeshPacket = MeshPacket()
|
||||
meshPacket.to = UInt32(fromUser.num)
|
||||
meshPacket.from = UInt32(fromUser.num)
|
||||
meshPacket.id = UInt32.random(in: UInt32(UInt8.max)..<UInt32.max)
|
||||
meshPacket.priority = MeshPacket.Priority.reliable
|
||||
meshPacket.wantAck = true
|
||||
meshPacket.channel = UInt32(channel)
|
||||
var dataMessage = DataMessage()
|
||||
dataMessage.payload = try! adminPacket.serializedData()
|
||||
dataMessage.portnum = PortNum.adminApp
|
||||
meshPacket.decoded = dataMessage
|
||||
let messageDescription = "🚀 Sent Remove Fixed Position Admin Message to: \(fromUser.longName ?? "unknown".localized) from: \(fromUser.longName ?? "unknown".localized)"
|
||||
if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: fromUser) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
public func sendPosition(channel: Int32, destNum: Int64, wantResponse: Bool) -> Bool {
|
||||
var success = false
|
||||
let fromNodeNum = connectedPeripheral.num
|
||||
guard let positionPacket = getPositionFromPhoneGPS(channel: channel, destNum: destNum) else {
|
||||
return false
|
||||
}
|
||||
|
||||
var meshPacket = MeshPacket()
|
||||
meshPacket.to = UInt32(destNum)
|
||||
|
|
|
|||
|
|
@ -60,14 +60,10 @@ import CoreLocation
|
|||
self.isStationary = update.isStationary
|
||||
|
||||
var locationAdded: Bool
|
||||
if enableSmartPosition {
|
||||
locationAdded = addLocation(loc)
|
||||
//print("Added Location \(self.count): \(loc)")
|
||||
} else {
|
||||
locationsArray.append(loc)
|
||||
locationAdded = true
|
||||
}
|
||||
if locationAdded {
|
||||
locationAdded = addLocation(loc, smartPostion: enableSmartPosition)
|
||||
if !isRecording && locationAdded {
|
||||
self.count = 1
|
||||
} else if locationAdded && isRecording {
|
||||
self.count += 1
|
||||
}
|
||||
}
|
||||
|
|
@ -84,19 +80,21 @@ import CoreLocation
|
|||
self.updatesStarted = false
|
||||
}
|
||||
|
||||
func addLocation(_ location: CLLocation) -> Bool {
|
||||
let age = -location.timestamp.timeIntervalSinceNow
|
||||
if age > 10 {
|
||||
print("Bad Location \(self.count): Too Old \(age) seconds ago \(location)")
|
||||
return false
|
||||
}
|
||||
if location.horizontalAccuracy < 0 {
|
||||
print("Bad Location \(self.count): Horizontal Accuracy: \(location.horizontalAccuracy) \(location)")
|
||||
return false
|
||||
}
|
||||
if location.horizontalAccuracy > 25 {
|
||||
print("Bad Location \(self.count): Horizontal Accuracy: \(location.horizontalAccuracy) \(location)")
|
||||
return false
|
||||
func addLocation(_ location: CLLocation, smartPostion: Bool) -> Bool {
|
||||
if smartPostion {
|
||||
let age = -location.timestamp.timeIntervalSinceNow
|
||||
if age > 10 {
|
||||
print("Bad Location \(self.count): Too Old \(age) seconds ago \(location)")
|
||||
return false
|
||||
}
|
||||
if location.horizontalAccuracy < 0 {
|
||||
print("Bad Location \(self.count): Horizontal Accuracy: \(location.horizontalAccuracy) \(location)")
|
||||
return false
|
||||
}
|
||||
if location.horizontalAccuracy > 25 {
|
||||
print("Bad Location \(self.count): Horizontal Accuracy: \(location.horizontalAccuracy) \(location)")
|
||||
return false
|
||||
}
|
||||
}
|
||||
if isRecording {
|
||||
if let lastLocation = locationsArray.last {
|
||||
|
|
@ -107,8 +105,10 @@ import CoreLocation
|
|||
elevationGain += gain
|
||||
}
|
||||
}
|
||||
locationsArray.append(location)
|
||||
} else {
|
||||
locationsArray = [location]
|
||||
}
|
||||
locationsArray.append(location)
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,28 +13,34 @@ import ActivityKit
|
|||
#endif
|
||||
|
||||
func generateMessageMarkdown (message: String) -> String {
|
||||
let types: NSTextCheckingResult.CheckingType = [.address, .link, .phoneNumber]
|
||||
let detector = try! NSDataDetector(types: types.rawValue)
|
||||
let matches = detector.matches(in: message, options: [], range: NSRange(location: 0, length: message.utf16.count))
|
||||
var messageWithMarkdown = message
|
||||
if matches.count > 0 {
|
||||
for match in matches {
|
||||
guard let range = Range(match.range, in: message) else { continue }
|
||||
if match.resultType == .address {
|
||||
let address = message[range]
|
||||
let urlEncodedAddress = address.addingPercentEncoding(withAllowedCharacters: .alphanumerics)
|
||||
messageWithMarkdown = messageWithMarkdown.replacingOccurrences(of: address, with: "[\(address)](http://maps.apple.com/?address=\(urlEncodedAddress ?? ""))")
|
||||
} else if match.resultType == .phoneNumber {
|
||||
let phone = messageWithMarkdown[range]
|
||||
messageWithMarkdown = messageWithMarkdown.replacingOccurrences(of: phone, with: "[\(phone)](tel:\(phone))")
|
||||
} else if match.resultType == .link {
|
||||
let url = messageWithMarkdown[range]
|
||||
let absoluteUrl = match.url?.absoluteString ?? ""
|
||||
messageWithMarkdown = messageWithMarkdown.replacingOccurrences(of: url, with: "[\(String(match.url?.host ?? "Link"))\(String(match.url?.path ?? ""))](\(absoluteUrl))")
|
||||
if !message.isEmoji() {
|
||||
let types: NSTextCheckingResult.CheckingType = [.address, .link, .phoneNumber]
|
||||
let detector = try! NSDataDetector(types: types.rawValue)
|
||||
let matches = detector.matches(in: message, options: [], range: NSRange(location: 0, length: message.utf16.count))
|
||||
var messageWithMarkdown = message
|
||||
if matches.count > 0 {
|
||||
for match in matches {
|
||||
guard let range = Range(match.range, in: message) else { continue }
|
||||
if match.resultType == .address {
|
||||
let address = message[range]
|
||||
let urlEncodedAddress = address.addingPercentEncoding(withAllowedCharacters: .alphanumerics)
|
||||
messageWithMarkdown = messageWithMarkdown.replacingOccurrences(of: address, with: "[\(address)](http://maps.apple.com/?address=\(urlEncodedAddress ?? ""))")
|
||||
} else if match.resultType == .phoneNumber {
|
||||
let phone = messageWithMarkdown[range]
|
||||
messageWithMarkdown = messageWithMarkdown.replacingOccurrences(of: phone, with: "[\(phone)](tel:\(phone))")
|
||||
} else if match.resultType == .link {
|
||||
let start = match.range.lowerBound
|
||||
let stop = match.range.upperBound
|
||||
let url = message[start ..< stop]
|
||||
let absoluteUrl = match.url?.absoluteString ?? ""
|
||||
let markdownUrl = "[\(url)](\(absoluteUrl))"
|
||||
messageWithMarkdown = messageWithMarkdown.replacingOccurrences(of: url, with: markdownUrl)
|
||||
}
|
||||
}
|
||||
}
|
||||
return messageWithMarkdown
|
||||
}
|
||||
return messageWithMarkdown
|
||||
return message
|
||||
}
|
||||
|
||||
func localConfig (config: Config, context: NSManagedObjectContext, nodeNum: Int64, nodeLongName: String) {
|
||||
|
|
@ -257,6 +263,8 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje
|
|||
newNode.id = Int64(nodeInfo.num)
|
||||
newNode.num = Int64(nodeInfo.num)
|
||||
newNode.channel = Int32(nodeInfo.channel)
|
||||
newNode.favorite = nodeInfo.isFavorite
|
||||
newNode.hopsAway = Int32(nodeInfo.hopsAway)
|
||||
|
||||
if nodeInfo.hasDeviceMetrics {
|
||||
let telemetry = TelemetryEntity(context: context)
|
||||
|
|
@ -340,6 +348,8 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje
|
|||
fetchedNode[0].lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(nodeInfo.lastHeard)))
|
||||
fetchedNode[0].snr = nodeInfo.snr
|
||||
fetchedNode[0].channel = Int32(nodeInfo.channel)
|
||||
fetchedNode[0].favorite = nodeInfo.isFavorite
|
||||
fetchedNode[0].hopsAway = Int32(nodeInfo.hopsAway)
|
||||
|
||||
if nodeInfo.hasUser {
|
||||
if (fetchedNode[0].user == nil) {
|
||||
|
|
@ -724,24 +734,6 @@ func telemetryPacket(packet: MeshPacket, connectedNode: Int64, context: NSManage
|
|||
)
|
||||
]
|
||||
manager.schedule()
|
||||
|
||||
// let content = UNMutableNotificationContent()
|
||||
// content.title = "Critically Low Battery!"
|
||||
// content.body = "Time to charge your radio, there is \(telemetry.batteryLevel)% battery remaining."
|
||||
// content.userInfo["target"] = "node"
|
||||
// content.userInfo["path"] = "meshtastic://node/\(telemetry.nodeTelemetry?.num ?? 0)"
|
||||
// let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false)
|
||||
// let uuidString = UUID().uuidString
|
||||
// let request = UNNotificationRequest(identifier: uuidString, content: content, trigger: trigger)
|
||||
// let notificationCenter = UNUserNotificationCenter.current()
|
||||
// notificationCenter.add(request) { (error) in
|
||||
// if error != nil {
|
||||
// // Handle any errors.
|
||||
// print("Error creating local low battery notification: \(error?.localizedDescription ?? "no description")")
|
||||
// } else {
|
||||
// print("Created local low battery notification.")
|
||||
// }
|
||||
// }
|
||||
}
|
||||
// Update our live activity if there is one running, not available on mac iOS >= 16.2
|
||||
#if !targetEnvironment(macCatalyst)
|
||||
|
|
@ -775,7 +767,7 @@ func telemetryPacket(packet: MeshPacket, connectedNode: Int64, context: NSManage
|
|||
func textMessageAppPacket(packet: MeshPacket, wantRangeTestPackets: Bool, connectedNode: Int64, storeForward: Bool = false, context: NSManagedObjectContext) {
|
||||
|
||||
var messageText = String(bytes: packet.decoded.payload, encoding: .utf8)
|
||||
if !wantRangeTestPackets && ((messageText?.starts(with: "seq ")) != nil) {
|
||||
if !wantRangeTestPackets && (String(messageText ?? "seq ").starts(with: "seq ")) {
|
||||
return
|
||||
}
|
||||
var storeForwardBroadcast = false
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ class MqttClientProxyManager {
|
|||
private static let defaultKeepAliveInterval: Int32 = 60
|
||||
weak var delegate: MqttClientProxyManagerDelegate?
|
||||
var mqttClientProxy: CocoaMQTT?
|
||||
var topic = "msh/2/c"
|
||||
var topic = "msh"
|
||||
var debugLog = false
|
||||
func connectFromConfigSettings(node: NodeInfoEntity) {
|
||||
let defaultServerAddress = "mqtt.meshtastic.org"
|
||||
|
|
@ -36,13 +36,17 @@ class MqttClientProxyManager {
|
|||
defaultServerPort = Int(fullHost.components(separatedBy: ":")[1]) ?? (useSsl ? 8883 : 1883)
|
||||
}
|
||||
}
|
||||
let minimumVersion = "2.3.2"
|
||||
let currentVersion = UserDefaults.firmwareVersion
|
||||
let supportedVersion = minimumVersion.compare(currentVersion, options: .numeric) == .orderedAscending || minimumVersion.compare(currentVersion, options: .numeric) == .orderedSame
|
||||
|
||||
if let host = host {
|
||||
let port = defaultServerPort
|
||||
let username = node.mqttConfig?.username
|
||||
let password = node.mqttConfig?.password
|
||||
let root = node.mqttConfig?.root?.count ?? 0 > 0 ? node.mqttConfig?.root : "msh"
|
||||
let prefix = root! + "/2/c"
|
||||
topic = prefix + "/#"
|
||||
let prefix = root!
|
||||
topic = prefix + (supportedVersion ? "/2/e" : "/2/c") + "/#"
|
||||
let qos = CocoaMQTTQoS(rawValue: UInt8(1))!
|
||||
connect(host: host, port: port, useSsl: useSsl, username: username, password: password, topic: topic, qos: qos, cleanSession: true)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,6 @@
|
|||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>_XCCurrentVersionName</key>
|
||||
<string>MeshtasticDataModelV 28.xcdatamodel</string>
|
||||
<string>MeshtasticDataModelV 31.xcdatamodel</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="22522" systemVersion="23E5205c" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="22522" systemVersion="23E5211a" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
||||
<entity name="AmbientLightingConfigEntity" representedClassName="AmbientLightingConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="blue" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="current" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
|
|
@ -230,8 +230,8 @@
|
|||
<entity name="NodeInfoEntity" representedClassName="NodeInfoEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="bleName" optional="YES" attributeType="String"/>
|
||||
<attribute name="channel" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="detection" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="environment" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="detection" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="environment" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="id" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="lastHeard" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="num" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
|
|
@ -239,31 +239,31 @@
|
|||
<attribute name="rssi" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="viaMqtt" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<relationship name="ambientLightingConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="AmbientLightingConfigEntity" inverseName="ambientLightingConfigNode" inverseEntity="AmbientLightingConfigEntity"/>
|
||||
<relationship name="bluetoothConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="BluetoothConfigEntity" inverseName="bluetoothConfigNode" inverseEntity="BluetoothConfigEntity"/>
|
||||
<relationship name="cannedMessageConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="CannedMessageConfigEntity" inverseName="cannedMessagesConfigNode" inverseEntity="CannedMessageConfigEntity"/>
|
||||
<relationship name="detectionSensorConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="DetectionSensorConfigEntity" inverseName="detectionSensorConfigNode" inverseEntity="DetectionSensorConfigEntity"/>
|
||||
<relationship name="deviceConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="DeviceConfigEntity" inverseName="deviceConfigNode" inverseEntity="DeviceConfigEntity"/>
|
||||
<relationship name="displayConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="DisplayConfigEntity" inverseName="displayConfigNode" inverseEntity="DisplayConfigEntity"/>
|
||||
<relationship name="externalNotificationConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="ExternalNotificationConfigEntity" inverseName="externalNotificationConfigNode" inverseEntity="ExternalNotificationConfigEntity"/>
|
||||
<relationship name="loRaConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="LoRaConfigEntity" inverseName="loRaConfigNode" inverseEntity="LoRaConfigEntity"/>
|
||||
<relationship name="metadata" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="DeviceMetadataEntity" inverseName="metadataNode" inverseEntity="DeviceMetadataEntity"/>
|
||||
<relationship name="mqttConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MQTTConfigEntity" inverseName="mqttConfigNode" inverseEntity="MQTTConfigEntity"/>
|
||||
<relationship name="myInfo" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MyInfoEntity" inverseName="myInfoNode" inverseEntity="MyInfoEntity"/>
|
||||
<relationship name="networkConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NetworkConfigEntity" inverseName="networkConfigNode" inverseEntity="NetworkConfigEntity"/>
|
||||
<relationship name="pax" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="PaxCounterEntity" inverseName="paxNode" inverseEntity="PaxCounterEntity"/>
|
||||
<relationship name="paxCounterConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="PaxCounterConfigEntity" inverseName="paxCounterConfigNode" inverseEntity="PaxCounterConfigEntity"/>
|
||||
<relationship name="positionConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="PositionConfigEntity" inverseName="positionConfigNode" inverseEntity="PositionConfigEntity"/>
|
||||
<relationship name="positions" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="PositionEntity" inverseName="nodePosition" inverseEntity="PositionEntity"/>
|
||||
<relationship name="powerConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="PowerConfigEntity" inverseName="powerConfigNode" inverseEntity="PowerConfigEntity"/>
|
||||
<relationship name="rangeTestConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="RangeTestConfigEntity" inverseName="rangeTestConfigNode" inverseEntity="RangeTestConfigEntity"/>
|
||||
<relationship name="rtttlConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="RTTTLConfigEntity" inverseName="rtttlConfigNode" inverseEntity="RTTTLConfigEntity"/>
|
||||
<relationship name="serialConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="SerialConfigEntity" inverseName="serialConfigNode" inverseEntity="SerialConfigEntity"/>
|
||||
<relationship name="storeForwardConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="StoreForwardConfigEntity" inverseName="storeForwardConfigNode" inverseEntity="StoreForwardConfigEntity"/>
|
||||
<relationship name="telemetries" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="TelemetryEntity" inverseName="nodeTelemetry" inverseEntity="TelemetryEntity"/>
|
||||
<relationship name="telemetryConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="TelemetryConfigEntity" inverseName="telemetryConfigNode" inverseEntity="TelemetryConfigEntity"/>
|
||||
<relationship name="traceRoutes" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="TraceRouteEntity" inverseName="node" inverseEntity="TraceRouteEntity"/>
|
||||
<relationship name="user" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="UserEntity" inverseName="userNode" inverseEntity="UserEntity"/>
|
||||
<relationship name="ambientLightingConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="AmbientLightingConfigEntity" inverseName="ambientLightingConfigNode" inverseEntity="AmbientLightingConfigEntity"/>
|
||||
<relationship name="bluetoothConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="BluetoothConfigEntity" inverseName="bluetoothConfigNode" inverseEntity="BluetoothConfigEntity"/>
|
||||
<relationship name="cannedMessageConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="CannedMessageConfigEntity" inverseName="cannedMessagesConfigNode" inverseEntity="CannedMessageConfigEntity"/>
|
||||
<relationship name="detectionSensorConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="DetectionSensorConfigEntity" inverseName="detectionSensorConfigNode" inverseEntity="DetectionSensorConfigEntity"/>
|
||||
<relationship name="deviceConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="DeviceConfigEntity" inverseName="deviceConfigNode" inverseEntity="DeviceConfigEntity"/>
|
||||
<relationship name="displayConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="DisplayConfigEntity" inverseName="displayConfigNode" inverseEntity="DisplayConfigEntity"/>
|
||||
<relationship name="externalNotificationConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="ExternalNotificationConfigEntity" inverseName="externalNotificationConfigNode" inverseEntity="ExternalNotificationConfigEntity"/>
|
||||
<relationship name="loRaConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="LoRaConfigEntity" inverseName="loRaConfigNode" inverseEntity="LoRaConfigEntity"/>
|
||||
<relationship name="metadata" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="DeviceMetadataEntity" inverseName="metadataNode" inverseEntity="DeviceMetadataEntity"/>
|
||||
<relationship name="mqttConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="MQTTConfigEntity" inverseName="mqttConfigNode" inverseEntity="MQTTConfigEntity"/>
|
||||
<relationship name="myInfo" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="MyInfoEntity" inverseName="myInfoNode" inverseEntity="MyInfoEntity"/>
|
||||
<relationship name="networkConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="NetworkConfigEntity" inverseName="networkConfigNode" inverseEntity="NetworkConfigEntity"/>
|
||||
<relationship name="pax" optional="YES" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="PaxCounterEntity" inverseName="paxNode" inverseEntity="PaxCounterEntity"/>
|
||||
<relationship name="paxCounterConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="PaxCounterConfigEntity" inverseName="paxCounterConfigNode" inverseEntity="PaxCounterConfigEntity"/>
|
||||
<relationship name="positionConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="PositionConfigEntity" inverseName="positionConfigNode" inverseEntity="PositionConfigEntity"/>
|
||||
<relationship name="positions" optional="YES" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="PositionEntity" inverseName="nodePosition" inverseEntity="PositionEntity"/>
|
||||
<relationship name="powerConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="PowerConfigEntity" inverseName="powerConfigNode" inverseEntity="PowerConfigEntity"/>
|
||||
<relationship name="rangeTestConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="RangeTestConfigEntity" inverseName="rangeTestConfigNode" inverseEntity="RangeTestConfigEntity"/>
|
||||
<relationship name="rtttlConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="RTTTLConfigEntity" inverseName="rtttlConfigNode" inverseEntity="RTTTLConfigEntity"/>
|
||||
<relationship name="serialConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="SerialConfigEntity" inverseName="serialConfigNode" inverseEntity="SerialConfigEntity"/>
|
||||
<relationship name="storeForwardConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="StoreForwardConfigEntity" inverseName="storeForwardConfigNode" inverseEntity="StoreForwardConfigEntity"/>
|
||||
<relationship name="telemetries" optional="YES" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="TelemetryEntity" inverseName="nodeTelemetry" inverseEntity="TelemetryEntity"/>
|
||||
<relationship name="telemetryConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="TelemetryConfigEntity" inverseName="telemetryConfigNode" inverseEntity="TelemetryConfigEntity"/>
|
||||
<relationship name="traceRoutes" optional="YES" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="TraceRouteEntity" inverseName="node" inverseEntity="TraceRouteEntity"/>
|
||||
<relationship name="user" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="UserEntity" inverseName="userNode" inverseEntity="UserEntity"/>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="num"/>
|
||||
|
|
@ -422,8 +422,8 @@
|
|||
<attribute name="shortName" attributeType="String"/>
|
||||
<attribute name="userId" attributeType="String"/>
|
||||
<attribute name="vip" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<relationship name="receivedMessages" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="MessageEntity" inverseName="toUser" inverseEntity="MessageEntity"/>
|
||||
<relationship name="sentMessages" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="MessageEntity" inverseName="fromUser" inverseEntity="MessageEntity"/>
|
||||
<relationship name="receivedMessages" optional="YES" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="MessageEntity" inverseName="toUser" inverseEntity="MessageEntity"/>
|
||||
<relationship name="sentMessages" optional="YES" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="MessageEntity" inverseName="fromUser" inverseEntity="MessageEntity"/>
|
||||
<relationship name="userNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="user" inverseEntity="NodeInfoEntity"/>
|
||||
<fetchedProperty name="adminMessages" optional="YES">
|
||||
<fetchRequest name="fetchedPropertyFetchRequest" entity="MessageEntity" predicateString="(fromUser.num == $FETCH_SOURCE.num) AND isEmoji == false AND admin = true"/>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,449 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="22522" systemVersion="23E5211a" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
||||
<entity name="AmbientLightingConfigEntity" representedClassName="AmbientLightingConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="blue" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="current" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="green" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="ledState" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="red" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="ambientLightingConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="ambientLightingConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="BluetoothConfigEntity" representedClassName="BluetoothConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="fixedPin" optional="YES" attributeType="Integer 32" defaultValueString="123456" usesScalarValueType="YES"/>
|
||||
<attribute name="mode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="bluetoothConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="bluetoothConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="CannedMessageConfigEntity" representedClassName="CannedMessageConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerEventCcw" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerEventCw" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerEventPress" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerPinA" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerPinB" optional="YES" attributeType="Integer 32" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerPinPress" optional="YES" attributeType="Integer 32" usesScalarValueType="YES"/>
|
||||
<attribute name="messages" optional="YES" attributeType="String" minValueString="0" maxValueString="198"/>
|
||||
<attribute name="rotary1Enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="sendBell" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="updown1Enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<relationship name="cannedMessagesConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="cannedMessageConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="ChannelEntity" representedClassName="ChannelEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="downlinkEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="id" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="index" attributeType="Integer 32" minValueString="0" maxValueString="13" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="mute" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="name" optional="YES" attributeType="String"/>
|
||||
<attribute name="positionPrecision" optional="YES" attributeType="Integer 32" defaultValueString="32" usesScalarValueType="YES"/>
|
||||
<attribute name="psk" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="role" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="uplinkEnabled" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<relationship name="myInfoChannel" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MyInfoEntity" inverseName="channels" inverseEntity="MyInfoEntity"/>
|
||||
<fetchedProperty name="allPrivateMessages" optional="YES">
|
||||
<fetchRequest name="fetchedPropertyFetchRequest" entity="MessageEntity" predicateString="channel == $FETCH_SOURCE.index && toUser == nil AND isEmoji == false"/>
|
||||
</fetchedProperty>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="index"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="DetectionSensorConfigEntity" representedClassName="DetectionSensorConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="detectionTriggeredHigh" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="minimumBroadcastSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="monitorPin" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="name" optional="YES" attributeType="String"/>
|
||||
<attribute name="sendBell" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="stateBroadcastSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="usePullup" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<relationship name="detectionSensorConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="detectionSensorConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="DeviceConfigEntity" representedClassName="DeviceConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="buttonGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="buzzerGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="debugLogEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="disableTripleClick" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="doubleTapAsButtonPress" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="isManaged" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="nodeInfoBroadcastSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="rebroadcastMode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="role" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="serialEnabled" optional="YES" attributeType="Boolean" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="deviceConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="deviceConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="DeviceHardwareEntity" representedClassName="DeviceHardwareEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="activelySupported" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="architecture" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="displayName" optional="YES" attributeType="String"/>
|
||||
<attribute name="hwModel" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="hwModelSlug" optional="YES" attributeType="String"/>
|
||||
<attribute name="platformioTarget" optional="YES" attributeType="String"/>
|
||||
</entity>
|
||||
<entity name="DeviceMetadataEntity" representedClassName="DeviceMetadataEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="canShutdown" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="deviceStateVersion" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="firmwareVersion" optional="YES" attributeType="String"/>
|
||||
<attribute name="hasBluetooth" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="hasEthernet" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="hasWifi" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="hwModel" optional="YES" attributeType="String"/>
|
||||
<attribute name="positionFlags" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="role" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="metadataNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="metadata" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="DisplayConfigEntity" representedClassName="DisplayConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="compassNorthTop" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="displayMode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="flipScreen" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="gpsFormat" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="headingBold" optional="YES" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<attribute name="oledType" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="screenCarouselInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="screenOnSeconds" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="units" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="wakeOnTapOrMotion" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<relationship name="displayConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="displayConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="ExternalNotificationConfigEntity" representedClassName="ExternalNotificationConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="active" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="alertBell" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="alertBellBuzzer" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="alertBellVibra" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="alertMessage" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="alertMessageBuzzer" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="alertMessageVibra" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="nagTimeout" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="output" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="outputBuzzer" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="outputMilliseconds" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="outputVibra" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="useI2SAsBuzzer" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="usePWM" optional="YES" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<relationship name="externalNotificationConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="externalNotificationConfig" inverseEntity="NodeInfoEntity"/>
|
||||
<fetchedProperty name="fetchedProperty" optional="YES">
|
||||
<fetchRequest name="fetchedPropertyFetchRequest" entity="ExternalNotificationConfigEntity"/>
|
||||
</fetchedProperty>
|
||||
</entity>
|
||||
<entity name="LocationEntity" representedClassName="LocationEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="altitude" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="heading" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="id" optional="YES" attributeType="Integer 32" usesScalarValueType="YES"/>
|
||||
<attribute name="latitudeI" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="longitudeI" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="speed" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="routeLocation" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="RouteEntity" inverseName="locations" inverseEntity="RouteEntity"/>
|
||||
</entity>
|
||||
<entity name="LoRaConfigEntity" representedClassName="LoRaConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="bandwidth" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="channelNum" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="codingRate" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="frequencyOffset" optional="YES" attributeType="Float" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="hopLimit" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="ignoreMqtt" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="modemPreset" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="overrideDutyCycle" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="overrideFrequency" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="regionCode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="spreadFactor" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="sx126xRxBoostedGain" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="txEnabled" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<attribute name="txPower" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="usePreset" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<relationship name="loRaConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="loRaConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="MessageEntity" representedClassName="MessageEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="ackError" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="ackSNR" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="ackTimestamp" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="admin" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="adminDescription" optional="YES" attributeType="String"/>
|
||||
<attribute name="channel" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="isEmoji" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="messageId" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="messagePayload" optional="YES" attributeType="String" defaultValueString=""/>
|
||||
<attribute name="messagePayloadMarkdown" optional="YES" attributeType="String"/>
|
||||
<attribute name="messageTimestamp" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="portNum" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="read" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="realACK" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="receivedACK" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="receivedTimestamp" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="replyID" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="rssi" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<relationship name="fromUser" optional="YES" maxCount="1" deletionRule="Nullify" ordered="YES" destinationEntity="UserEntity" inverseName="sentMessages" inverseEntity="UserEntity"/>
|
||||
<relationship name="toUser" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="UserEntity" inverseName="receivedMessages" inverseEntity="UserEntity"/>
|
||||
<fetchedProperty name="tapbacks" optional="YES">
|
||||
<fetchRequest name="fetchedPropertyFetchRequest" entity="MessageEntity" predicateString="replyID == $FETCH_SOURCE.messageId AND isEmoji == true"/>
|
||||
</fetchedProperty>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="messageId"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="MQTTConfigEntity" representedClassName="MQTTConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="address" optional="YES" attributeType="String" maxValueString="30"/>
|
||||
<attribute name="enabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="encryptionEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="jsonEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="password" optional="YES" attributeType="String" maxValueString="30"/>
|
||||
<attribute name="proxyToClientEnabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="root" optional="YES" attributeType="String" defaultValueString="msh"/>
|
||||
<attribute name="tlsEnabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="username" optional="YES" attributeType="String" maxValueString="30"/>
|
||||
<relationship name="mqttConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="mqttConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="MyInfoEntity" representedClassName="MyInfoEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="adminIndex" optional="YES" attributeType="Integer 32" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="bleName" optional="YES" attributeType="String"/>
|
||||
<attribute name="minAppVersion" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="myNodeNum" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="peripheralId" optional="YES" attributeType="String"/>
|
||||
<attribute name="rebootCount" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="channels" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="ChannelEntity" inverseName="myInfoChannel" inverseEntity="ChannelEntity"/>
|
||||
<relationship name="myInfoNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="myInfo" inverseEntity="NodeInfoEntity"/>
|
||||
<fetchedProperty name="allMessages" optional="YES">
|
||||
<fetchRequest name="fetchedPropertyFetchRequest" entity="MessageEntity" predicateString="toUser == nil"/>
|
||||
</fetchedProperty>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="myNodeNum"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="NetworkConfigEntity" representedClassName="NetworkConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="dns" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="ethEnabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="gateway" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="ip" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="ntpServer" optional="YES" attributeType="String"/>
|
||||
<attribute name="subnet" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="wifiEnabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="wifiMode" optional="YES" attributeType="Integer 32" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="wifiPsk" optional="YES" attributeType="String" minValueString="0" maxValueString="60"/>
|
||||
<attribute name="wifiSsid" optional="YES" attributeType="String" minValueString="0" maxValueString="30"/>
|
||||
<relationship name="networkConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="networkConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="NodeInfoEntity" representedClassName="NodeInfoEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="bleName" optional="YES" attributeType="String"/>
|
||||
<attribute name="channel" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="hopsAway" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="id" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="lastHeard" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="num" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="peripheralId" optional="YES" attributeType="String"/>
|
||||
<attribute name="rssi" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="viaMqtt" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<relationship name="ambientLightingConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="AmbientLightingConfigEntity" inverseName="ambientLightingConfigNode" inverseEntity="AmbientLightingConfigEntity"/>
|
||||
<relationship name="bluetoothConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="BluetoothConfigEntity" inverseName="bluetoothConfigNode" inverseEntity="BluetoothConfigEntity"/>
|
||||
<relationship name="cannedMessageConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="CannedMessageConfigEntity" inverseName="cannedMessagesConfigNode" inverseEntity="CannedMessageConfigEntity"/>
|
||||
<relationship name="detectionSensorConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="DetectionSensorConfigEntity" inverseName="detectionSensorConfigNode" inverseEntity="DetectionSensorConfigEntity"/>
|
||||
<relationship name="deviceConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="DeviceConfigEntity" inverseName="deviceConfigNode" inverseEntity="DeviceConfigEntity"/>
|
||||
<relationship name="displayConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="DisplayConfigEntity" inverseName="displayConfigNode" inverseEntity="DisplayConfigEntity"/>
|
||||
<relationship name="externalNotificationConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="ExternalNotificationConfigEntity" inverseName="externalNotificationConfigNode" inverseEntity="ExternalNotificationConfigEntity"/>
|
||||
<relationship name="loRaConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="LoRaConfigEntity" inverseName="loRaConfigNode" inverseEntity="LoRaConfigEntity"/>
|
||||
<relationship name="metadata" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="DeviceMetadataEntity" inverseName="metadataNode" inverseEntity="DeviceMetadataEntity"/>
|
||||
<relationship name="mqttConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MQTTConfigEntity" inverseName="mqttConfigNode" inverseEntity="MQTTConfigEntity"/>
|
||||
<relationship name="myInfo" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MyInfoEntity" inverseName="myInfoNode" inverseEntity="MyInfoEntity"/>
|
||||
<relationship name="networkConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NetworkConfigEntity" inverseName="networkConfigNode" inverseEntity="NetworkConfigEntity"/>
|
||||
<relationship name="pax" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="PaxCounterEntity" inverseName="paxNode" inverseEntity="PaxCounterEntity"/>
|
||||
<relationship name="paxCounterConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="PaxCounterConfigEntity" inverseName="paxCounterConfigNode" inverseEntity="PaxCounterConfigEntity"/>
|
||||
<relationship name="positionConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="PositionConfigEntity" inverseName="positionConfigNode" inverseEntity="PositionConfigEntity"/>
|
||||
<relationship name="positions" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="PositionEntity" inverseName="nodePosition" inverseEntity="PositionEntity"/>
|
||||
<relationship name="powerConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="PowerConfigEntity" inverseName="powerConfigNode" inverseEntity="PowerConfigEntity"/>
|
||||
<relationship name="rangeTestConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="RangeTestConfigEntity" inverseName="rangeTestConfigNode" inverseEntity="RangeTestConfigEntity"/>
|
||||
<relationship name="rtttlConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="RTTTLConfigEntity" inverseName="rtttlConfigNode" inverseEntity="RTTTLConfigEntity"/>
|
||||
<relationship name="serialConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="SerialConfigEntity" inverseName="serialConfigNode" inverseEntity="SerialConfigEntity"/>
|
||||
<relationship name="storeForwardConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="StoreForwardConfigEntity" inverseName="storeForwardConfigNode" inverseEntity="StoreForwardConfigEntity"/>
|
||||
<relationship name="telemetries" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="TelemetryEntity" inverseName="nodeTelemetry" inverseEntity="TelemetryEntity"/>
|
||||
<relationship name="telemetryConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="TelemetryConfigEntity" inverseName="telemetryConfigNode" inverseEntity="TelemetryConfigEntity"/>
|
||||
<relationship name="traceRoutes" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="TraceRouteEntity" inverseName="node" inverseEntity="TraceRouteEntity"/>
|
||||
<relationship name="user" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="UserEntity" inverseName="userNode" inverseEntity="UserEntity"/>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="num"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="PaxCounterConfigEntity" representedClassName="PaxCounterConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="paxcounterUpdateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="paxCounterConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="paxCounterConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="PaxCounterEntity" representedClassName="PaxCounterEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="ble" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="time" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="uptime" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="wifi" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="paxNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="pax" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="PositionConfigEntity" representedClassName="PositionConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="broadcastSmartMinimumDistance" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="broadcastSmartMinimumIntervalSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="deviceGpsEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="fixedPosition" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="gpsAttemptTime" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="gpsEnGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="gpsMode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="gpsUpdateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="positionBroadcastSeconds" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="positionFlags" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="rxGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="smartPositionEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="txGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="positionConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="positionConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="PositionEntity" representedClassName="PositionEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="altitude" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="heading" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="latest" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="latitudeI" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="longitudeI" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="precisionBits" optional="YES" attributeType="Integer 32" defaultValueString="32" usesScalarValueType="YES"/>
|
||||
<attribute name="rssi" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="satsInView" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="seqNo" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="speed" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="time" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<relationship name="nodePosition" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="positions" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="PowerConfigEntity" representedClassName="PowerConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="adcMultiplierOverride" optional="YES" attributeType="Float" usesScalarValueType="YES"/>
|
||||
<attribute name="deviceBatteryInaAddress" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="isPowerSaving" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="lsSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="minWakeSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="onBatteryShutdownAfterSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="waitBluetoothSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="powerConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="powerConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="RangeTestConfigEntity" representedClassName="RangeTestConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="save" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="sender" optional="YES" attributeType="Integer 32" usesScalarValueType="YES"/>
|
||||
<relationship name="rangeTestConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="rangeTestConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="RouteEntity" representedClassName="RouteEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="color" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="date" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="id" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="name" optional="YES" attributeType="String"/>
|
||||
<attribute name="notes" optional="YES" attributeType="String"/>
|
||||
<relationship name="locations" optional="YES" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="LocationEntity" inverseName="routeLocation" inverseEntity="LocationEntity"/>
|
||||
</entity>
|
||||
<entity name="RTTTLConfigEntity" representedClassName="RTTTLConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="ringtone" optional="YES" attributeType="String" maxValueString="228" defaultValueString=""/>
|
||||
<relationship name="rtttlConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="rtttlConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="SerialConfigEntity" representedClassName="SerialConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="baudRate" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="echo" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="mode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="overrideConsoleSerialPort" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="rxd" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="timeout" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="txd" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="serialConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="serialConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="StoreForwardConfigEntity" representedClassName="StoreForwardConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="enabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="heartbeat" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<attribute name="historyReturnMax" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="historyReturnWindow" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="isRouter" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="lastHeartbeat" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="lastRequest" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="records" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="storeForwardConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="storeForwardConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="TelemetryConfigEntity" representedClassName="TelemetryConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="deviceUpdateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="environmentDisplayFahrenheit" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="environmentMeasurementEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="environmentScreenEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="environmentUpdateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="telemetryConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="telemetryConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="TelemetryEntity" representedClassName="TelemetryEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="airUtilTx" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="barometricPressure" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="batteryLevel" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="channelUtilization" optional="YES" attributeType="Float" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="current" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="gasResistance" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="metricsType" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="relativeHumidity" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="rssi" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="temperature" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="time" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="voltage" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<relationship name="nodeTelemetry" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="telemetries" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="TraceRouteEntity" representedClassName="TraceRouteEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="altitude" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="hasPositions" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="id" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="latitudeI" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="longitudeI" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="num" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="response" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="route" optional="YES" attributeType="Transformable" customClassName="[UInt32]"/>
|
||||
<attribute name="routeText" optional="YES" attributeType="String"/>
|
||||
<attribute name="time" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<relationship name="hops" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="TraceRouteHopEntity" inverseName="traceRoute" inverseEntity="TraceRouteHopEntity"/>
|
||||
<relationship name="node" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="traceRoutes" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="TraceRouteHopEntity" representedClassName="TraceRouteHopEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="altitude" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="latitudeI" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="longitudeI" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="name" optional="YES" attributeType="String"/>
|
||||
<attribute name="num" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="time" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<relationship name="traceRoute" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="TraceRouteEntity" inverseName="hops" inverseEntity="TraceRouteEntity"/>
|
||||
</entity>
|
||||
<entity name="UserEntity" representedClassName="UserEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="hwModel" attributeType="String"/>
|
||||
<attribute name="isLicensed" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="lastMessage" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="longName" attributeType="String"/>
|
||||
<attribute name="mute" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="num" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="role" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="shortName" attributeType="String"/>
|
||||
<attribute name="userId" attributeType="String"/>
|
||||
<attribute name="vip" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<relationship name="receivedMessages" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="MessageEntity" inverseName="toUser" inverseEntity="MessageEntity"/>
|
||||
<relationship name="sentMessages" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="MessageEntity" inverseName="fromUser" inverseEntity="MessageEntity"/>
|
||||
<relationship name="userNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="user" inverseEntity="NodeInfoEntity"/>
|
||||
<fetchedProperty name="adminMessages" optional="YES">
|
||||
<fetchRequest name="fetchedPropertyFetchRequest" entity="MessageEntity" predicateString="(fromUser.num == $FETCH_SOURCE.num) AND isEmoji == false AND admin = true"/>
|
||||
</fetchedProperty>
|
||||
<fetchedProperty name="allMessages" optional="YES">
|
||||
<fetchRequest name="fetchedPropertyFetchRequest" entity="MessageEntity" predicateString="((toUser.num == $FETCH_SOURCE.num) OR (fromUser.num == $FETCH_SOURCE.num)) AND toUser != nil AND fromUser != nil AND isEmoji == false AND admin = false AND portNum != 10 "/>
|
||||
</fetchedProperty>
|
||||
<fetchedProperty name="detectionSensorMessages" optional="YES">
|
||||
<fetchRequest name="fetchedPropertyFetchRequest" entity="MessageEntity" predicateString="(fromUser.num == $FETCH_SOURCE.num) AND portNum = 10"/>
|
||||
</fetchedProperty>
|
||||
</entity>
|
||||
<entity name="WaypointEntity" representedClassName="WaypointEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="created" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="expire" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="icon" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="id" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="lastUpdated" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="latitudeI" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="locked" attributeType="Integer 64" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="longDescription" optional="YES" attributeType="String" maxValueString="100"/>
|
||||
<attribute name="longitudeI" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="name" attributeType="String" minValueString="1" maxValueString="30"/>
|
||||
</entity>
|
||||
</model>
|
||||
|
|
@ -0,0 +1,457 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="22757" systemVersion="23E5211a" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
||||
<entity name="AmbientLightingConfigEntity" representedClassName="AmbientLightingConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="blue" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="current" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="green" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="ledState" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="red" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="ambientLightingConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="ambientLightingConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="BluetoothConfigEntity" representedClassName="BluetoothConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="fixedPin" optional="YES" attributeType="Integer 32" defaultValueString="123456" usesScalarValueType="YES"/>
|
||||
<attribute name="mode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="bluetoothConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="bluetoothConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="CannedMessageConfigEntity" representedClassName="CannedMessageConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerEventCcw" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerEventCw" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerEventPress" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerPinA" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerPinB" optional="YES" attributeType="Integer 32" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerPinPress" optional="YES" attributeType="Integer 32" usesScalarValueType="YES"/>
|
||||
<attribute name="messages" optional="YES" attributeType="String" minValueString="0" maxValueString="198"/>
|
||||
<attribute name="rotary1Enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="sendBell" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="updown1Enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<relationship name="cannedMessagesConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="cannedMessageConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="ChannelEntity" representedClassName="ChannelEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="downlinkEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="id" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="index" attributeType="Integer 32" minValueString="0" maxValueString="13" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="mute" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="name" optional="YES" attributeType="String"/>
|
||||
<attribute name="positionPrecision" optional="YES" attributeType="Integer 32" defaultValueString="32" usesScalarValueType="YES"/>
|
||||
<attribute name="psk" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="role" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="uplinkEnabled" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<relationship name="myInfoChannel" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MyInfoEntity" inverseName="channels" inverseEntity="MyInfoEntity"/>
|
||||
<fetchedProperty name="allPrivateMessages" optional="YES">
|
||||
<fetchRequest name="fetchedPropertyFetchRequest" entity="MessageEntity" predicateString="channel == $FETCH_SOURCE.index && toUser == nil AND isEmoji == false"/>
|
||||
</fetchedProperty>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="index"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="DetectionSensorConfigEntity" representedClassName="DetectionSensorConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="detectionTriggeredHigh" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="minimumBroadcastSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="monitorPin" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="name" optional="YES" attributeType="String"/>
|
||||
<attribute name="sendBell" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="stateBroadcastSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="usePullup" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<relationship name="detectionSensorConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="detectionSensorConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="DeviceConfigEntity" representedClassName="DeviceConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="buttonGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="buzzerGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="debugLogEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="disableTripleClick" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="doubleTapAsButtonPress" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="isManaged" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="nodeInfoBroadcastSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="rebroadcastMode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="role" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="serialEnabled" optional="YES" attributeType="Boolean" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="deviceConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="deviceConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="DeviceHardwareEntity" representedClassName="DeviceHardwareEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="activelySupported" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="architecture" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="displayName" optional="YES" attributeType="String"/>
|
||||
<attribute name="hwModel" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="hwModelSlug" optional="YES" attributeType="String"/>
|
||||
<attribute name="platformioTarget" optional="YES" attributeType="String"/>
|
||||
</entity>
|
||||
<entity name="DeviceMetadataEntity" representedClassName="DeviceMetadataEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="canShutdown" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="deviceStateVersion" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="firmwareVersion" optional="YES" attributeType="String"/>
|
||||
<attribute name="hasBluetooth" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="hasEthernet" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="hasWifi" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="hwModel" optional="YES" attributeType="String"/>
|
||||
<attribute name="positionFlags" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="role" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="metadataNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="metadata" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="DisplayConfigEntity" representedClassName="DisplayConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="compassNorthTop" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="displayMode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="flipScreen" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="gpsFormat" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="headingBold" optional="YES" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<attribute name="oledType" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="screenCarouselInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="screenOnSeconds" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="units" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="wakeOnTapOrMotion" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<relationship name="displayConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="displayConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="ExternalNotificationConfigEntity" representedClassName="ExternalNotificationConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="active" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="alertBell" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="alertBellBuzzer" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="alertBellVibra" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="alertMessage" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="alertMessageBuzzer" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="alertMessageVibra" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="nagTimeout" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="output" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="outputBuzzer" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="outputMilliseconds" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="outputVibra" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="useI2SAsBuzzer" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="usePWM" optional="YES" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<relationship name="externalNotificationConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="externalNotificationConfig" inverseEntity="NodeInfoEntity"/>
|
||||
<fetchedProperty name="fetchedProperty" optional="YES">
|
||||
<fetchRequest name="fetchedPropertyFetchRequest" entity="ExternalNotificationConfigEntity"/>
|
||||
</fetchedProperty>
|
||||
</entity>
|
||||
<entity name="LocationEntity" representedClassName="LocationEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="altitude" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="heading" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="id" optional="YES" attributeType="Integer 32" usesScalarValueType="YES"/>
|
||||
<attribute name="latitudeI" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="longitudeI" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="speed" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="routeLocation" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="RouteEntity" inverseName="locations" inverseEntity="RouteEntity"/>
|
||||
</entity>
|
||||
<entity name="LoRaConfigEntity" representedClassName="LoRaConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="bandwidth" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="channelNum" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="codingRate" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="frequencyOffset" optional="YES" attributeType="Float" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="hopLimit" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="ignoreMqtt" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="modemPreset" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="overrideDutyCycle" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="overrideFrequency" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="regionCode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="spreadFactor" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="sx126xRxBoostedGain" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="txEnabled" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<attribute name="txPower" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="usePreset" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<relationship name="loRaConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="loRaConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="MessageEntity" representedClassName="MessageEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="ackError" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="ackSNR" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="ackTimestamp" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="admin" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="adminDescription" optional="YES" attributeType="String"/>
|
||||
<attribute name="channel" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="isEmoji" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="messageId" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="messagePayload" optional="YES" attributeType="String" defaultValueString=""/>
|
||||
<attribute name="messagePayloadMarkdown" optional="YES" attributeType="String"/>
|
||||
<attribute name="messageTimestamp" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="portNum" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="read" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="realACK" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="receivedACK" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="receivedTimestamp" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="replyID" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="rssi" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<relationship name="fromUser" optional="YES" maxCount="1" deletionRule="Nullify" ordered="YES" destinationEntity="UserEntity" inverseName="sentMessages" inverseEntity="UserEntity"/>
|
||||
<relationship name="toUser" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="UserEntity" inverseName="receivedMessages" inverseEntity="UserEntity"/>
|
||||
<fetchedProperty name="tapbacks" optional="YES">
|
||||
<fetchRequest name="fetchedPropertyFetchRequest" entity="MessageEntity" predicateString="replyID == $FETCH_SOURCE.messageId AND isEmoji == true"/>
|
||||
</fetchedProperty>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="messageId"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="MQTTConfigEntity" representedClassName="MQTTConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="address" optional="YES" attributeType="String" maxValueString="30"/>
|
||||
<attribute name="enabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="encryptionEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="jsonEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="password" optional="YES" attributeType="String" maxValueString="30"/>
|
||||
<attribute name="proxyToClientEnabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="root" optional="YES" attributeType="String" defaultValueString="msh"/>
|
||||
<attribute name="tlsEnabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="username" optional="YES" attributeType="String" maxValueString="30"/>
|
||||
<relationship name="mqttConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="mqttConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="MyInfoEntity" representedClassName="MyInfoEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="adminIndex" optional="YES" attributeType="Integer 32" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="bleName" optional="YES" attributeType="String"/>
|
||||
<attribute name="minAppVersion" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="myNodeNum" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="peripheralId" optional="YES" attributeType="String"/>
|
||||
<attribute name="rebootCount" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="channels" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="ChannelEntity" inverseName="myInfoChannel" inverseEntity="ChannelEntity"/>
|
||||
<relationship name="myInfoNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="myInfo" inverseEntity="NodeInfoEntity"/>
|
||||
<fetchedProperty name="allMessages" optional="YES">
|
||||
<fetchRequest name="fetchedPropertyFetchRequest" entity="MessageEntity" predicateString="toUser == nil"/>
|
||||
</fetchedProperty>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="myNodeNum"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="NetworkConfigEntity" representedClassName="NetworkConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="dns" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="ethEnabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="gateway" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="ip" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="ntpServer" optional="YES" attributeType="String"/>
|
||||
<attribute name="subnet" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="wifiEnabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="wifiMode" optional="YES" attributeType="Integer 32" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="wifiPsk" optional="YES" attributeType="String" minValueString="0" maxValueString="60"/>
|
||||
<attribute name="wifiSsid" optional="YES" attributeType="String" minValueString="0" maxValueString="30"/>
|
||||
<relationship name="networkConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="networkConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="NodeInfoEntity" representedClassName="NodeInfoEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="bleName" optional="YES" attributeType="String"/>
|
||||
<attribute name="channel" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="hopsAway" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="id" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="lastHeard" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="num" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="peripheralId" optional="YES" attributeType="String"/>
|
||||
<attribute name="rssi" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="viaMqtt" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<relationship name="ambientLightingConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="AmbientLightingConfigEntity" inverseName="ambientLightingConfigNode" inverseEntity="AmbientLightingConfigEntity"/>
|
||||
<relationship name="bluetoothConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="BluetoothConfigEntity" inverseName="bluetoothConfigNode" inverseEntity="BluetoothConfigEntity"/>
|
||||
<relationship name="cannedMessageConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="CannedMessageConfigEntity" inverseName="cannedMessagesConfigNode" inverseEntity="CannedMessageConfigEntity"/>
|
||||
<relationship name="detectionSensorConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="DetectionSensorConfigEntity" inverseName="detectionSensorConfigNode" inverseEntity="DetectionSensorConfigEntity"/>
|
||||
<relationship name="deviceConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="DeviceConfigEntity" inverseName="deviceConfigNode" inverseEntity="DeviceConfigEntity"/>
|
||||
<relationship name="displayConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="DisplayConfigEntity" inverseName="displayConfigNode" inverseEntity="DisplayConfigEntity"/>
|
||||
<relationship name="externalNotificationConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="ExternalNotificationConfigEntity" inverseName="externalNotificationConfigNode" inverseEntity="ExternalNotificationConfigEntity"/>
|
||||
<relationship name="loRaConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="LoRaConfigEntity" inverseName="loRaConfigNode" inverseEntity="LoRaConfigEntity"/>
|
||||
<relationship name="metadata" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="DeviceMetadataEntity" inverseName="metadataNode" inverseEntity="DeviceMetadataEntity"/>
|
||||
<relationship name="mqttConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MQTTConfigEntity" inverseName="mqttConfigNode" inverseEntity="MQTTConfigEntity"/>
|
||||
<relationship name="myInfo" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MyInfoEntity" inverseName="myInfoNode" inverseEntity="MyInfoEntity"/>
|
||||
<relationship name="networkConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NetworkConfigEntity" inverseName="networkConfigNode" inverseEntity="NetworkConfigEntity"/>
|
||||
<relationship name="pax" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="PaxCounterEntity" inverseName="paxNode" inverseEntity="PaxCounterEntity"/>
|
||||
<relationship name="paxCounterConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="PaxCounterConfigEntity" inverseName="paxCounterConfigNode" inverseEntity="PaxCounterConfigEntity"/>
|
||||
<relationship name="positionConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="PositionConfigEntity" inverseName="positionConfigNode" inverseEntity="PositionConfigEntity"/>
|
||||
<relationship name="positions" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="PositionEntity" inverseName="nodePosition" inverseEntity="PositionEntity"/>
|
||||
<relationship name="powerConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="PowerConfigEntity" inverseName="powerConfigNode" inverseEntity="PowerConfigEntity"/>
|
||||
<relationship name="rangeTestConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="RangeTestConfigEntity" inverseName="rangeTestConfigNode" inverseEntity="RangeTestConfigEntity"/>
|
||||
<relationship name="rtttlConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="RTTTLConfigEntity" inverseName="rtttlConfigNode" inverseEntity="RTTTLConfigEntity"/>
|
||||
<relationship name="serialConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="SerialConfigEntity" inverseName="serialConfigNode" inverseEntity="SerialConfigEntity"/>
|
||||
<relationship name="storeForwardConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="StoreForwardConfigEntity" inverseName="storeForwardConfigNode" inverseEntity="StoreForwardConfigEntity"/>
|
||||
<relationship name="telemetries" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="TelemetryEntity" inverseName="nodeTelemetry" inverseEntity="TelemetryEntity"/>
|
||||
<relationship name="telemetryConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="TelemetryConfigEntity" inverseName="telemetryConfigNode" inverseEntity="TelemetryConfigEntity"/>
|
||||
<relationship name="traceRoutes" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="TraceRouteEntity" inverseName="node" inverseEntity="TraceRouteEntity"/>
|
||||
<relationship name="user" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="UserEntity" inverseName="userNode" inverseEntity="UserEntity"/>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="num"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="PaxCounterConfigEntity" representedClassName="PaxCounterConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="paxcounterUpdateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="paxCounterConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="paxCounterConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="PaxCounterEntity" representedClassName="PaxCounterEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="ble" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="time" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="uptime" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="wifi" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="paxNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="pax" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="PositionConfigEntity" representedClassName="PositionConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="broadcastSmartMinimumDistance" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="broadcastSmartMinimumIntervalSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="deviceGpsEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="fixedPosition" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="gpsAttemptTime" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="gpsEnGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="gpsMode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="gpsUpdateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="positionBroadcastSeconds" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="positionFlags" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="rxGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="smartPositionEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="txGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="positionConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="positionConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="PositionEntity" representedClassName="PositionEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="altitude" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="heading" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="latest" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="latitudeI" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="longitudeI" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="precisionBits" optional="YES" attributeType="Integer 32" defaultValueString="32" usesScalarValueType="YES"/>
|
||||
<attribute name="rssi" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="satsInView" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="seqNo" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="speed" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="time" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<relationship name="nodePosition" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="positions" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="PowerConfigEntity" representedClassName="PowerConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="adcMultiplierOverride" optional="YES" attributeType="Float" usesScalarValueType="YES"/>
|
||||
<attribute name="deviceBatteryInaAddress" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="isPowerSaving" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="lsSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="minWakeSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="onBatteryShutdownAfterSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="waitBluetoothSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="powerConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="powerConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="RangeTestConfigEntity" representedClassName="RangeTestConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="save" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="sender" optional="YES" attributeType="Integer 32" usesScalarValueType="YES"/>
|
||||
<relationship name="rangeTestConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="rangeTestConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="RouteEntity" representedClassName="RouteEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="color" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="date" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="id" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="name" optional="YES" attributeType="String"/>
|
||||
<attribute name="notes" optional="YES" attributeType="String"/>
|
||||
<relationship name="locations" optional="YES" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="LocationEntity" inverseName="routeLocation" inverseEntity="LocationEntity"/>
|
||||
</entity>
|
||||
<entity name="RTTTLConfigEntity" representedClassName="RTTTLConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="ringtone" optional="YES" attributeType="String" maxValueString="228" defaultValueString=""/>
|
||||
<relationship name="rtttlConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="rtttlConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="SerialConfigEntity" representedClassName="SerialConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="baudRate" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="echo" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="mode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="overrideConsoleSerialPort" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="rxd" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="timeout" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="txd" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="serialConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="serialConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="StoreForwardConfigEntity" representedClassName="StoreForwardConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="enabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="heartbeat" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<attribute name="historyReturnMax" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="historyReturnWindow" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="isRouter" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="lastHeartbeat" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="lastRequest" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="records" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="storeForwardConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="storeForwardConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="TelemetryConfigEntity" representedClassName="TelemetryConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="deviceUpdateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="environmentDisplayFahrenheit" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="environmentMeasurementEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="environmentScreenEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="environmentUpdateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<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="role" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="shortName" attributeType="String"/>
|
||||
<attribute name="userId" attributeType="String"/>
|
||||
<attribute name="vip" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<relationship name="receivedMessages" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="MessageEntity" inverseName="toUser" inverseEntity="MessageEntity"/>
|
||||
<relationship name="sentMessages" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="MessageEntity" inverseName="fromUser" inverseEntity="MessageEntity"/>
|
||||
<relationship name="userNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="user" inverseEntity="NodeInfoEntity"/>
|
||||
<fetchedProperty name="adminMessages" optional="YES">
|
||||
<fetchRequest name="fetchedPropertyFetchRequest" entity="MessageEntity" predicateString="(fromUser.num == $FETCH_SOURCE.num) AND isEmoji == false AND admin = true"/>
|
||||
</fetchedProperty>
|
||||
<fetchedProperty name="allMessages" optional="YES">
|
||||
<fetchRequest name="fetchedPropertyFetchRequest" entity="MessageEntity" predicateString="((toUser.num == $FETCH_SOURCE.num) OR (fromUser.num == $FETCH_SOURCE.num)) AND toUser != nil AND fromUser != nil AND isEmoji == false AND admin = false AND portNum != 10 "/>
|
||||
</fetchedProperty>
|
||||
<fetchedProperty name="detectionSensorMessages" optional="YES">
|
||||
<fetchRequest name="fetchedPropertyFetchRequest" entity="MessageEntity" predicateString="(fromUser.num == $FETCH_SOURCE.num) AND portNum = 10"/>
|
||||
</fetchedProperty>
|
||||
</entity>
|
||||
<entity name="WaypointEntity" representedClassName="WaypointEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="created" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="expire" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="icon" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="id" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="lastUpdated" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="latitudeI" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="locked" attributeType="Integer 64" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="longDescription" optional="YES" attributeType="String" maxValueString="100"/>
|
||||
<attribute name="longitudeI" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="name" attributeType="String" minValueString="1" maxValueString="30"/>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="id"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
</model>
|
||||
|
|
@ -0,0 +1,461 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="22757" systemVersion="23E5211a" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
||||
<entity name="AmbientLightingConfigEntity" representedClassName="AmbientLightingConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="blue" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="current" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="green" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="ledState" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="red" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="ambientLightingConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="ambientLightingConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="BluetoothConfigEntity" representedClassName="BluetoothConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="fixedPin" optional="YES" attributeType="Integer 32" defaultValueString="123456" usesScalarValueType="YES"/>
|
||||
<attribute name="mode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="bluetoothConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="bluetoothConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="CannedMessageConfigEntity" representedClassName="CannedMessageConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerEventCcw" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerEventCw" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerEventPress" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerPinA" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerPinB" optional="YES" attributeType="Integer 32" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerPinPress" optional="YES" attributeType="Integer 32" usesScalarValueType="YES"/>
|
||||
<attribute name="messages" optional="YES" attributeType="String" minValueString="0" maxValueString="198"/>
|
||||
<attribute name="rotary1Enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="sendBell" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="updown1Enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<relationship name="cannedMessagesConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="cannedMessageConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="ChannelEntity" representedClassName="ChannelEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="downlinkEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="id" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="index" attributeType="Integer 32" minValueString="0" maxValueString="13" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="mute" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="name" optional="YES" attributeType="String"/>
|
||||
<attribute name="positionPrecision" optional="YES" attributeType="Integer 32" defaultValueString="32" usesScalarValueType="YES"/>
|
||||
<attribute name="psk" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="role" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="uplinkEnabled" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<relationship name="myInfoChannel" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MyInfoEntity" inverseName="channels" inverseEntity="MyInfoEntity"/>
|
||||
<fetchedProperty name="allPrivateMessages" optional="YES">
|
||||
<fetchRequest name="fetchedPropertyFetchRequest" entity="MessageEntity" predicateString="channel == $FETCH_SOURCE.index && toUser == nil AND isEmoji == false"/>
|
||||
</fetchedProperty>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="index"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="DetectionSensorConfigEntity" representedClassName="DetectionSensorConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="detectionTriggeredHigh" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="minimumBroadcastSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="monitorPin" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="name" optional="YES" attributeType="String"/>
|
||||
<attribute name="sendBell" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="stateBroadcastSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="usePullup" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<relationship name="detectionSensorConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="detectionSensorConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="DeviceConfigEntity" representedClassName="DeviceConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="buttonGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="buzzerGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="debugLogEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="disableTripleClick" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="doubleTapAsButtonPress" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="isManaged" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="nodeInfoBroadcastSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="rebroadcastMode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="role" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="serialEnabled" optional="YES" attributeType="Boolean" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="deviceConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="deviceConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="DeviceHardwareEntity" representedClassName="DeviceHardwareEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="activelySupported" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="architecture" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="displayName" optional="YES" attributeType="String"/>
|
||||
<attribute name="hwModel" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="hwModelSlug" optional="YES" attributeType="String"/>
|
||||
<attribute name="platformioTarget" optional="YES" attributeType="String"/>
|
||||
</entity>
|
||||
<entity name="DeviceMetadataEntity" representedClassName="DeviceMetadataEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="canShutdown" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="deviceStateVersion" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="firmwareVersion" optional="YES" attributeType="String"/>
|
||||
<attribute name="hasBluetooth" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="hasEthernet" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="hasWifi" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="hwModel" optional="YES" attributeType="String"/>
|
||||
<attribute name="positionFlags" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="role" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="metadataNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="metadata" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="DisplayConfigEntity" representedClassName="DisplayConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="compassNorthTop" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="displayMode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="flipScreen" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="gpsFormat" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="headingBold" optional="YES" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<attribute name="oledType" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="screenCarouselInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="screenOnSeconds" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="units" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="wakeOnTapOrMotion" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<relationship name="displayConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="displayConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="ExternalNotificationConfigEntity" representedClassName="ExternalNotificationConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="active" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="alertBell" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="alertBellBuzzer" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="alertBellVibra" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="alertMessage" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="alertMessageBuzzer" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="alertMessageVibra" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="nagTimeout" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="output" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="outputBuzzer" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="outputMilliseconds" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="outputVibra" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="useI2SAsBuzzer" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="usePWM" optional="YES" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<relationship name="externalNotificationConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="externalNotificationConfig" inverseEntity="NodeInfoEntity"/>
|
||||
<fetchedProperty name="fetchedProperty" optional="YES">
|
||||
<fetchRequest name="fetchedPropertyFetchRequest" entity="ExternalNotificationConfigEntity"/>
|
||||
</fetchedProperty>
|
||||
</entity>
|
||||
<entity name="LocationEntity" representedClassName="LocationEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="altitude" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="heading" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="id" optional="YES" attributeType="Integer 32" usesScalarValueType="YES"/>
|
||||
<attribute name="latitudeI" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="longitudeI" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="speed" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="routeLocation" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="RouteEntity" inverseName="locations" inverseEntity="RouteEntity"/>
|
||||
</entity>
|
||||
<entity name="LoRaConfigEntity" representedClassName="LoRaConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="bandwidth" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="channelNum" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="codingRate" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="frequencyOffset" optional="YES" attributeType="Float" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="hopLimit" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="ignoreMqtt" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="modemPreset" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="overrideDutyCycle" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="overrideFrequency" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="regionCode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="spreadFactor" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="sx126xRxBoostedGain" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="txEnabled" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<attribute name="txPower" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="usePreset" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<relationship name="loRaConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="loRaConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="MessageEntity" representedClassName="MessageEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="ackError" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="ackSNR" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="ackTimestamp" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="admin" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="adminDescription" optional="YES" attributeType="String"/>
|
||||
<attribute name="channel" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="isEmoji" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="messageId" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="messagePayload" optional="YES" attributeType="String" defaultValueString=""/>
|
||||
<attribute name="messagePayloadMarkdown" optional="YES" attributeType="String"/>
|
||||
<attribute name="messageTimestamp" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="portNum" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="read" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="realACK" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="receivedACK" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="receivedTimestamp" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="replyID" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="rssi" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<relationship name="fromUser" optional="YES" maxCount="1" deletionRule="Nullify" ordered="YES" destinationEntity="UserEntity" inverseName="sentMessages" inverseEntity="UserEntity"/>
|
||||
<relationship name="toUser" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="UserEntity" inverseName="receivedMessages" inverseEntity="UserEntity"/>
|
||||
<fetchedProperty name="tapbacks" optional="YES">
|
||||
<fetchRequest name="fetchedPropertyFetchRequest" entity="MessageEntity" predicateString="replyID == $FETCH_SOURCE.messageId AND isEmoji == true"/>
|
||||
</fetchedProperty>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="messageId"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="MQTTConfigEntity" representedClassName="MQTTConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="address" optional="YES" attributeType="String" maxValueString="30"/>
|
||||
<attribute name="enabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="encryptionEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="jsonEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="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="role" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="shortName" attributeType="String"/>
|
||||
<attribute name="userId" attributeType="String"/>
|
||||
<attribute name="vip" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<relationship name="receivedMessages" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="MessageEntity" inverseName="toUser" inverseEntity="MessageEntity"/>
|
||||
<relationship name="sentMessages" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="MessageEntity" inverseName="fromUser" inverseEntity="MessageEntity"/>
|
||||
<relationship name="userNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="user" inverseEntity="NodeInfoEntity"/>
|
||||
<fetchedProperty name="adminMessages" optional="YES">
|
||||
<fetchRequest name="fetchedPropertyFetchRequest" entity="MessageEntity" predicateString="(fromUser.num == $FETCH_SOURCE.num) AND isEmoji == false AND admin = true"/>
|
||||
</fetchedProperty>
|
||||
<fetchedProperty name="allMessages" optional="YES">
|
||||
<fetchRequest name="fetchedPropertyFetchRequest" entity="MessageEntity" predicateString="((toUser.num == $FETCH_SOURCE.num) OR (fromUser.num == $FETCH_SOURCE.num)) AND toUser != nil AND fromUser != nil AND isEmoji == false AND admin = false AND portNum != 10 "/>
|
||||
</fetchedProperty>
|
||||
<fetchedProperty name="detectionSensorMessages" optional="YES">
|
||||
<fetchRequest name="fetchedPropertyFetchRequest" entity="MessageEntity" predicateString="(fromUser.num == $FETCH_SOURCE.num) AND portNum = 10"/>
|
||||
</fetchedProperty>
|
||||
</entity>
|
||||
<entity name="WaypointEntity" representedClassName="WaypointEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="created" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="expire" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="icon" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="id" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="lastUpdated" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="latitudeI" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="locked" attributeType="Integer 64" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="longDescription" optional="YES" attributeType="String" maxValueString="100"/>
|
||||
<attribute name="longitudeI" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="name" attributeType="String" minValueString="1" maxValueString="30"/>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="id"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
</model>
|
||||
|
|
@ -144,14 +144,32 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext)
|
|||
newNode.snr = packet.rxSnr
|
||||
newNode.rssi = packet.rxRssi
|
||||
newNode.viaMqtt = packet.viaMqtt
|
||||
newNode.channel = Int32(packet.channel)
|
||||
|
||||
if packet.to == 4294967295 || packet.to == UserDefaults.preferredPeripheralNum {
|
||||
newNode.channel = Int32(packet.channel)
|
||||
}
|
||||
if let nodeInfoMessage = try? NodeInfo(serializedData: packet.decoded.payload) {
|
||||
newNode.channel = Int32(nodeInfoMessage.channel)
|
||||
print(packet.channel)
|
||||
print("Channel From Message\(nodeInfoMessage.channel)")
|
||||
newNode.hopsAway = Int32(nodeInfoMessage.hopsAway)
|
||||
newNode.favorite = nodeInfoMessage.isFavorite
|
||||
} else if packet.hopStart != 0 && packet.hopLimit <= packet.hopStart {
|
||||
newNode.hopsAway = Int32(packet.hopStart - packet.hopLimit)
|
||||
}
|
||||
if let newUserMessage = try? User(serializedData: packet.decoded.payload) {
|
||||
let newUser = UserEntity(context: context)
|
||||
|
||||
if newUserMessage.id.isEmpty {
|
||||
let newUser = UserEntity(context: context)
|
||||
newUser.num = Int64(packet.from)
|
||||
let userId = String(format:"%2X", packet.from)
|
||||
newUser.userId = "!\(userId)"
|
||||
let last4 = String(userId.suffix(4))
|
||||
newUser.longName = "Meshtastic \(last4)"
|
||||
newUser.shortName = last4
|
||||
newUser.hwModel = "UNSET"
|
||||
newNode.user = newUser
|
||||
|
||||
} else {
|
||||
|
||||
let newUser = UserEntity(context: context)
|
||||
newUser.userId = newUserMessage.id
|
||||
newUser.num = Int64(packet.from)
|
||||
newUser.longName = newUserMessage.longName
|
||||
|
|
@ -159,6 +177,17 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext)
|
|||
newUser.role = Int32(newUserMessage.role.rawValue)
|
||||
newUser.hwModel = String(describing: newUserMessage.hwModel).uppercased()
|
||||
newNode.user = newUser
|
||||
}
|
||||
} else {
|
||||
let newUser = UserEntity(context: context)
|
||||
newUser.num = Int64(packet.from)
|
||||
let userId = String(format:"%2X", packet.from)
|
||||
newUser.userId = "!\(userId)"
|
||||
let last4 = String(userId.suffix(4))
|
||||
newUser.longName = "Meshtastic \(last4)"
|
||||
newUser.shortName = last4
|
||||
newUser.hwModel = "UNSET"
|
||||
newNode.user = newUser
|
||||
}
|
||||
|
||||
if newNode.user == nil {
|
||||
|
|
@ -177,7 +206,6 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext)
|
|||
print("💥 Error Inserting New Core Data MyInfoEntity: \(nsError)")
|
||||
}
|
||||
newNode.myInfo = myInfoEntity
|
||||
//newNode.objectWillChange.send()
|
||||
|
||||
} else {
|
||||
// Update an existing node
|
||||
|
|
@ -189,10 +217,14 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext)
|
|||
fetchedNode[0].snr = packet.rxSnr
|
||||
fetchedNode[0].rssi = packet.rxRssi
|
||||
fetchedNode[0].viaMqtt = packet.viaMqtt
|
||||
fetchedNode[0].channel = Int32(packet.channel)
|
||||
if packet.to == 4294967295 || packet.to == UserDefaults.preferredPeripheralNum {
|
||||
fetchedNode[0].channel = Int32(packet.channel)
|
||||
}
|
||||
|
||||
if let nodeInfoMessage = try? NodeInfo(serializedData: packet.decoded.payload) {
|
||||
fetchedNode[0].channel = Int32(nodeInfoMessage.channel)
|
||||
|
||||
fetchedNode[0].hopsAway = Int32(nodeInfoMessage.hopsAway)
|
||||
fetchedNode[0].favorite = nodeInfoMessage.isFavorite
|
||||
if nodeInfoMessage.hasDeviceMetrics {
|
||||
let telemetry = TelemetryEntity(context: context)
|
||||
telemetry.batteryLevel = Int32(nodeInfoMessage.deviceMetrics.batteryLevel)
|
||||
|
|
@ -204,6 +236,7 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext)
|
|||
fetchedNode[0].telemetries? = NSOrderedSet(array: newTelemetries)
|
||||
}
|
||||
if nodeInfoMessage.hasUser {
|
||||
fetchedNode[0].user!.vip = nodeInfoMessage.isFavorite
|
||||
/// Seeing Some crashes here ?
|
||||
fetchedNode[0].user!.userId = nodeInfoMessage.user.id
|
||||
fetchedNode[0].user!.num = Int64(nodeInfoMessage.num)
|
||||
|
|
@ -211,19 +244,20 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext)
|
|||
fetchedNode[0].user!.shortName = nodeInfoMessage.user.shortName
|
||||
fetchedNode[0].user!.role = Int32(nodeInfoMessage.user.role.rawValue)
|
||||
fetchedNode[0].user!.hwModel = String(describing: nodeInfoMessage.user.hwModel).uppercased()
|
||||
} else {
|
||||
if (fetchedNode[0].user == nil) {
|
||||
let newUser = UserEntity(context: context)
|
||||
newUser.num = Int64(nodeInfoMessage.num)
|
||||
let userId = String(format:"%2X", nodeInfoMessage.num)
|
||||
newUser.userId = "!\(userId)"
|
||||
let last4 = String(userId.suffix(4))
|
||||
newUser.longName = "Meshtastic \(last4)"
|
||||
newUser.shortName = last4
|
||||
newUser.hwModel = "UNSET"
|
||||
fetchedNode[0].user! = newUser
|
||||
}
|
||||
}
|
||||
} else if packet.hopStart != 0 && packet.hopLimit <= packet.hopStart {
|
||||
fetchedNode[0].hopsAway = Int32(packet.hopStart - packet.hopLimit)
|
||||
}
|
||||
if (fetchedNode[0].user == nil) {
|
||||
let newUser = UserEntity(context: context)
|
||||
newUser.num = Int64(packet.from)
|
||||
let userId = String(format:"%2X", packet.from)
|
||||
newUser.userId = "!\(userId)"
|
||||
let last4 = String(userId.suffix(4))
|
||||
newUser.longName = "Meshtastic \(last4)"
|
||||
newUser.shortName = last4
|
||||
newUser.hwModel = "UNSET"
|
||||
fetchedNode[0].user! = newUser
|
||||
}
|
||||
do {
|
||||
try context.save()
|
||||
|
|
@ -291,9 +325,9 @@ func upsertPositionPacket (packet: MeshPacket, context: NSManagedObjectContext)
|
|||
return
|
||||
}
|
||||
/// Don't save nearly the same position over and over. If the next position is less than 10 meters from the new position, delete the previous position and save the new one.
|
||||
if mutablePositions.count > 0 && position.precisionBits == 32 {
|
||||
if mutablePositions.count > 0 && (position.precisionBits == 32 || position.precisionBits == 0) {
|
||||
let mostRecent = mutablePositions.lastObject as! PositionEntity
|
||||
if mostRecent.coordinate.distance(from: position.coordinate) < 15 {
|
||||
if mostRecent.coordinate.distance(from: position.coordinate) < 15.0 {
|
||||
mutablePositions.remove(mostRecent)
|
||||
}
|
||||
} else if mutablePositions.count > 0 && 11...16 ~= position.precisionBits {
|
||||
|
|
@ -1085,6 +1119,9 @@ func upsertMqttModuleConfigPacket(config: Meshtastic.ModuleConfig.MQTTConfig, no
|
|||
newMQTTConfig.encryptionEnabled = config.encryptionEnabled
|
||||
newMQTTConfig.jsonEnabled = config.jsonEnabled
|
||||
newMQTTConfig.tlsEnabled = config.tlsEnabled
|
||||
newMQTTConfig.mapReportingEnabled = config.mapReportingEnabled
|
||||
newMQTTConfig.mapPositionPrecision = Int32(config.mapReportSettings.positionPrecision)
|
||||
newMQTTConfig.mapPublishIntervalSecs = Int32(config.mapReportSettings.publishIntervalSecs)
|
||||
fetchedNode[0].mqttConfig = newMQTTConfig
|
||||
} else {
|
||||
fetchedNode[0].mqttConfig?.enabled = config.enabled
|
||||
|
|
@ -1096,6 +1133,9 @@ func upsertMqttModuleConfigPacket(config: Meshtastic.ModuleConfig.MQTTConfig, no
|
|||
fetchedNode[0].mqttConfig?.encryptionEnabled = config.encryptionEnabled
|
||||
fetchedNode[0].mqttConfig?.jsonEnabled = config.jsonEnabled
|
||||
fetchedNode[0].mqttConfig?.tlsEnabled = config.tlsEnabled
|
||||
fetchedNode[0].mqttConfig?.mapReportingEnabled = config.mapReportingEnabled
|
||||
fetchedNode[0].mqttConfig?.mapPositionPrecision = Int32(config.mapReportSettings.positionPrecision)
|
||||
fetchedNode[0].mqttConfig?.mapPublishIntervalSecs = Int32(config.mapReportSettings.publishIntervalSecs)
|
||||
}
|
||||
do {
|
||||
try context.save()
|
||||
|
|
@ -1294,6 +1334,9 @@ func upsertTelemetryModuleConfigPacket(config: Meshtastic.ModuleConfig.Telemetry
|
|||
newTelemetryConfig.environmentMeasurementEnabled = config.environmentMeasurementEnabled
|
||||
newTelemetryConfig.environmentScreenEnabled = config.environmentScreenEnabled
|
||||
newTelemetryConfig.environmentDisplayFahrenheit = config.environmentDisplayFahrenheit
|
||||
newTelemetryConfig.powerMeasurementEnabled = config.powerMeasurementEnabled
|
||||
newTelemetryConfig.powerUpdateInterval = Int32(config.powerUpdateInterval)
|
||||
newTelemetryConfig.powerScreenEnabled = config.powerScreenEnabled
|
||||
fetchedNode[0].telemetryConfig = newTelemetryConfig
|
||||
|
||||
} else {
|
||||
|
|
@ -1302,6 +1345,9 @@ func upsertTelemetryModuleConfigPacket(config: Meshtastic.ModuleConfig.Telemetry
|
|||
fetchedNode[0].telemetryConfig?.environmentMeasurementEnabled = config.environmentMeasurementEnabled
|
||||
fetchedNode[0].telemetryConfig?.environmentScreenEnabled = config.environmentScreenEnabled
|
||||
fetchedNode[0].telemetryConfig?.environmentDisplayFahrenheit = config.environmentDisplayFahrenheit
|
||||
fetchedNode[0].telemetryConfig?.powerMeasurementEnabled = config.powerMeasurementEnabled
|
||||
fetchedNode[0].telemetryConfig?.powerUpdateInterval = Int32(config.powerUpdateInterval)
|
||||
fetchedNode[0].telemetryConfig?.powerScreenEnabled = config.powerScreenEnabled
|
||||
}
|
||||
|
||||
do {
|
||||
|
|
|
|||
|
|
@ -319,6 +319,46 @@ struct AdminMessage {
|
|||
set {payloadVariant = .removeByNodenum(newValue)}
|
||||
}
|
||||
|
||||
///
|
||||
/// Set specified node-num to be favorited on the NodeDB on the device
|
||||
var setFavoriteNode: UInt32 {
|
||||
get {
|
||||
if case .setFavoriteNode(let v)? = payloadVariant {return v}
|
||||
return 0
|
||||
}
|
||||
set {payloadVariant = .setFavoriteNode(newValue)}
|
||||
}
|
||||
|
||||
///
|
||||
/// Set specified node-num to be un-favorited on the NodeDB on the device
|
||||
var removeFavoriteNode: UInt32 {
|
||||
get {
|
||||
if case .removeFavoriteNode(let v)? = payloadVariant {return v}
|
||||
return 0
|
||||
}
|
||||
set {payloadVariant = .removeFavoriteNode(newValue)}
|
||||
}
|
||||
|
||||
///
|
||||
/// Set fixed position data on the node and then set the position.fixed_position = true
|
||||
var setFixedPosition: Position {
|
||||
get {
|
||||
if case .setFixedPosition(let v)? = payloadVariant {return v}
|
||||
return Position()
|
||||
}
|
||||
set {payloadVariant = .setFixedPosition(newValue)}
|
||||
}
|
||||
|
||||
///
|
||||
/// Clear fixed position coordinates and then set position.fixed_position = false
|
||||
var removeFixedPosition: Bool {
|
||||
get {
|
||||
if case .removeFixedPosition(let v)? = payloadVariant {return v}
|
||||
return false
|
||||
}
|
||||
set {payloadVariant = .removeFixedPosition(newValue)}
|
||||
}
|
||||
|
||||
///
|
||||
/// Begins an edit transaction for config, module config, owner, and channel settings changes
|
||||
/// This will delay the standard *implicit* save to the file system and subsequent reboot behavior until committed (commit_edit_settings)
|
||||
|
|
@ -498,6 +538,18 @@ struct AdminMessage {
|
|||
/// Remove the node by the specified node-num from the NodeDB on the device
|
||||
case removeByNodenum(UInt32)
|
||||
///
|
||||
/// Set specified node-num to be favorited on the NodeDB on the device
|
||||
case setFavoriteNode(UInt32)
|
||||
///
|
||||
/// Set specified node-num to be un-favorited on the NodeDB on the device
|
||||
case removeFavoriteNode(UInt32)
|
||||
///
|
||||
/// Set fixed position data on the node and then set the position.fixed_position = true
|
||||
case setFixedPosition(Position)
|
||||
///
|
||||
/// Clear fixed position coordinates and then set position.fixed_position = false
|
||||
case removeFixedPosition(Bool)
|
||||
///
|
||||
/// Begins an edit transaction for config, module config, owner, and channel settings changes
|
||||
/// This will delay the standard *implicit* save to the file system and subsequent reboot behavior until committed (commit_edit_settings)
|
||||
case beginEditSettings(Bool)
|
||||
|
|
@ -643,6 +695,22 @@ struct AdminMessage {
|
|||
guard case .removeByNodenum(let l) = lhs, case .removeByNodenum(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.setFavoriteNode, .setFavoriteNode): return {
|
||||
guard case .setFavoriteNode(let l) = lhs, case .setFavoriteNode(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.removeFavoriteNode, .removeFavoriteNode): return {
|
||||
guard case .removeFavoriteNode(let l) = lhs, case .removeFavoriteNode(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.setFixedPosition, .setFixedPosition): return {
|
||||
guard case .setFixedPosition(let l) = lhs, case .setFixedPosition(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.removeFixedPosition, .removeFixedPosition): return {
|
||||
guard case .removeFixedPosition(let l) = lhs, case .removeFixedPosition(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.beginEditSettings, .beginEditSettings): return {
|
||||
guard case .beginEditSettings(let l) = lhs, case .beginEditSettings(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
|
|
@ -978,6 +1046,10 @@ extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat
|
|||
36: .standard(proto: "set_canned_message_module_messages"),
|
||||
37: .standard(proto: "set_ringtone_message"),
|
||||
38: .standard(proto: "remove_by_nodenum"),
|
||||
39: .standard(proto: "set_favorite_node"),
|
||||
40: .standard(proto: "remove_favorite_node"),
|
||||
41: .standard(proto: "set_fixed_position"),
|
||||
42: .standard(proto: "remove_fixed_position"),
|
||||
64: .standard(proto: "begin_edit_settings"),
|
||||
65: .standard(proto: "commit_edit_settings"),
|
||||
95: .standard(proto: "reboot_ota_seconds"),
|
||||
|
|
@ -1278,6 +1350,43 @@ extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat
|
|||
self.payloadVariant = .removeByNodenum(v)
|
||||
}
|
||||
}()
|
||||
case 39: try {
|
||||
var v: UInt32?
|
||||
try decoder.decodeSingularUInt32Field(value: &v)
|
||||
if let v = v {
|
||||
if self.payloadVariant != nil {try decoder.handleConflictingOneOf()}
|
||||
self.payloadVariant = .setFavoriteNode(v)
|
||||
}
|
||||
}()
|
||||
case 40: try {
|
||||
var v: UInt32?
|
||||
try decoder.decodeSingularUInt32Field(value: &v)
|
||||
if let v = v {
|
||||
if self.payloadVariant != nil {try decoder.handleConflictingOneOf()}
|
||||
self.payloadVariant = .removeFavoriteNode(v)
|
||||
}
|
||||
}()
|
||||
case 41: try {
|
||||
var v: Position?
|
||||
var hadOneofValue = false
|
||||
if let current = self.payloadVariant {
|
||||
hadOneofValue = true
|
||||
if case .setFixedPosition(let m) = current {v = m}
|
||||
}
|
||||
try decoder.decodeSingularMessageField(value: &v)
|
||||
if let v = v {
|
||||
if hadOneofValue {try decoder.handleConflictingOneOf()}
|
||||
self.payloadVariant = .setFixedPosition(v)
|
||||
}
|
||||
}()
|
||||
case 42: try {
|
||||
var v: Bool?
|
||||
try decoder.decodeSingularBoolField(value: &v)
|
||||
if let v = v {
|
||||
if self.payloadVariant != nil {try decoder.handleConflictingOneOf()}
|
||||
self.payloadVariant = .removeFixedPosition(v)
|
||||
}
|
||||
}()
|
||||
case 64: try {
|
||||
var v: Bool?
|
||||
try decoder.decodeSingularBoolField(value: &v)
|
||||
|
|
@ -1465,6 +1574,22 @@ extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat
|
|||
guard case .removeByNodenum(let v)? = self.payloadVariant else { preconditionFailure() }
|
||||
try visitor.visitSingularUInt32Field(value: v, fieldNumber: 38)
|
||||
}()
|
||||
case .setFavoriteNode?: try {
|
||||
guard case .setFavoriteNode(let v)? = self.payloadVariant else { preconditionFailure() }
|
||||
try visitor.visitSingularUInt32Field(value: v, fieldNumber: 39)
|
||||
}()
|
||||
case .removeFavoriteNode?: try {
|
||||
guard case .removeFavoriteNode(let v)? = self.payloadVariant else { preconditionFailure() }
|
||||
try visitor.visitSingularUInt32Field(value: v, fieldNumber: 40)
|
||||
}()
|
||||
case .setFixedPosition?: try {
|
||||
guard case .setFixedPosition(let v)? = self.payloadVariant else { preconditionFailure() }
|
||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 41)
|
||||
}()
|
||||
case .removeFixedPosition?: try {
|
||||
guard case .removeFixedPosition(let v)? = self.payloadVariant else { preconditionFailure() }
|
||||
try visitor.visitSingularBoolField(value: v, fieldNumber: 42)
|
||||
}()
|
||||
case .beginEditSettings?: try {
|
||||
guard case .beginEditSettings(let v)? = self.payloadVariant else { preconditionFailure() }
|
||||
try visitor.visitSingularBoolField(value: v, fieldNumber: 64)
|
||||
|
|
|
|||
|
|
@ -140,6 +140,7 @@ struct DeviceState {
|
|||
///
|
||||
/// Used only during development.
|
||||
/// Indicates developer is testing and changes should never be saved to flash.
|
||||
/// Deprecated in 2.3.1
|
||||
var noSave: Bool {
|
||||
get {return _storage._noSave}
|
||||
set {_uniqueStorage()._noSave = newValue}
|
||||
|
|
@ -254,6 +255,28 @@ struct NodeInfoLite {
|
|||
set {_uniqueStorage()._channel = newValue}
|
||||
}
|
||||
|
||||
///
|
||||
/// True if we witnessed the node over MQTT instead of LoRA transport
|
||||
var viaMqtt: Bool {
|
||||
get {return _storage._viaMqtt}
|
||||
set {_uniqueStorage()._viaMqtt = newValue}
|
||||
}
|
||||
|
||||
///
|
||||
/// Number of hops away from us this node is (0 if adjacent)
|
||||
var hopsAway: UInt32 {
|
||||
get {return _storage._hopsAway}
|
||||
set {_uniqueStorage()._hopsAway = newValue}
|
||||
}
|
||||
|
||||
///
|
||||
/// 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() {}
|
||||
|
|
@ -382,35 +405,6 @@ struct OEMStore {
|
|||
fileprivate var _oemLocalModuleConfig: LocalModuleConfig? = nil
|
||||
}
|
||||
|
||||
///
|
||||
/// RemoteHardwarePins associated with a node
|
||||
struct NodeRemoteHardwarePin {
|
||||
// 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_num exposing the available gpio pin
|
||||
var nodeNum: UInt32 = 0
|
||||
|
||||
///
|
||||
/// The the available gpio pin for usage with RemoteHardware module
|
||||
var pin: RemoteHardwarePin {
|
||||
get {return _pin ?? RemoteHardwarePin()}
|
||||
set {_pin = newValue}
|
||||
}
|
||||
/// Returns true if `pin` has been explicitly set.
|
||||
var hasPin: Bool {return self._pin != nil}
|
||||
/// Clears the value of `pin`. Subsequent reads from it will return its default value.
|
||||
mutating func clearPin() {self._pin = nil}
|
||||
|
||||
var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
init() {}
|
||||
|
||||
fileprivate var _pin: RemoteHardwarePin? = nil
|
||||
}
|
||||
|
||||
#if swift(>=5.5) && canImport(_Concurrency)
|
||||
extension ScreenFonts: @unchecked Sendable {}
|
||||
extension DeviceState: @unchecked Sendable {}
|
||||
|
|
@ -418,7 +412,6 @@ extension NodeInfoLite: @unchecked Sendable {}
|
|||
extension PositionLite: @unchecked Sendable {}
|
||||
extension ChannelFile: @unchecked Sendable {}
|
||||
extension OEMStore: @unchecked Sendable {}
|
||||
extension NodeRemoteHardwarePin: @unchecked Sendable {}
|
||||
#endif // swift(>=5.5) && canImport(_Concurrency)
|
||||
|
||||
// MARK: - Code below here is support for the SwiftProtobuf runtime.
|
||||
|
|
@ -583,6 +576,9 @@ extension NodeInfoLite: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat
|
|||
5: .standard(proto: "last_heard"),
|
||||
6: .standard(proto: "device_metrics"),
|
||||
7: .same(proto: "channel"),
|
||||
8: .standard(proto: "via_mqtt"),
|
||||
9: .standard(proto: "hops_away"),
|
||||
10: .standard(proto: "is_favorite"),
|
||||
]
|
||||
|
||||
fileprivate class _StorageClass {
|
||||
|
|
@ -593,6 +589,9 @@ extension NodeInfoLite: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat
|
|||
var _lastHeard: UInt32 = 0
|
||||
var _deviceMetrics: DeviceMetrics? = nil
|
||||
var _channel: UInt32 = 0
|
||||
var _viaMqtt: Bool = false
|
||||
var _hopsAway: UInt32 = 0
|
||||
var _isFavorite: Bool = false
|
||||
|
||||
static let defaultInstance = _StorageClass()
|
||||
|
||||
|
|
@ -606,6 +605,9 @@ extension NodeInfoLite: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat
|
|||
_lastHeard = source._lastHeard
|
||||
_deviceMetrics = source._deviceMetrics
|
||||
_channel = source._channel
|
||||
_viaMqtt = source._viaMqtt
|
||||
_hopsAway = source._hopsAway
|
||||
_isFavorite = source._isFavorite
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -631,6 +633,9 @@ extension NodeInfoLite: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat
|
|||
case 5: try { try decoder.decodeSingularFixed32Field(value: &_storage._lastHeard) }()
|
||||
case 6: try { try decoder.decodeSingularMessageField(value: &_storage._deviceMetrics) }()
|
||||
case 7: try { try decoder.decodeSingularUInt32Field(value: &_storage._channel) }()
|
||||
case 8: try { try decoder.decodeSingularBoolField(value: &_storage._viaMqtt) }()
|
||||
case 9: try { try decoder.decodeSingularUInt32Field(value: &_storage._hopsAway) }()
|
||||
case 10: try { try decoder.decodeSingularBoolField(value: &_storage._isFavorite) }()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
|
@ -664,6 +669,15 @@ extension NodeInfoLite: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat
|
|||
if _storage._channel != 0 {
|
||||
try visitor.visitSingularUInt32Field(value: _storage._channel, fieldNumber: 7)
|
||||
}
|
||||
if _storage._viaMqtt != false {
|
||||
try visitor.visitSingularBoolField(value: _storage._viaMqtt, fieldNumber: 8)
|
||||
}
|
||||
if _storage._hopsAway != 0 {
|
||||
try visitor.visitSingularUInt32Field(value: _storage._hopsAway, fieldNumber: 9)
|
||||
}
|
||||
if _storage._isFavorite != false {
|
||||
try visitor.visitSingularBoolField(value: _storage._isFavorite, fieldNumber: 10)
|
||||
}
|
||||
}
|
||||
try unknownFields.traverse(visitor: &visitor)
|
||||
}
|
||||
|
|
@ -680,6 +694,9 @@ extension NodeInfoLite: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat
|
|||
if _storage._lastHeard != rhs_storage._lastHeard {return false}
|
||||
if _storage._deviceMetrics != rhs_storage._deviceMetrics {return false}
|
||||
if _storage._channel != rhs_storage._channel {return false}
|
||||
if _storage._viaMqtt != rhs_storage._viaMqtt {return false}
|
||||
if _storage._hopsAway != rhs_storage._hopsAway {return false}
|
||||
if _storage._isFavorite != rhs_storage._isFavorite {return false}
|
||||
return true
|
||||
}
|
||||
if !storagesAreEqual {return false}
|
||||
|
|
@ -860,45 +877,3 @@ extension OEMStore: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB
|
|||
return true
|
||||
}
|
||||
}
|
||||
|
||||
extension NodeRemoteHardwarePin: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
||||
static let protoMessageName: String = _protobuf_package + ".NodeRemoteHardwarePin"
|
||||
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
||||
1: .standard(proto: "node_num"),
|
||||
2: .same(proto: "pin"),
|
||||
]
|
||||
|
||||
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||
while let fieldNumber = try decoder.nextFieldNumber() {
|
||||
// The use of inline closures is to circumvent an issue where the compiler
|
||||
// allocates stack space for every case branch when no optimizations are
|
||||
// enabled. https://github.com/apple/swift-protobuf/issues/1034
|
||||
switch fieldNumber {
|
||||
case 1: try { try decoder.decodeSingularUInt32Field(value: &self.nodeNum) }()
|
||||
case 2: try { try decoder.decodeSingularMessageField(value: &self._pin) }()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
|
||||
// The use of inline closures is to circumvent an issue where the compiler
|
||||
// allocates stack space for every if/case branch local when no optimizations
|
||||
// are enabled. https://github.com/apple/swift-protobuf/issues/1034 and
|
||||
// https://github.com/apple/swift-protobuf/issues/1182
|
||||
if self.nodeNum != 0 {
|
||||
try visitor.visitSingularUInt32Field(value: self.nodeNum, fieldNumber: 1)
|
||||
}
|
||||
try { if let v = self._pin {
|
||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 2)
|
||||
} }()
|
||||
try unknownFields.traverse(visitor: &visitor)
|
||||
}
|
||||
|
||||
static func ==(lhs: NodeRemoteHardwarePin, rhs: NodeRemoteHardwarePin) -> Bool {
|
||||
if lhs.nodeNum != rhs.nodeNum {return false}
|
||||
if lhs._pin != rhs._pin {return false}
|
||||
if lhs.unknownFields != rhs.unknownFields {return false}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1434,8 +1434,7 @@ struct MeshPacket {
|
|||
}
|
||||
|
||||
///
|
||||
/// The (immediatSee Priority description for more details.y should be fixed32 instead, this encoding only
|
||||
/// hurts the ble link though.
|
||||
/// The (immediate) destination for this packet
|
||||
var to: UInt32 {
|
||||
get {return _storage._to}
|
||||
set {_uniqueStorage()._to = newValue}
|
||||
|
|
@ -1566,6 +1565,14 @@ struct MeshPacket {
|
|||
set {_uniqueStorage()._viaMqtt = newValue}
|
||||
}
|
||||
|
||||
///
|
||||
/// Hop limit with which the original packet started. Sent via LoRa using three bits in the unencrypted header.
|
||||
/// When receiving a packet, the difference between hop_start and hop_limit gives how many hops it traveled.
|
||||
var hopStart: UInt32 {
|
||||
get {return _storage._hopStart}
|
||||
set {_uniqueStorage()._hopStart = newValue}
|
||||
}
|
||||
|
||||
var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
enum OneOf_PayloadVariant: Equatable {
|
||||
|
|
@ -1779,62 +1786,94 @@ struct NodeInfo {
|
|||
|
||||
///
|
||||
/// The node number
|
||||
var num: UInt32 = 0
|
||||
var num: UInt32 {
|
||||
get {return _storage._num}
|
||||
set {_uniqueStorage()._num = newValue}
|
||||
}
|
||||
|
||||
///
|
||||
/// The user info for this node
|
||||
var user: User {
|
||||
get {return _user ?? User()}
|
||||
set {_user = newValue}
|
||||
get {return _storage._user ?? User()}
|
||||
set {_uniqueStorage()._user = newValue}
|
||||
}
|
||||
/// Returns true if `user` has been explicitly set.
|
||||
var hasUser: Bool {return self._user != nil}
|
||||
var hasUser: Bool {return _storage._user != nil}
|
||||
/// Clears the value of `user`. Subsequent reads from it will return its default value.
|
||||
mutating func clearUser() {self._user = nil}
|
||||
mutating func clearUser() {_uniqueStorage()._user = nil}
|
||||
|
||||
///
|
||||
/// This position data. Note: before 1.2.14 we would also store the last time we've heard from this node in position.time, that is no longer true.
|
||||
/// Position.time now indicates the last time we received a POSITION from that node.
|
||||
var position: Position {
|
||||
get {return _position ?? Position()}
|
||||
set {_position = newValue}
|
||||
get {return _storage._position ?? Position()}
|
||||
set {_uniqueStorage()._position = newValue}
|
||||
}
|
||||
/// Returns true if `position` has been explicitly set.
|
||||
var hasPosition: Bool {return self._position != nil}
|
||||
var hasPosition: Bool {return _storage._position != nil}
|
||||
/// Clears the value of `position`. Subsequent reads from it will return its default value.
|
||||
mutating func clearPosition() {self._position = nil}
|
||||
mutating func clearPosition() {_uniqueStorage()._position = nil}
|
||||
|
||||
///
|
||||
/// Returns the Signal-to-noise ratio (SNR) of the last received message,
|
||||
/// as measured by the receiver. Return SNR of the last received message in dB
|
||||
var snr: Float = 0
|
||||
var snr: Float {
|
||||
get {return _storage._snr}
|
||||
set {_uniqueStorage()._snr = newValue}
|
||||
}
|
||||
|
||||
///
|
||||
/// Set to indicate the last time we received a packet from this node
|
||||
var lastHeard: UInt32 = 0
|
||||
var lastHeard: UInt32 {
|
||||
get {return _storage._lastHeard}
|
||||
set {_uniqueStorage()._lastHeard = newValue}
|
||||
}
|
||||
|
||||
///
|
||||
/// The latest device metrics for the node.
|
||||
var deviceMetrics: DeviceMetrics {
|
||||
get {return _deviceMetrics ?? DeviceMetrics()}
|
||||
set {_deviceMetrics = newValue}
|
||||
get {return _storage._deviceMetrics ?? DeviceMetrics()}
|
||||
set {_uniqueStorage()._deviceMetrics = newValue}
|
||||
}
|
||||
/// Returns true if `deviceMetrics` has been explicitly set.
|
||||
var hasDeviceMetrics: Bool {return self._deviceMetrics != nil}
|
||||
var hasDeviceMetrics: Bool {return _storage._deviceMetrics != nil}
|
||||
/// Clears the value of `deviceMetrics`. Subsequent reads from it will return its default value.
|
||||
mutating func clearDeviceMetrics() {self._deviceMetrics = nil}
|
||||
mutating func clearDeviceMetrics() {_uniqueStorage()._deviceMetrics = nil}
|
||||
|
||||
///
|
||||
/// local channel index we heard that node on. Only populated if its not the default channel.
|
||||
var channel: UInt32 = 0
|
||||
var channel: UInt32 {
|
||||
get {return _storage._channel}
|
||||
set {_uniqueStorage()._channel = newValue}
|
||||
}
|
||||
|
||||
///
|
||||
/// True if we witnessed the node over MQTT instead of LoRA transport
|
||||
var viaMqtt: Bool {
|
||||
get {return _storage._viaMqtt}
|
||||
set {_uniqueStorage()._viaMqtt = newValue}
|
||||
}
|
||||
|
||||
///
|
||||
/// Number of hops away from us this node is (0 if adjacent)
|
||||
var hopsAway: UInt32 {
|
||||
get {return _storage._hopsAway}
|
||||
set {_uniqueStorage()._hopsAway = newValue}
|
||||
}
|
||||
|
||||
///
|
||||
/// 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 _user: User? = nil
|
||||
fileprivate var _position: Position? = nil
|
||||
fileprivate var _deviceMetrics: DeviceMetrics? = nil
|
||||
fileprivate var _storage = _StorageClass.defaultInstance
|
||||
}
|
||||
|
||||
///
|
||||
|
|
@ -2357,6 +2396,16 @@ struct ToRadio {
|
|||
set {payloadVariant = .mqttClientProxyMessage(newValue)}
|
||||
}
|
||||
|
||||
///
|
||||
/// Heartbeat message (used to keep the device connection awake on serial)
|
||||
var heartbeat: Heartbeat {
|
||||
get {
|
||||
if case .heartbeat(let v)? = payloadVariant {return v}
|
||||
return Heartbeat()
|
||||
}
|
||||
set {payloadVariant = .heartbeat(newValue)}
|
||||
}
|
||||
|
||||
var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
///
|
||||
|
|
@ -2384,6 +2433,9 @@ struct ToRadio {
|
|||
///
|
||||
/// MQTT Client Proxy Message (for client / phone subscribed to MQTT sending to device)
|
||||
case mqttClientProxyMessage(MqttClientProxyMessage)
|
||||
///
|
||||
/// Heartbeat message (used to keep the device connection awake on serial)
|
||||
case heartbeat(Heartbeat)
|
||||
|
||||
#if !swift(>=4.1)
|
||||
static func ==(lhs: ToRadio.OneOf_PayloadVariant, rhs: ToRadio.OneOf_PayloadVariant) -> Bool {
|
||||
|
|
@ -2411,6 +2463,10 @@ struct ToRadio {
|
|||
guard case .mqttClientProxyMessage(let l) = lhs, case .mqttClientProxyMessage(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.heartbeat, .heartbeat): return {
|
||||
guard case .heartbeat(let l) = lhs, case .heartbeat(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
|
|
@ -2550,6 +2606,48 @@ struct DeviceMetadata {
|
|||
init() {}
|
||||
}
|
||||
|
||||
///
|
||||
/// A heartbeat message is sent to the node from the client to keep the connection alive.
|
||||
/// This is currently only needed to keep serial connections alive, but can be used by any PhoneAPI.
|
||||
struct Heartbeat {
|
||||
// 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.
|
||||
|
||||
var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
init() {}
|
||||
}
|
||||
|
||||
///
|
||||
/// RemoteHardwarePins associated with a node
|
||||
struct NodeRemoteHardwarePin {
|
||||
// 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_num exposing the available gpio pin
|
||||
var nodeNum: UInt32 = 0
|
||||
|
||||
///
|
||||
/// The the available gpio pin for usage with RemoteHardware module
|
||||
var pin: RemoteHardwarePin {
|
||||
get {return _pin ?? RemoteHardwarePin()}
|
||||
set {_pin = newValue}
|
||||
}
|
||||
/// Returns true if `pin` has been explicitly set.
|
||||
var hasPin: Bool {return self._pin != nil}
|
||||
/// Clears the value of `pin`. Subsequent reads from it will return its default value.
|
||||
mutating func clearPin() {self._pin = nil}
|
||||
|
||||
var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
init() {}
|
||||
|
||||
fileprivate var _pin: RemoteHardwarePin? = nil
|
||||
}
|
||||
|
||||
#if swift(>=5.5) && canImport(_Concurrency)
|
||||
extension HardwareModel: @unchecked Sendable {}
|
||||
extension Constants: @unchecked Sendable {}
|
||||
|
|
@ -2583,6 +2681,8 @@ extension Compressed: @unchecked Sendable {}
|
|||
extension NeighborInfo: @unchecked Sendable {}
|
||||
extension Neighbor: @unchecked Sendable {}
|
||||
extension DeviceMetadata: @unchecked Sendable {}
|
||||
extension Heartbeat: @unchecked Sendable {}
|
||||
extension NodeRemoteHardwarePin: @unchecked Sendable {}
|
||||
#endif // swift(>=5.5) && canImport(_Concurrency)
|
||||
|
||||
// MARK: - Code below here is support for the SwiftProtobuf runtime.
|
||||
|
|
@ -3369,6 +3469,7 @@ extension MeshPacket: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio
|
|||
12: .standard(proto: "rx_rssi"),
|
||||
13: .same(proto: "delayed"),
|
||||
14: .standard(proto: "via_mqtt"),
|
||||
15: .standard(proto: "hop_start"),
|
||||
]
|
||||
|
||||
fileprivate class _StorageClass {
|
||||
|
|
@ -3385,6 +3486,7 @@ extension MeshPacket: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio
|
|||
var _rxRssi: Int32 = 0
|
||||
var _delayed: MeshPacket.Delayed = .noDelay
|
||||
var _viaMqtt: Bool = false
|
||||
var _hopStart: UInt32 = 0
|
||||
|
||||
static let defaultInstance = _StorageClass()
|
||||
|
||||
|
|
@ -3404,6 +3506,7 @@ extension MeshPacket: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio
|
|||
_rxRssi = source._rxRssi
|
||||
_delayed = source._delayed
|
||||
_viaMqtt = source._viaMqtt
|
||||
_hopStart = source._hopStart
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -3455,6 +3558,7 @@ extension MeshPacket: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio
|
|||
case 12: try { try decoder.decodeSingularInt32Field(value: &_storage._rxRssi) }()
|
||||
case 13: try { try decoder.decodeSingularEnumField(value: &_storage._delayed) }()
|
||||
case 14: try { try decoder.decodeSingularBoolField(value: &_storage._viaMqtt) }()
|
||||
case 15: try { try decoder.decodeSingularUInt32Field(value: &_storage._hopStart) }()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
|
@ -3514,6 +3618,9 @@ extension MeshPacket: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio
|
|||
if _storage._viaMqtt != false {
|
||||
try visitor.visitSingularBoolField(value: _storage._viaMqtt, fieldNumber: 14)
|
||||
}
|
||||
if _storage._hopStart != 0 {
|
||||
try visitor.visitSingularUInt32Field(value: _storage._hopStart, fieldNumber: 15)
|
||||
}
|
||||
}
|
||||
try unknownFields.traverse(visitor: &visitor)
|
||||
}
|
||||
|
|
@ -3536,6 +3643,7 @@ extension MeshPacket: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio
|
|||
if _storage._rxRssi != rhs_storage._rxRssi {return false}
|
||||
if _storage._delayed != rhs_storage._delayed {return false}
|
||||
if _storage._viaMqtt != rhs_storage._viaMqtt {return false}
|
||||
if _storage._hopStart != rhs_storage._hopStart {return false}
|
||||
return true
|
||||
}
|
||||
if !storagesAreEqual {return false}
|
||||
|
|
@ -3575,63 +3683,131 @@ extension NodeInfo: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB
|
|||
5: .standard(proto: "last_heard"),
|
||||
6: .standard(proto: "device_metrics"),
|
||||
7: .same(proto: "channel"),
|
||||
8: .standard(proto: "via_mqtt"),
|
||||
9: .standard(proto: "hops_away"),
|
||||
10: .standard(proto: "is_favorite"),
|
||||
]
|
||||
|
||||
fileprivate class _StorageClass {
|
||||
var _num: UInt32 = 0
|
||||
var _user: User? = nil
|
||||
var _position: Position? = nil
|
||||
var _snr: Float = 0
|
||||
var _lastHeard: UInt32 = 0
|
||||
var _deviceMetrics: DeviceMetrics? = nil
|
||||
var _channel: UInt32 = 0
|
||||
var _viaMqtt: Bool = false
|
||||
var _hopsAway: UInt32 = 0
|
||||
var _isFavorite: Bool = false
|
||||
|
||||
static let defaultInstance = _StorageClass()
|
||||
|
||||
private init() {}
|
||||
|
||||
init(copying source: _StorageClass) {
|
||||
_num = source._num
|
||||
_user = source._user
|
||||
_position = source._position
|
||||
_snr = source._snr
|
||||
_lastHeard = source._lastHeard
|
||||
_deviceMetrics = source._deviceMetrics
|
||||
_channel = source._channel
|
||||
_viaMqtt = source._viaMqtt
|
||||
_hopsAway = source._hopsAway
|
||||
_isFavorite = source._isFavorite
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate mutating func _uniqueStorage() -> _StorageClass {
|
||||
if !isKnownUniquelyReferenced(&_storage) {
|
||||
_storage = _StorageClass(copying: _storage)
|
||||
}
|
||||
return _storage
|
||||
}
|
||||
|
||||
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||
while let fieldNumber = try decoder.nextFieldNumber() {
|
||||
// The use of inline closures is to circumvent an issue where the compiler
|
||||
// allocates stack space for every case branch when no optimizations are
|
||||
// enabled. https://github.com/apple/swift-protobuf/issues/1034
|
||||
switch fieldNumber {
|
||||
case 1: try { try decoder.decodeSingularUInt32Field(value: &self.num) }()
|
||||
case 2: try { try decoder.decodeSingularMessageField(value: &self._user) }()
|
||||
case 3: try { try decoder.decodeSingularMessageField(value: &self._position) }()
|
||||
case 4: try { try decoder.decodeSingularFloatField(value: &self.snr) }()
|
||||
case 5: try { try decoder.decodeSingularFixed32Field(value: &self.lastHeard) }()
|
||||
case 6: try { try decoder.decodeSingularMessageField(value: &self._deviceMetrics) }()
|
||||
case 7: try { try decoder.decodeSingularUInt32Field(value: &self.channel) }()
|
||||
default: break
|
||||
_ = _uniqueStorage()
|
||||
try withExtendedLifetime(_storage) { (_storage: _StorageClass) in
|
||||
while let fieldNumber = try decoder.nextFieldNumber() {
|
||||
// The use of inline closures is to circumvent an issue where the compiler
|
||||
// allocates stack space for every case branch when no optimizations are
|
||||
// enabled. https://github.com/apple/swift-protobuf/issues/1034
|
||||
switch fieldNumber {
|
||||
case 1: try { try decoder.decodeSingularUInt32Field(value: &_storage._num) }()
|
||||
case 2: try { try decoder.decodeSingularMessageField(value: &_storage._user) }()
|
||||
case 3: try { try decoder.decodeSingularMessageField(value: &_storage._position) }()
|
||||
case 4: try { try decoder.decodeSingularFloatField(value: &_storage._snr) }()
|
||||
case 5: try { try decoder.decodeSingularFixed32Field(value: &_storage._lastHeard) }()
|
||||
case 6: try { try decoder.decodeSingularMessageField(value: &_storage._deviceMetrics) }()
|
||||
case 7: try { try decoder.decodeSingularUInt32Field(value: &_storage._channel) }()
|
||||
case 8: try { try decoder.decodeSingularBoolField(value: &_storage._viaMqtt) }()
|
||||
case 9: try { try decoder.decodeSingularUInt32Field(value: &_storage._hopsAway) }()
|
||||
case 10: try { try decoder.decodeSingularBoolField(value: &_storage._isFavorite) }()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
|
||||
// The use of inline closures is to circumvent an issue where the compiler
|
||||
// allocates stack space for every if/case branch local when no optimizations
|
||||
// are enabled. https://github.com/apple/swift-protobuf/issues/1034 and
|
||||
// https://github.com/apple/swift-protobuf/issues/1182
|
||||
if self.num != 0 {
|
||||
try visitor.visitSingularUInt32Field(value: self.num, fieldNumber: 1)
|
||||
}
|
||||
try { if let v = self._user {
|
||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 2)
|
||||
} }()
|
||||
try { if let v = self._position {
|
||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 3)
|
||||
} }()
|
||||
if self.snr != 0 {
|
||||
try visitor.visitSingularFloatField(value: self.snr, fieldNumber: 4)
|
||||
}
|
||||
if self.lastHeard != 0 {
|
||||
try visitor.visitSingularFixed32Field(value: self.lastHeard, fieldNumber: 5)
|
||||
}
|
||||
try { if let v = self._deviceMetrics {
|
||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 6)
|
||||
} }()
|
||||
if self.channel != 0 {
|
||||
try visitor.visitSingularUInt32Field(value: self.channel, fieldNumber: 7)
|
||||
try withExtendedLifetime(_storage) { (_storage: _StorageClass) in
|
||||
// The use of inline closures is to circumvent an issue where the compiler
|
||||
// allocates stack space for every if/case branch local when no optimizations
|
||||
// are enabled. https://github.com/apple/swift-protobuf/issues/1034 and
|
||||
// https://github.com/apple/swift-protobuf/issues/1182
|
||||
if _storage._num != 0 {
|
||||
try visitor.visitSingularUInt32Field(value: _storage._num, fieldNumber: 1)
|
||||
}
|
||||
try { if let v = _storage._user {
|
||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 2)
|
||||
} }()
|
||||
try { if let v = _storage._position {
|
||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 3)
|
||||
} }()
|
||||
if _storage._snr != 0 {
|
||||
try visitor.visitSingularFloatField(value: _storage._snr, fieldNumber: 4)
|
||||
}
|
||||
if _storage._lastHeard != 0 {
|
||||
try visitor.visitSingularFixed32Field(value: _storage._lastHeard, fieldNumber: 5)
|
||||
}
|
||||
try { if let v = _storage._deviceMetrics {
|
||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 6)
|
||||
} }()
|
||||
if _storage._channel != 0 {
|
||||
try visitor.visitSingularUInt32Field(value: _storage._channel, fieldNumber: 7)
|
||||
}
|
||||
if _storage._viaMqtt != false {
|
||||
try visitor.visitSingularBoolField(value: _storage._viaMqtt, fieldNumber: 8)
|
||||
}
|
||||
if _storage._hopsAway != 0 {
|
||||
try visitor.visitSingularUInt32Field(value: _storage._hopsAway, fieldNumber: 9)
|
||||
}
|
||||
if _storage._isFavorite != false {
|
||||
try visitor.visitSingularBoolField(value: _storage._isFavorite, fieldNumber: 10)
|
||||
}
|
||||
}
|
||||
try unknownFields.traverse(visitor: &visitor)
|
||||
}
|
||||
|
||||
static func ==(lhs: NodeInfo, rhs: NodeInfo) -> Bool {
|
||||
if lhs.num != rhs.num {return false}
|
||||
if lhs._user != rhs._user {return false}
|
||||
if lhs._position != rhs._position {return false}
|
||||
if lhs.snr != rhs.snr {return false}
|
||||
if lhs.lastHeard != rhs.lastHeard {return false}
|
||||
if lhs._deviceMetrics != rhs._deviceMetrics {return false}
|
||||
if lhs.channel != rhs.channel {return false}
|
||||
if lhs._storage !== rhs._storage {
|
||||
let storagesAreEqual: Bool = withExtendedLifetime((lhs._storage, rhs._storage)) { (_args: (_StorageClass, _StorageClass)) in
|
||||
let _storage = _args.0
|
||||
let rhs_storage = _args.1
|
||||
if _storage._num != rhs_storage._num {return false}
|
||||
if _storage._user != rhs_storage._user {return false}
|
||||
if _storage._position != rhs_storage._position {return false}
|
||||
if _storage._snr != rhs_storage._snr {return false}
|
||||
if _storage._lastHeard != rhs_storage._lastHeard {return false}
|
||||
if _storage._deviceMetrics != rhs_storage._deviceMetrics {return false}
|
||||
if _storage._channel != rhs_storage._channel {return false}
|
||||
if _storage._viaMqtt != rhs_storage._viaMqtt {return false}
|
||||
if _storage._hopsAway != rhs_storage._hopsAway {return false}
|
||||
if _storage._isFavorite != rhs_storage._isFavorite {return false}
|
||||
return true
|
||||
}
|
||||
if !storagesAreEqual {return false}
|
||||
}
|
||||
if lhs.unknownFields != rhs.unknownFields {return false}
|
||||
return true
|
||||
}
|
||||
|
|
@ -4099,6 +4275,7 @@ extension ToRadio: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBa
|
|||
4: .same(proto: "disconnect"),
|
||||
5: .same(proto: "xmodemPacket"),
|
||||
6: .same(proto: "mqttClientProxyMessage"),
|
||||
7: .same(proto: "heartbeat"),
|
||||
]
|
||||
|
||||
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||
|
|
@ -4162,6 +4339,19 @@ extension ToRadio: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBa
|
|||
self.payloadVariant = .mqttClientProxyMessage(v)
|
||||
}
|
||||
}()
|
||||
case 7: try {
|
||||
var v: Heartbeat?
|
||||
var hadOneofValue = false
|
||||
if let current = self.payloadVariant {
|
||||
hadOneofValue = true
|
||||
if case .heartbeat(let m) = current {v = m}
|
||||
}
|
||||
try decoder.decodeSingularMessageField(value: &v)
|
||||
if let v = v {
|
||||
if hadOneofValue {try decoder.handleConflictingOneOf()}
|
||||
self.payloadVariant = .heartbeat(v)
|
||||
}
|
||||
}()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
|
@ -4193,6 +4383,10 @@ extension ToRadio: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBa
|
|||
guard case .mqttClientProxyMessage(let v)? = self.payloadVariant else { preconditionFailure() }
|
||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 6)
|
||||
}()
|
||||
case .heartbeat?: try {
|
||||
guard case .heartbeat(let v)? = self.payloadVariant else { preconditionFailure() }
|
||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 7)
|
||||
}()
|
||||
case nil: break
|
||||
}
|
||||
try unknownFields.traverse(visitor: &visitor)
|
||||
|
|
@ -4428,3 +4622,64 @@ extension DeviceMetadata: SwiftProtobuf.Message, SwiftProtobuf._MessageImplement
|
|||
return true
|
||||
}
|
||||
}
|
||||
|
||||
extension Heartbeat: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
||||
static let protoMessageName: String = _protobuf_package + ".Heartbeat"
|
||||
static let _protobuf_nameMap = SwiftProtobuf._NameMap()
|
||||
|
||||
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||
while let _ = try decoder.nextFieldNumber() {
|
||||
}
|
||||
}
|
||||
|
||||
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
|
||||
try unknownFields.traverse(visitor: &visitor)
|
||||
}
|
||||
|
||||
static func ==(lhs: Heartbeat, rhs: Heartbeat) -> Bool {
|
||||
if lhs.unknownFields != rhs.unknownFields {return false}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
extension NodeRemoteHardwarePin: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
||||
static let protoMessageName: String = _protobuf_package + ".NodeRemoteHardwarePin"
|
||||
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
||||
1: .standard(proto: "node_num"),
|
||||
2: .same(proto: "pin"),
|
||||
]
|
||||
|
||||
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||
while let fieldNumber = try decoder.nextFieldNumber() {
|
||||
// The use of inline closures is to circumvent an issue where the compiler
|
||||
// allocates stack space for every case branch when no optimizations are
|
||||
// enabled. https://github.com/apple/swift-protobuf/issues/1034
|
||||
switch fieldNumber {
|
||||
case 1: try { try decoder.decodeSingularUInt32Field(value: &self.nodeNum) }()
|
||||
case 2: try { try decoder.decodeSingularMessageField(value: &self._pin) }()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
|
||||
// The use of inline closures is to circumvent an issue where the compiler
|
||||
// allocates stack space for every if/case branch local when no optimizations
|
||||
// are enabled. https://github.com/apple/swift-protobuf/issues/1034 and
|
||||
// https://github.com/apple/swift-protobuf/issues/1182
|
||||
if self.nodeNum != 0 {
|
||||
try visitor.visitSingularUInt32Field(value: self.nodeNum, fieldNumber: 1)
|
||||
}
|
||||
try { if let v = self._pin {
|
||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 2)
|
||||
} }()
|
||||
try unknownFields.traverse(visitor: &visitor)
|
||||
}
|
||||
|
||||
static func ==(lhs: NodeRemoteHardwarePin, rhs: NodeRemoteHardwarePin) -> Bool {
|
||||
if lhs.nodeNum != rhs.nodeNum {return false}
|
||||
if lhs._pin != rhs._pin {return false}
|
||||
if lhs.unknownFields != rhs.unknownFields {return false}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -376,6 +376,43 @@ struct ModuleConfig {
|
|||
/// If true, we can use the connected phone / client to proxy messages to MQTT instead of a direct connection
|
||||
var proxyToClientEnabled: Bool = false
|
||||
|
||||
///
|
||||
/// If true, we will periodically report unencrypted information about our node to a map via MQTT
|
||||
var mapReportingEnabled: Bool = false
|
||||
|
||||
///
|
||||
/// Settings for reporting information about our node to a map via MQTT
|
||||
var mapReportSettings: ModuleConfig.MapReportSettings {
|
||||
get {return _mapReportSettings ?? ModuleConfig.MapReportSettings()}
|
||||
set {_mapReportSettings = newValue}
|
||||
}
|
||||
/// Returns true if `mapReportSettings` has been explicitly set.
|
||||
var hasMapReportSettings: Bool {return self._mapReportSettings != nil}
|
||||
/// Clears the value of `mapReportSettings`. Subsequent reads from it will return its default value.
|
||||
mutating func clearMapReportSettings() {self._mapReportSettings = nil}
|
||||
|
||||
var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
init() {}
|
||||
|
||||
fileprivate var _mapReportSettings: ModuleConfig.MapReportSettings? = nil
|
||||
}
|
||||
|
||||
///
|
||||
/// Settings for reporting unencrypted information about our node to a map via MQTT
|
||||
struct MapReportSettings {
|
||||
// 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.
|
||||
|
||||
///
|
||||
/// How often we should report our info to the map (in seconds)
|
||||
var publishIntervalSecs: UInt32 = 0
|
||||
|
||||
///
|
||||
/// Bits of precision for the location sent (default of 32 is full precision).
|
||||
var positionPrecision: UInt32 = 0
|
||||
|
||||
var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
init() {}
|
||||
|
|
@ -1207,6 +1244,7 @@ extension RemoteHardwarePinType: @unchecked Sendable {}
|
|||
extension ModuleConfig: @unchecked Sendable {}
|
||||
extension ModuleConfig.OneOf_PayloadVariant: @unchecked Sendable {}
|
||||
extension ModuleConfig.MQTTConfig: @unchecked Sendable {}
|
||||
extension ModuleConfig.MapReportSettings: @unchecked Sendable {}
|
||||
extension ModuleConfig.RemoteHardwareConfig: @unchecked Sendable {}
|
||||
extension ModuleConfig.NeighborInfoConfig: @unchecked Sendable {}
|
||||
extension ModuleConfig.DetectionSensorConfig: @unchecked Sendable {}
|
||||
|
|
@ -1518,6 +1556,8 @@ extension ModuleConfig.MQTTConfig: SwiftProtobuf.Message, SwiftProtobuf._Message
|
|||
7: .standard(proto: "tls_enabled"),
|
||||
8: .same(proto: "root"),
|
||||
9: .standard(proto: "proxy_to_client_enabled"),
|
||||
10: .standard(proto: "map_reporting_enabled"),
|
||||
11: .standard(proto: "map_report_settings"),
|
||||
]
|
||||
|
||||
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||
|
|
@ -1535,12 +1575,18 @@ extension ModuleConfig.MQTTConfig: SwiftProtobuf.Message, SwiftProtobuf._Message
|
|||
case 7: try { try decoder.decodeSingularBoolField(value: &self.tlsEnabled) }()
|
||||
case 8: try { try decoder.decodeSingularStringField(value: &self.root) }()
|
||||
case 9: try { try decoder.decodeSingularBoolField(value: &self.proxyToClientEnabled) }()
|
||||
case 10: try { try decoder.decodeSingularBoolField(value: &self.mapReportingEnabled) }()
|
||||
case 11: try { try decoder.decodeSingularMessageField(value: &self._mapReportSettings) }()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
|
||||
// The use of inline closures is to circumvent an issue where the compiler
|
||||
// allocates stack space for every if/case branch local when no optimizations
|
||||
// are enabled. https://github.com/apple/swift-protobuf/issues/1034 and
|
||||
// https://github.com/apple/swift-protobuf/issues/1182
|
||||
if self.enabled != false {
|
||||
try visitor.visitSingularBoolField(value: self.enabled, fieldNumber: 1)
|
||||
}
|
||||
|
|
@ -1568,6 +1614,12 @@ extension ModuleConfig.MQTTConfig: SwiftProtobuf.Message, SwiftProtobuf._Message
|
|||
if self.proxyToClientEnabled != false {
|
||||
try visitor.visitSingularBoolField(value: self.proxyToClientEnabled, fieldNumber: 9)
|
||||
}
|
||||
if self.mapReportingEnabled != false {
|
||||
try visitor.visitSingularBoolField(value: self.mapReportingEnabled, fieldNumber: 10)
|
||||
}
|
||||
try { if let v = self._mapReportSettings {
|
||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 11)
|
||||
} }()
|
||||
try unknownFields.traverse(visitor: &visitor)
|
||||
}
|
||||
|
||||
|
|
@ -1581,6 +1633,46 @@ extension ModuleConfig.MQTTConfig: SwiftProtobuf.Message, SwiftProtobuf._Message
|
|||
if lhs.tlsEnabled != rhs.tlsEnabled {return false}
|
||||
if lhs.root != rhs.root {return false}
|
||||
if lhs.proxyToClientEnabled != rhs.proxyToClientEnabled {return false}
|
||||
if lhs.mapReportingEnabled != rhs.mapReportingEnabled {return false}
|
||||
if lhs._mapReportSettings != rhs._mapReportSettings {return false}
|
||||
if lhs.unknownFields != rhs.unknownFields {return false}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
extension ModuleConfig.MapReportSettings: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
||||
static let protoMessageName: String = ModuleConfig.protoMessageName + ".MapReportSettings"
|
||||
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
||||
1: .standard(proto: "publish_interval_secs"),
|
||||
2: .standard(proto: "position_precision"),
|
||||
]
|
||||
|
||||
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||
while let fieldNumber = try decoder.nextFieldNumber() {
|
||||
// The use of inline closures is to circumvent an issue where the compiler
|
||||
// allocates stack space for every case branch when no optimizations are
|
||||
// enabled. https://github.com/apple/swift-protobuf/issues/1034
|
||||
switch fieldNumber {
|
||||
case 1: try { try decoder.decodeSingularUInt32Field(value: &self.publishIntervalSecs) }()
|
||||
case 2: try { try decoder.decodeSingularUInt32Field(value: &self.positionPrecision) }()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
|
||||
if self.publishIntervalSecs != 0 {
|
||||
try visitor.visitSingularUInt32Field(value: self.publishIntervalSecs, fieldNumber: 1)
|
||||
}
|
||||
if self.positionPrecision != 0 {
|
||||
try visitor.visitSingularUInt32Field(value: self.positionPrecision, fieldNumber: 2)
|
||||
}
|
||||
try unknownFields.traverse(visitor: &visitor)
|
||||
}
|
||||
|
||||
static func ==(lhs: ModuleConfig.MapReportSettings, rhs: ModuleConfig.MapReportSettings) -> Bool {
|
||||
if lhs.publishIntervalSecs != rhs.publishIntervalSecs {return false}
|
||||
if lhs.positionPrecision != rhs.positionPrecision {return false}
|
||||
if lhs.unknownFields != rhs.unknownFields {return false}
|
||||
return true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,8 +55,75 @@ struct ServiceEnvelope {
|
|||
fileprivate var _packet: MeshPacket? = nil
|
||||
}
|
||||
|
||||
///
|
||||
/// Information about a node intended to be reported unencrypted to a map using MQTT.
|
||||
struct MapReport {
|
||||
// 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.
|
||||
|
||||
///
|
||||
/// A full name for this user, i.e. "Kevin Hester"
|
||||
var longName: String = String()
|
||||
|
||||
///
|
||||
/// A VERY short name, ideally two characters.
|
||||
/// Suitable for a tiny OLED screen
|
||||
var shortName: String = String()
|
||||
|
||||
///
|
||||
/// Role of the node that applies specific settings for a particular use-case
|
||||
var role: Config.DeviceConfig.Role = .client
|
||||
|
||||
///
|
||||
/// Hardware model of the node, i.e. T-Beam, Heltec V3, etc...
|
||||
var hwModel: HardwareModel = .unset
|
||||
|
||||
///
|
||||
/// Device firmware version string
|
||||
var firmwareVersion: String = String()
|
||||
|
||||
///
|
||||
/// The region code for the radio (US, CN, EU433, etc...)
|
||||
var region: Config.LoRaConfig.RegionCode = .unset
|
||||
|
||||
///
|
||||
/// Modem preset used by the radio (LongFast, MediumSlow, etc...)
|
||||
var modemPreset: Config.LoRaConfig.ModemPreset = .longFast
|
||||
|
||||
///
|
||||
/// Whether the node has a channel with default PSK and name (LongFast, MediumSlow, etc...)
|
||||
/// and it uses the default frequency slot given the region and modem preset.
|
||||
var hasDefaultChannel_p: Bool = false
|
||||
|
||||
///
|
||||
/// Latitude: multiply by 1e-7 to get degrees in floating point
|
||||
var latitudeI: Int32 = 0
|
||||
|
||||
///
|
||||
/// Longitude: multiply by 1e-7 to get degrees in floating point
|
||||
var longitudeI: Int32 = 0
|
||||
|
||||
///
|
||||
/// Altitude in meters above MSL
|
||||
var altitude: Int32 = 0
|
||||
|
||||
///
|
||||
/// Indicates the bits of precision for latitude and longitude set by the sending node
|
||||
var positionPrecision: UInt32 = 0
|
||||
|
||||
///
|
||||
/// Number of online nodes (heard in the last 2 hours) this node has in its list that were received locally (not via MQTT)
|
||||
var numOnlineLocalNodes: UInt32 = 0
|
||||
|
||||
var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
init() {}
|
||||
}
|
||||
|
||||
#if swift(>=5.5) && canImport(_Concurrency)
|
||||
extension ServiceEnvelope: @unchecked Sendable {}
|
||||
extension MapReport: @unchecked Sendable {}
|
||||
#endif // swift(>=5.5) && canImport(_Concurrency)
|
||||
|
||||
// MARK: - Code below here is support for the SwiftProtobuf runtime.
|
||||
|
|
@ -110,3 +177,107 @@ extension ServiceEnvelope: SwiftProtobuf.Message, SwiftProtobuf._MessageImplemen
|
|||
return true
|
||||
}
|
||||
}
|
||||
|
||||
extension MapReport: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
||||
static let protoMessageName: String = _protobuf_package + ".MapReport"
|
||||
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
||||
1: .standard(proto: "long_name"),
|
||||
2: .standard(proto: "short_name"),
|
||||
3: .same(proto: "role"),
|
||||
4: .standard(proto: "hw_model"),
|
||||
5: .standard(proto: "firmware_version"),
|
||||
6: .same(proto: "region"),
|
||||
7: .standard(proto: "modem_preset"),
|
||||
8: .standard(proto: "has_default_channel"),
|
||||
9: .standard(proto: "latitude_i"),
|
||||
10: .standard(proto: "longitude_i"),
|
||||
11: .same(proto: "altitude"),
|
||||
12: .standard(proto: "position_precision"),
|
||||
13: .standard(proto: "num_online_local_nodes"),
|
||||
]
|
||||
|
||||
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.decodeSingularStringField(value: &self.longName) }()
|
||||
case 2: try { try decoder.decodeSingularStringField(value: &self.shortName) }()
|
||||
case 3: try { try decoder.decodeSingularEnumField(value: &self.role) }()
|
||||
case 4: try { try decoder.decodeSingularEnumField(value: &self.hwModel) }()
|
||||
case 5: try { try decoder.decodeSingularStringField(value: &self.firmwareVersion) }()
|
||||
case 6: try { try decoder.decodeSingularEnumField(value: &self.region) }()
|
||||
case 7: try { try decoder.decodeSingularEnumField(value: &self.modemPreset) }()
|
||||
case 8: try { try decoder.decodeSingularBoolField(value: &self.hasDefaultChannel_p) }()
|
||||
case 9: try { try decoder.decodeSingularSFixed32Field(value: &self.latitudeI) }()
|
||||
case 10: try { try decoder.decodeSingularSFixed32Field(value: &self.longitudeI) }()
|
||||
case 11: try { try decoder.decodeSingularInt32Field(value: &self.altitude) }()
|
||||
case 12: try { try decoder.decodeSingularUInt32Field(value: &self.positionPrecision) }()
|
||||
case 13: try { try decoder.decodeSingularUInt32Field(value: &self.numOnlineLocalNodes) }()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
|
||||
if !self.longName.isEmpty {
|
||||
try visitor.visitSingularStringField(value: self.longName, fieldNumber: 1)
|
||||
}
|
||||
if !self.shortName.isEmpty {
|
||||
try visitor.visitSingularStringField(value: self.shortName, fieldNumber: 2)
|
||||
}
|
||||
if self.role != .client {
|
||||
try visitor.visitSingularEnumField(value: self.role, fieldNumber: 3)
|
||||
}
|
||||
if self.hwModel != .unset {
|
||||
try visitor.visitSingularEnumField(value: self.hwModel, fieldNumber: 4)
|
||||
}
|
||||
if !self.firmwareVersion.isEmpty {
|
||||
try visitor.visitSingularStringField(value: self.firmwareVersion, fieldNumber: 5)
|
||||
}
|
||||
if self.region != .unset {
|
||||
try visitor.visitSingularEnumField(value: self.region, fieldNumber: 6)
|
||||
}
|
||||
if self.modemPreset != .longFast {
|
||||
try visitor.visitSingularEnumField(value: self.modemPreset, fieldNumber: 7)
|
||||
}
|
||||
if self.hasDefaultChannel_p != false {
|
||||
try visitor.visitSingularBoolField(value: self.hasDefaultChannel_p, fieldNumber: 8)
|
||||
}
|
||||
if self.latitudeI != 0 {
|
||||
try visitor.visitSingularSFixed32Field(value: self.latitudeI, fieldNumber: 9)
|
||||
}
|
||||
if self.longitudeI != 0 {
|
||||
try visitor.visitSingularSFixed32Field(value: self.longitudeI, fieldNumber: 10)
|
||||
}
|
||||
if self.altitude != 0 {
|
||||
try visitor.visitSingularInt32Field(value: self.altitude, fieldNumber: 11)
|
||||
}
|
||||
if self.positionPrecision != 0 {
|
||||
try visitor.visitSingularUInt32Field(value: self.positionPrecision, fieldNumber: 12)
|
||||
}
|
||||
if self.numOnlineLocalNodes != 0 {
|
||||
try visitor.visitSingularUInt32Field(value: self.numOnlineLocalNodes, fieldNumber: 13)
|
||||
}
|
||||
try unknownFields.traverse(visitor: &visitor)
|
||||
}
|
||||
|
||||
static func ==(lhs: MapReport, rhs: MapReport) -> Bool {
|
||||
if lhs.longName != rhs.longName {return false}
|
||||
if lhs.shortName != rhs.shortName {return false}
|
||||
if lhs.role != rhs.role {return false}
|
||||
if lhs.hwModel != rhs.hwModel {return false}
|
||||
if lhs.firmwareVersion != rhs.firmwareVersion {return false}
|
||||
if lhs.region != rhs.region {return false}
|
||||
if lhs.modemPreset != rhs.modemPreset {return false}
|
||||
if lhs.hasDefaultChannel_p != rhs.hasDefaultChannel_p {return false}
|
||||
if lhs.latitudeI != rhs.latitudeI {return false}
|
||||
if lhs.longitudeI != rhs.longitudeI {return false}
|
||||
if lhs.altitude != rhs.altitude {return false}
|
||||
if lhs.positionPrecision != rhs.positionPrecision {return false}
|
||||
if lhs.numOnlineLocalNodes != rhs.numOnlineLocalNodes {return false}
|
||||
if lhs.unknownFields != rhs.unknownFields {return false}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,25 +58,25 @@ enum PortNum: SwiftProtobuf.Enum {
|
|||
|
||||
///
|
||||
/// The built-in position messaging app.
|
||||
/// Payload is a [Position](/docs/developers/protobufs/api#position) message
|
||||
/// Payload is a Position message.
|
||||
/// ENCODING: Protobuf
|
||||
case positionApp // = 3
|
||||
|
||||
///
|
||||
/// The built-in user info app.
|
||||
/// Payload is a [User](/docs/developers/protobufs/api#user) message
|
||||
/// Payload is a User message.
|
||||
/// ENCODING: Protobuf
|
||||
case nodeinfoApp // = 4
|
||||
|
||||
///
|
||||
/// Protocol control packets for mesh protocol use.
|
||||
/// Payload is a [Routing](/docs/developers/protobufs/api#routing) message
|
||||
/// Payload is a Routing message.
|
||||
/// ENCODING: Protobuf
|
||||
case routingApp // = 5
|
||||
|
||||
///
|
||||
/// Admin control packets.
|
||||
/// Payload is a [AdminMessage](/docs/developers/protobufs/api#adminmessage) message
|
||||
/// Payload is a AdminMessage message.
|
||||
/// ENCODING: Protobuf
|
||||
case adminApp // = 6
|
||||
|
||||
|
|
@ -90,7 +90,7 @@ enum PortNum: SwiftProtobuf.Enum {
|
|||
|
||||
///
|
||||
/// Waypoint payloads.
|
||||
/// Payload is a [Waypoint](/docs/developers/protobufs/api#waypoint) message
|
||||
/// Payload is a Waypoint message.
|
||||
/// ENCODING: Protobuf
|
||||
case waypointApp // = 8
|
||||
|
||||
|
|
@ -181,6 +181,10 @@ enum PortNum: SwiftProtobuf.Enum {
|
|||
/// Portnum for payloads from the official Meshtastic ATAK plugin
|
||||
case atakPlugin // = 72
|
||||
|
||||
///
|
||||
/// Provides unencrypted information about a node for consumption by a map via MQTT
|
||||
case mapReportApp // = 73
|
||||
|
||||
///
|
||||
/// Private applications should use portnums >= 256.
|
||||
/// To simplify initial development and testing you can use "PRIVATE_APP"
|
||||
|
|
@ -226,6 +230,7 @@ enum PortNum: SwiftProtobuf.Enum {
|
|||
case 70: self = .tracerouteApp
|
||||
case 71: self = .neighborinfoApp
|
||||
case 72: self = .atakPlugin
|
||||
case 73: self = .mapReportApp
|
||||
case 256: self = .privateApp
|
||||
case 257: self = .atakForwarder
|
||||
case 511: self = .max
|
||||
|
|
@ -258,6 +263,7 @@ enum PortNum: SwiftProtobuf.Enum {
|
|||
case .tracerouteApp: return 70
|
||||
case .neighborinfoApp: return 71
|
||||
case .atakPlugin: return 72
|
||||
case .mapReportApp: return 73
|
||||
case .privateApp: return 256
|
||||
case .atakForwarder: return 257
|
||||
case .max: return 511
|
||||
|
|
@ -295,6 +301,7 @@ extension PortNum: CaseIterable {
|
|||
.tracerouteApp,
|
||||
.neighborinfoApp,
|
||||
.atakPlugin,
|
||||
.mapReportApp,
|
||||
.privateApp,
|
||||
.atakForwarder,
|
||||
.max,
|
||||
|
|
@ -334,6 +341,7 @@ extension PortNum: SwiftProtobuf._ProtoNameProviding {
|
|||
70: .same(proto: "TRACEROUTE_APP"),
|
||||
71: .same(proto: "NEIGHBORINFO_APP"),
|
||||
72: .same(proto: "ATAK_PLUGIN"),
|
||||
73: .same(proto: "MAP_REPORT_APP"),
|
||||
256: .same(proto: "PRIVATE_APP"),
|
||||
257: .same(proto: "ATAK_FORWARDER"),
|
||||
511: .same(proto: "MAX"),
|
||||
|
|
|
|||
|
|
@ -84,6 +84,10 @@ enum TelemetrySensorType: SwiftProtobuf.Enum {
|
|||
///
|
||||
/// INA3221 3 Channel Voltage / Current Sensor
|
||||
case ina3221 // = 14
|
||||
|
||||
///
|
||||
/// BMP085/BMP180 High accuracy temperature and pressure (older Version of BMP280)
|
||||
case bmp085 // = 15
|
||||
case UNRECOGNIZED(Int)
|
||||
|
||||
init() {
|
||||
|
|
@ -107,6 +111,7 @@ enum TelemetrySensorType: SwiftProtobuf.Enum {
|
|||
case 12: self = .sht31
|
||||
case 13: self = .pmsa003I
|
||||
case 14: self = .ina3221
|
||||
case 15: self = .bmp085
|
||||
default: self = .UNRECOGNIZED(rawValue)
|
||||
}
|
||||
}
|
||||
|
|
@ -128,6 +133,7 @@ enum TelemetrySensorType: SwiftProtobuf.Enum {
|
|||
case .sht31: return 12
|
||||
case .pmsa003I: return 13
|
||||
case .ina3221: return 14
|
||||
case .bmp085: return 15
|
||||
case .UNRECOGNIZED(let i): return i
|
||||
}
|
||||
}
|
||||
|
|
@ -154,6 +160,7 @@ extension TelemetrySensorType: CaseIterable {
|
|||
.sht31,
|
||||
.pmsa003I,
|
||||
.ina3221,
|
||||
.bmp085,
|
||||
]
|
||||
}
|
||||
|
||||
|
|
@ -450,6 +457,7 @@ extension TelemetrySensorType: SwiftProtobuf._ProtoNameProviding {
|
|||
12: .same(proto: "SHT31"),
|
||||
13: .same(proto: "PMSA003I"),
|
||||
14: .same(proto: "INA3221"),
|
||||
15: .same(proto: "BMP085"),
|
||||
]
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -42,3 +42,20 @@ struct CreateChannelsTip: Tip {
|
|||
Image(systemName: "fibrechannel")
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 17.0, macOS 14.0, *)
|
||||
struct AdminChannelTip: Tip {
|
||||
|
||||
var id: String {
|
||||
return "tip.channel.admin"
|
||||
}
|
||||
var title: Text {
|
||||
Text("tip.channel.admin.title")
|
||||
}
|
||||
var message: Text? {
|
||||
Text("tip.channel.admin.message")
|
||||
}
|
||||
var image: Image? {
|
||||
Image(systemName: "fibrechannel")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 settings.", isPresented: $presentingSwitchPreferredPeripheral, titleVisibility: .visible) {
|
||||
.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) {
|
||||
|
||||
Button("Connect to new radio?", role: .destructive) {
|
||||
UserDefaults.preferredPeripheralId = selectedPeripherialId
|
||||
|
|
|
|||
|
|
@ -17,12 +17,19 @@ struct UserList: View {
|
|||
@Environment(\.managedObjectContext) var context
|
||||
@EnvironmentObject var bleManager: BLEManager
|
||||
@State private var searchText = ""
|
||||
|
||||
var usersQuery: Binding<String> {
|
||||
Binding {
|
||||
searchText
|
||||
} set: { newValue in
|
||||
searchText = newValue
|
||||
users.nsPredicate = newValue.isEmpty ? nil : NSPredicate(format: "longName CONTAINS[c] %@ OR shortName CONTAINS[c] %@", newValue, newValue)
|
||||
/// Case Insensitive Search Text Predicates
|
||||
let searchPredicates = ["userId", "hwModel", "longName", "shortName"].map { property in
|
||||
return NSPredicate(format: "%K CONTAINS[c] %@", property, searchText)
|
||||
}
|
||||
/// Create a compound predicate using each text search predicate as an OR
|
||||
let textSearchPredicate = NSCompoundPredicate(type: .or, subpredicates: searchPredicates)
|
||||
users.nsPredicate = newValue.isEmpty ? nil : textSearchPredicate
|
||||
}
|
||||
}
|
||||
@FetchRequest(
|
||||
|
|
@ -48,94 +55,94 @@ struct UserList: View {
|
|||
let lastMessageDay = Calendar.current.dateComponents([.day], from: lastMessageTime).day ?? 0
|
||||
let currentDay = Calendar.current.dateComponents([.day], from: Date()).day ?? 0
|
||||
if user.num != bleManager.connectedPeripheral?.num ?? 0 {
|
||||
NavigationLink(destination: UserMessageList(user: user)) {
|
||||
ZStack {
|
||||
Image(systemName: "circle.fill")
|
||||
.opacity(user.unreadMessages > 0 ? 1 : 0)
|
||||
.font(.system(size: 10))
|
||||
.foregroundColor(.accentColor)
|
||||
.brightness(0.2)
|
||||
}
|
||||
|
||||
CircleText(text: user.shortName ?? "?", color: Color(UIColor(hex: UInt32(user.num))))
|
||||
|
||||
VStack(alignment: .leading){
|
||||
HStack{
|
||||
Text(user.longName ?? "unknown".localized)
|
||||
.font(.headline)
|
||||
Spacer()
|
||||
if user.vip {
|
||||
Image(systemName: "star.fill")
|
||||
.foregroundColor(.yellow)
|
||||
NavigationLink(destination: UserMessageList(user: user)) {
|
||||
ZStack {
|
||||
Image(systemName: "circle.fill")
|
||||
.opacity(user.unreadMessages > 0 ? 1 : 0)
|
||||
.font(.system(size: 10))
|
||||
.foregroundColor(.accentColor)
|
||||
.brightness(0.2)
|
||||
}
|
||||
|
||||
CircleText(text: user.shortName ?? "?", color: Color(UIColor(hex: UInt32(user.num))))
|
||||
|
||||
VStack(alignment: .leading){
|
||||
HStack{
|
||||
Text(user.longName ?? "unknown".localized)
|
||||
.font(.headline)
|
||||
Spacer()
|
||||
if user.vip {
|
||||
Image(systemName: "star.fill")
|
||||
.foregroundColor(.yellow)
|
||||
}
|
||||
if user.messageList.count > 0 {
|
||||
if lastMessageDay == currentDay {
|
||||
Text(lastMessageTime, style: .time )
|
||||
.font(.footnote)
|
||||
.foregroundColor(.secondary)
|
||||
} else if lastMessageDay == (currentDay - 1) {
|
||||
Text("Yesterday")
|
||||
.font(.footnote)
|
||||
.foregroundColor(.secondary)
|
||||
} else if lastMessageDay < (currentDay - 1) && lastMessageDay > (currentDay - 5) {
|
||||
Text(lastMessageTime.formattedDate(format: dateFormatString))
|
||||
.font(.footnote)
|
||||
.foregroundColor(.secondary)
|
||||
} else if lastMessageDay < (currentDay - 1800) {
|
||||
Text(lastMessageTime.formattedDate(format: dateFormatString))
|
||||
.font(.footnote)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if user.messageList.count > 0 {
|
||||
if lastMessageDay == currentDay {
|
||||
Text(lastMessageTime, style: .time )
|
||||
.font(.footnote)
|
||||
.foregroundColor(.secondary)
|
||||
} else if lastMessageDay == (currentDay - 1) {
|
||||
Text("Yesterday")
|
||||
.font(.footnote)
|
||||
.foregroundColor(.secondary)
|
||||
} else if lastMessageDay < (currentDay - 1) && lastMessageDay > (currentDay - 5) {
|
||||
Text(lastMessageTime.formattedDate(format: dateFormatString))
|
||||
.font(.footnote)
|
||||
.foregroundColor(.secondary)
|
||||
} else if lastMessageDay < (currentDay - 1800) {
|
||||
Text(lastMessageTime.formattedDate(format: dateFormatString))
|
||||
HStack(alignment: .top) {
|
||||
Text("\(mostRecent != nil ? mostRecent!.messagePayload! : " ")")
|
||||
.font(.footnote)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if user.messageList.count > 0 {
|
||||
HStack(alignment: .top) {
|
||||
Text("\(mostRecent != nil ? mostRecent!.messagePayload! : " ")")
|
||||
.font(.footnote)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
.frame(height: 62)
|
||||
.contextMenu {
|
||||
Button {
|
||||
user.vip = !user.vip
|
||||
do {
|
||||
try context.save()
|
||||
} catch {
|
||||
context.rollback()
|
||||
print("💥 Save User VIP Error")
|
||||
}
|
||||
} label: {
|
||||
Label(user.vip ? "Un-Favorite" : "Favorite", systemImage: user.vip ? "star.slash.fill" : "star.fill")
|
||||
}
|
||||
Button {
|
||||
user.mute = !user.mute
|
||||
do {
|
||||
try context.save()
|
||||
} catch {
|
||||
context.rollback()
|
||||
print("💥 Save User Mute Error")
|
||||
}
|
||||
} label: {
|
||||
Label(user.mute ? "Show Alerts" : "Hide Alerts", systemImage: user.mute ? "bell" : "bell.slash")
|
||||
}
|
||||
if user.messageList.count > 0 {
|
||||
Button(role: .destructive) {
|
||||
isPresentingDeleteUserMessagesConfirm = true
|
||||
userSelection = user
|
||||
} label: {
|
||||
Label("Delete Messages", systemImage: "trash")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(height: 62)
|
||||
.contextMenu {
|
||||
Button {
|
||||
user.vip = !user.vip
|
||||
do {
|
||||
try context.save()
|
||||
} catch {
|
||||
context.rollback()
|
||||
print("💥 Save User VIP Error")
|
||||
}
|
||||
} label: {
|
||||
Label(user.vip ? "Un-Favorite" : "Favorite", systemImage: user.vip ? "star.slash.fill" : "star.fill")
|
||||
}
|
||||
Button {
|
||||
user.mute = !user.mute
|
||||
do {
|
||||
try context.save()
|
||||
} catch {
|
||||
context.rollback()
|
||||
print("💥 Save User Mute Error")
|
||||
}
|
||||
} label: {
|
||||
Label(user.mute ? "Show Alerts" : "Hide Alerts", systemImage: user.mute ? "bell" : "bell.slash")
|
||||
}
|
||||
if user.messageList.count > 0 {
|
||||
Button(role: .destructive) {
|
||||
isPresentingDeleteUserMessagesConfirm = true
|
||||
userSelection = user
|
||||
} label: {
|
||||
Label("Delete Messages", systemImage: "trash")
|
||||
}
|
||||
}
|
||||
}
|
||||
.confirmationDialog(
|
||||
"This conversation will be deleted.",
|
||||
isPresented: $isPresentingDeleteUserMessagesConfirm,
|
||||
titleVisibility: .visible
|
||||
) {
|
||||
.confirmationDialog(
|
||||
"This conversation will be deleted.",
|
||||
isPresented: $isPresentingDeleteUserMessagesConfirm,
|
||||
titleVisibility: .visible
|
||||
) {
|
||||
Button(role: .destructive) {
|
||||
deleteUserMessages(user: userSelection!, context: context)
|
||||
context.refresh(node!.user!, mergeChanges: true)
|
||||
|
|
@ -149,7 +156,9 @@ struct UserList: View {
|
|||
}
|
||||
.listStyle(.plain)
|
||||
.navigationTitle(String.localizedStringWithFormat("contacts %@".localized, String(users.count == 0 ? 0 : users.count - 1)))
|
||||
.searchable(text: usersQuery, prompt: "Find a contact")
|
||||
.searchable(text: usersQuery, placement: users.count > 10 ? .navigationBarDrawer(displayMode: .always) : .automatic, prompt: "Find a contact")
|
||||
.disableAutocorrection(true)
|
||||
.scrollDismissesKeyboard(.immediately)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ struct UserMessageList: View {
|
|||
if message.realACK {
|
||||
Text("\(ackErrorVal?.display ?? "Empty Ack Error")").font(.caption2).foregroundColor(.gray)
|
||||
} else {
|
||||
Text("Implicit ACK from another node").font(.caption2).foregroundColor(.orange)
|
||||
Text("Acknowledged by another node").font(.caption2).foregroundColor(.orange)
|
||||
}
|
||||
} else if currentUser && message.ackError == 0 {
|
||||
// Empty Error
|
||||
|
|
|
|||
|
|
@ -43,13 +43,14 @@ struct DeviceMetricsLog: View {
|
|||
.accessibilityLabel("Line Series")
|
||||
.accessibilityValue("X: \(point.time!), Y: \(point.batteryLevel)")
|
||||
.foregroundStyle(batteryChartColor)
|
||||
.interpolationMethod(.cardinal)
|
||||
.interpolationMethod(.linear)
|
||||
|
||||
Plot {
|
||||
PointMark(
|
||||
x: .value("x", point.time!),
|
||||
y: .value("y", point.channelUtilization)
|
||||
)
|
||||
.symbolSize(25)
|
||||
}
|
||||
.accessibilityLabel("Line Series")
|
||||
.accessibilityValue("X: \(point.time!), Y: \(point.channelUtilization)")
|
||||
|
|
@ -64,6 +65,7 @@ struct DeviceMetricsLog: View {
|
|||
x: .value("x", point.time!),
|
||||
y: .value("y", point.airUtilTx)
|
||||
)
|
||||
.symbolSize(25)
|
||||
}
|
||||
.accessibilityLabel("Line Series")
|
||||
.accessibilityValue("X: \(point.time!), Y: \(point.airUtilTx)")
|
||||
|
|
|
|||
|
|
@ -0,0 +1,209 @@
|
|||
//
|
||||
// MeshMapContent.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Created by Garth Vander Houwen on 3/17/24.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import MapKit
|
||||
|
||||
@available(iOS 17.0, macOS 14.0, *)
|
||||
struct MeshMapContent: MapContent {
|
||||
|
||||
/// Parameters
|
||||
@Binding var showUserLocation: Bool
|
||||
@AppStorage("meshMapShowNodeHistory") private var showNodeHistory = false
|
||||
@AppStorage("meshMapShowRouteLines") private var showRouteLines = false
|
||||
@AppStorage("enableMapConvexHull") private var showConvexHull = false
|
||||
@Binding var showTraffic: Bool
|
||||
@Binding var showPointsOfInterest: Bool
|
||||
@Binding var selectedMapLayer: MapLayer
|
||||
// Map Configuration
|
||||
@Binding var selectedPosition: PositionEntity?
|
||||
@AppStorage("enableMapWaypoints") private var showWaypoints = false
|
||||
@Binding var selectedWaypoint: WaypointEntity?
|
||||
|
||||
@FetchRequest(fetchRequest: PositionEntity.allPositionsFetchRequest(), animation: .easeIn)
|
||||
var positions: FetchedResults<PositionEntity>
|
||||
|
||||
@FetchRequest(fetchRequest: WaypointEntity.allWaypointssFetchRequest(), animation: .none)
|
||||
var waypoints: FetchedResults<WaypointEntity>
|
||||
|
||||
@FetchRequest(sortDescriptors: [NSSortDescriptor(key: "name", ascending: true)],
|
||||
predicate: NSPredicate(format: "enabled == true", ""), animation: .none)
|
||||
private var routes: FetchedResults<RouteEntity>
|
||||
|
||||
var delay: Double = 0
|
||||
@State private var scale: CGFloat = 0.5
|
||||
|
||||
@MapContentBuilder
|
||||
var meshMap: some MapContent {
|
||||
let lineCoords = Array(positions).compactMap({(position) -> CLLocationCoordinate2D in
|
||||
return position.nodeCoordinate ?? LocationsHandler.DefaultLocation
|
||||
})
|
||||
/// Convex Hull
|
||||
if showConvexHull {
|
||||
if lineCoords.count > 0 {
|
||||
let hull = lineCoords.getConvexHull()
|
||||
MapPolygon(coordinates: hull)
|
||||
.stroke(.blue, lineWidth: 3)
|
||||
.foregroundStyle(.indigo.opacity(0.4))
|
||||
}
|
||||
}
|
||||
/// Position Annotations
|
||||
ForEach(Array(positions), id: \.id) { position in
|
||||
/// Node color from node.num
|
||||
let nodeColor = UIColor(hex: UInt32(position.nodePosition?.num ?? 0))
|
||||
/// Latest Position Anotations
|
||||
Annotation(position.nodePosition?.user?.longName ?? "?", coordinate: position.coordinate) {
|
||||
LazyVStack {
|
||||
ZStack {
|
||||
let nodeColor = UIColor(hex: UInt32(position.nodePosition?.num ?? 0))
|
||||
if position.nodePosition?.isOnline ?? false {
|
||||
Circle()
|
||||
.fill(Color(nodeColor.lighter()).opacity(0.4).shadow(.drop(color: Color(nodeColor).isLight() ? .black : .white, radius: 5)))
|
||||
.foregroundStyle(Color(nodeColor.lighter()).opacity(0.3))
|
||||
.scaleEffect(scale)
|
||||
.animation(
|
||||
Animation.easeInOut(duration: 0.6)
|
||||
.repeatForever().delay(delay), value: scale
|
||||
)
|
||||
.onAppear {
|
||||
self.scale = 1
|
||||
}
|
||||
.frame(width: 60, height: 60)
|
||||
}
|
||||
if position.nodePosition?.hasDetectionSensorMetrics ?? false {
|
||||
Image(systemName: "sensor.fill")
|
||||
.symbolRenderingMode(.palette)
|
||||
.symbolEffect(.variableColor)
|
||||
.padding()
|
||||
.foregroundStyle(.white)
|
||||
.background(Color(nodeColor))
|
||||
.clipShape(Circle())
|
||||
} else {
|
||||
CircleText(text: position.nodePosition?.user?.shortName ?? "?", color: Color(nodeColor), circleSize: 40)
|
||||
}
|
||||
}
|
||||
}
|
||||
.onTapGesture { location in
|
||||
selectedPosition = (selectedPosition == position ? nil : position)
|
||||
}
|
||||
}
|
||||
|
||||
/// Node History and Route Lines for favorites
|
||||
if position.nodePosition?.user?.vip ?? false {
|
||||
if showRouteLines {
|
||||
let nodePositions = Array(position.nodePosition!.positions!) as! [PositionEntity]
|
||||
let routeCoords = nodePositions.compactMap({(pos) -> CLLocationCoordinate2D in
|
||||
return pos.nodeCoordinate ?? LocationHelper.DefaultLocation
|
||||
})
|
||||
let gradient = LinearGradient(
|
||||
colors: [Color(nodeColor.lighter().lighter()), Color(nodeColor.lighter()), Color(nodeColor)],
|
||||
startPoint: .leading, endPoint: .trailing
|
||||
)
|
||||
let dashed = StrokeStyle(
|
||||
lineWidth: 3,
|
||||
lineCap: .round, lineJoin: .round, dash: [10, 10]
|
||||
)
|
||||
MapPolyline(coordinates: routeCoords)
|
||||
.stroke(gradient, style: dashed)
|
||||
}
|
||||
if showNodeHistory {
|
||||
ForEach(Array(position.nodePosition!.positions!) as! [PositionEntity], id: \.self) { (mappin: PositionEntity) in
|
||||
if mappin.latest == false && mappin.nodePosition?.user?.vip ?? false {
|
||||
let pf = PositionFlags(rawValue: Int(mappin.nodePosition?.metadata?.positionFlags ?? 771))
|
||||
let headingDegrees = Angle.degrees(Double(mappin.heading))
|
||||
Annotation("", coordinate: mappin.coordinate) {
|
||||
LazyVStack {
|
||||
if pf.contains(.Heading) {
|
||||
Image(systemName: "location.north.circle")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.foregroundStyle(Color(UIColor(hex: UInt32(mappin.nodePosition?.num ?? 0))).isLight() ? .black : .white)
|
||||
.background(Color(UIColor(hex: UInt32(mappin.nodePosition?.num ?? 0))))
|
||||
.clipShape(Circle())
|
||||
.rotationEffect(headingDegrees)
|
||||
.frame(width: 16, height: 16)
|
||||
|
||||
} else {
|
||||
Circle()
|
||||
.fill(Color(UIColor(hex: UInt32(mappin.nodePosition?.num ?? 0))))
|
||||
.strokeBorder(Color(UIColor(hex: UInt32(mappin.nodePosition?.num ?? 0))).isLight() ? .black : .white ,lineWidth: 2)
|
||||
.frame(width: 12, height: 12)
|
||||
}
|
||||
}
|
||||
}
|
||||
.annotationTitles(.hidden)
|
||||
.annotationSubtitles(.hidden)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Reduced Precision Map Circles
|
||||
if 11...16 ~= position.precisionBits {
|
||||
let pp = PositionPrecision(rawValue: Int(position.precisionBits))
|
||||
let radius : CLLocationDistance = pp?.precisionMeters ?? 0
|
||||
if radius > 0.0 {
|
||||
MapCircle(center: position.coordinate, radius: radius)
|
||||
.foregroundStyle(Color(nodeColor).opacity(0.25))
|
||||
.stroke(.white, lineWidth: 2)
|
||||
}
|
||||
}
|
||||
/// Routes
|
||||
ForEach(Array(routes)) { route in
|
||||
let routeLocations = Array(route.locations!) as! [LocationEntity]
|
||||
let routeCoords = routeLocations.compactMap({(loc) -> CLLocationCoordinate2D in
|
||||
return loc.locationCoordinate ?? LocationHelper.DefaultLocation
|
||||
})
|
||||
Annotation("Start", coordinate: routeCoords.first ?? LocationHelper.DefaultLocation) {
|
||||
ZStack {
|
||||
Circle()
|
||||
.fill(Color(.green))
|
||||
.strokeBorder(.white, lineWidth: 3)
|
||||
.frame(width: 15, height: 15)
|
||||
}
|
||||
}
|
||||
.annotationTitles(.automatic)
|
||||
Annotation("Finish", coordinate: routeCoords.last ?? LocationHelper.DefaultLocation) {
|
||||
ZStack {
|
||||
Circle()
|
||||
.fill(Color(.black))
|
||||
.strokeBorder(.white, lineWidth: 3)
|
||||
.frame(width: 15, height: 15)
|
||||
}
|
||||
}
|
||||
.annotationTitles(.automatic)
|
||||
let solid = StrokeStyle(
|
||||
lineWidth: 3,
|
||||
lineCap: .round, lineJoin: .round
|
||||
)
|
||||
MapPolyline(coordinates: routeCoords)
|
||||
.stroke(Color(UIColor(hex: UInt32(route.color))), style: solid)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// Waypoint Annotations
|
||||
if waypoints.count > 0 && showWaypoints {
|
||||
ForEach(Array(waypoints) as! [WaypointEntity], id: \.self) { waypoint in
|
||||
Annotation(waypoint.name ?? "?", coordinate: waypoint.coordinate) {
|
||||
LazyVStack {
|
||||
ZStack {
|
||||
CircleText(text: String(UnicodeScalar(Int(waypoint.icon)) ?? "📍"), color: Color.orange, circleSize: 40)
|
||||
.onTapGesture(perform: { location in
|
||||
selectedWaypoint = (selectedWaypoint == waypoint ? nil : waypoint)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@MapContentBuilder
|
||||
var body: some MapContent {
|
||||
meshMap
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,173 @@
|
|||
//
|
||||
// RouteLines.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Created by Garth Vander Houwen on 3/14/24.
|
||||
//
|
||||
import SwiftUI
|
||||
import MapKit
|
||||
import CoreData
|
||||
|
||||
@available(iOS 17.0, macOS 14.0, *)
|
||||
struct NodeMapContent: MapContent {
|
||||
|
||||
@ObservedObject var node: NodeInfoEntity
|
||||
@State var showUserLocation: Bool = false
|
||||
@State var positions: [PositionEntity] = []
|
||||
/// Map State User Defaults
|
||||
@AppStorage("meshMapShowNodeHistory") private var showNodeHistory = false
|
||||
@AppStorage("meshMapShowRouteLines") private var showRouteLines = false
|
||||
@AppStorage("enableMapWaypoints") private var showWaypoints = false
|
||||
@AppStorage("enableMapConvexHull") private var showConvexHull = false
|
||||
@AppStorage("enableMapTraffic") private var showTraffic: Bool = false
|
||||
@AppStorage("enableMapPointsOfInterest") private var showPointsOfInterest: Bool = false
|
||||
@AppStorage("mapLayer") private var selectedMapLayer: MapLayer = .hybrid
|
||||
|
||||
// Map Configuration
|
||||
@Namespace var mapScope
|
||||
@State var mapStyle: MapStyle = MapStyle.hybrid(elevation: .realistic, pointsOfInterest: .all, showsTraffic: true)
|
||||
@State var position = MapCameraPosition.automatic
|
||||
@State var scene: MKLookAroundScene?
|
||||
@State var isLookingAround = false
|
||||
@State var isShowingAltitude = false
|
||||
@State var isEditingSettings = false
|
||||
@State var selectedPosition: PositionEntity?
|
||||
@State var isMeshMap = false
|
||||
|
||||
@MapContentBuilder
|
||||
var nodeMap: some MapContent {
|
||||
let positionArray = node.positions?.array as? [PositionEntity] ?? []
|
||||
let lineCoords = positionArray.compactMap({(position) -> CLLocationCoordinate2D in
|
||||
return position.nodeCoordinate ?? LocationsHandler.DefaultLocation
|
||||
})
|
||||
/// Node Color from node.num
|
||||
let nodeColor = UIColor(hex: UInt32(node.num))
|
||||
|
||||
/// Node Annotations
|
||||
ForEach(node.positions?.array as? [PositionEntity] ?? [], id: \.id) { position in
|
||||
|
||||
let pf = PositionFlags(rawValue: Int(position.nodePosition?.metadata?.positionFlags ?? 771))
|
||||
let headingDegrees = Angle.degrees(Double(position.heading))
|
||||
/// Reduced Precision Map Circle
|
||||
if position.latest && 11...16 ~= position.precisionBits {
|
||||
let pp = PositionPrecision(rawValue: Int(position.precisionBits))
|
||||
let radius : CLLocationDistance = pp?.precisionMeters ?? 0
|
||||
if radius > 0.0 {
|
||||
MapCircle(center: position.coordinate, radius: radius)
|
||||
.foregroundStyle(Color(nodeColor).opacity(0.25))
|
||||
.stroke(.white, lineWidth: 2)
|
||||
}
|
||||
}
|
||||
/// Convex Hull
|
||||
if showConvexHull {
|
||||
if lineCoords.count > 0 {
|
||||
let hull = lineCoords.getConvexHull()
|
||||
MapPolygon(coordinates: hull)
|
||||
.stroke(Color(nodeColor.darker()), lineWidth: 3)
|
||||
.foregroundStyle(Color(nodeColor).opacity(0.4))
|
||||
}
|
||||
}
|
||||
/// Route Lines
|
||||
if showRouteLines {
|
||||
let gradient = LinearGradient(
|
||||
colors: [Color(nodeColor.lighter().lighter().lighter()), Color(nodeColor.lighter()), Color(nodeColor)],
|
||||
startPoint: .leading, endPoint: .trailing
|
||||
)
|
||||
let dashed = StrokeStyle(
|
||||
lineWidth: 3,
|
||||
lineCap: .round, lineJoin: .round, dash: [10, 10]
|
||||
)
|
||||
MapPolyline(coordinates: lineCoords)
|
||||
.stroke(gradient, style: dashed)
|
||||
}
|
||||
/// Lastest Position Pin
|
||||
if position.latest {
|
||||
/// Node Annotations
|
||||
Annotation(position.latest ? node.user?.shortName ?? "?": "", coordinate: position.coordinate) {
|
||||
LazyVStack {
|
||||
ZStack {
|
||||
Circle()
|
||||
.fill(Color(nodeColor.lighter()).opacity(0.4).shadow(.drop(color: Color(nodeColor).isLight() ? .black : .white, radius: 5)))
|
||||
.foregroundStyle(Color(nodeColor.lighter()).opacity(0.3))
|
||||
.frame(width: 50, height: 50)
|
||||
if pf.contains(.Heading) {
|
||||
Image(systemName: pf.contains(.Speed) && position.speed > 1 ? "location.north" : "octagon")
|
||||
.symbolEffect(.pulse.byLayer)
|
||||
.padding(5)
|
||||
.foregroundStyle(Color(nodeColor).isLight() ? .black : .white)
|
||||
.background(Color(nodeColor.darker()))
|
||||
.clipShape(Circle())
|
||||
.rotationEffect(headingDegrees)
|
||||
.onTapGesture {
|
||||
selectedPosition = (selectedPosition == position ? nil : position)
|
||||
}
|
||||
.popover(item: $selectedPosition) { selection in
|
||||
PositionPopover(position: selection)
|
||||
.padding()
|
||||
.opacity(0.8)
|
||||
.presentationCompactAdaptation(.popover)
|
||||
}
|
||||
|
||||
} else {
|
||||
Image(systemName: "flipphone")
|
||||
.symbolEffect(.pulse.byLayer)
|
||||
.padding(5)
|
||||
.foregroundStyle(Color(nodeColor).isLight() ? .black : .white)
|
||||
.background(Color(UIColor(hex: UInt32(node.num)).darker()))
|
||||
.clipShape(Circle())
|
||||
.onTapGesture {
|
||||
selectedPosition = (selectedPosition == position ? nil : position)
|
||||
}
|
||||
.popover(item: $selectedPosition) { selection in
|
||||
PositionPopover(position: selection)
|
||||
.padding()
|
||||
.opacity(0.8)
|
||||
.presentationCompactAdaptation(.popover)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// .tag(position.time)
|
||||
.annotationTitles(.automatic)
|
||||
.annotationSubtitles(.automatic)
|
||||
}
|
||||
/// Node History
|
||||
if showNodeHistory {
|
||||
if position.latest == false && position.nodePosition?.user?.vip ?? false {
|
||||
let pf = PositionFlags(rawValue: Int(position.nodePosition?.metadata?.positionFlags ?? 771))
|
||||
let headingDegrees = Angle.degrees(Double(position.heading))
|
||||
Annotation("", coordinate: position.coordinate) {
|
||||
LazyVStack {
|
||||
if pf.contains(.Heading) {
|
||||
Image(systemName: "location.north.circle")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.foregroundStyle(Color(UIColor(hex: UInt32(position.nodePosition?.num ?? 0))).isLight() ? .black : .white)
|
||||
.background(Color(UIColor(hex: UInt32(position.nodePosition?.num ?? 0))))
|
||||
.clipShape(Circle())
|
||||
.rotationEffect(headingDegrees)
|
||||
.frame(width: 16, height: 16)
|
||||
|
||||
} else {
|
||||
Circle()
|
||||
.fill(Color(UIColor(hex: UInt32(position.nodePosition?.num ?? 0))))
|
||||
.strokeBorder(Color(UIColor(hex: UInt32(position.nodePosition?.num ?? 0))).isLight() ? .black : .white ,lineWidth: 2)
|
||||
.frame(width: 12, height: 12)
|
||||
}
|
||||
}
|
||||
}
|
||||
.annotationTitles(.hidden)
|
||||
.annotationSubtitles(.hidden)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@MapContentBuilder
|
||||
var body: some MapContent {
|
||||
if node.positions?.count ?? 0 > 0 {
|
||||
nodeMap
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -13,12 +13,14 @@ import MapKit
|
|||
@available(iOS 17.0, macOS 14.0, *)
|
||||
struct MapSettingsForm: View {
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@Binding var nodeHistory: Bool
|
||||
@Binding var routeLines: Bool
|
||||
@Binding var convexHull: Bool
|
||||
@AppStorage("meshMapShowNodeHistory") private var nodeHistory = false
|
||||
@AppStorage("meshMapShowRouteLines") private var routeLines = false
|
||||
@AppStorage("enableMapConvexHull") private var convexHull = false
|
||||
@AppStorage("enableMapWaypoints") private var waypoints = false
|
||||
@Binding var traffic: Bool
|
||||
@Binding var pointsOfInterest: Bool
|
||||
@Binding var mapLayer: MapLayer
|
||||
@AppStorage("meshMapDistance") private var meshMapDistance: Double = 800000
|
||||
@Binding var meshMap: Bool
|
||||
|
||||
var body: some View {
|
||||
|
|
@ -39,24 +41,46 @@ struct MapSettingsForm: View {
|
|||
.onChange(of: mapLayer) { newMapLayer in
|
||||
UserDefaults.mapLayer = newMapLayer
|
||||
}
|
||||
if !meshMap {
|
||||
Toggle(isOn: $nodeHistory) {
|
||||
Label("Node History", systemImage: "building.columns.fill")
|
||||
if meshMap {
|
||||
HStack {
|
||||
Label("Show nodes", systemImage: "lines.measurement.horizontal")
|
||||
Picker("", selection: $meshMapDistance) {
|
||||
ForEach(MeshMapDistances.allCases) { di in
|
||||
Text(di.description)
|
||||
.tag(di.id)
|
||||
}
|
||||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
}
|
||||
.onChange(of: meshMapDistance) { newMeshMapDistance in
|
||||
UserDefaults.meshMapDistance = newMeshMapDistance
|
||||
}
|
||||
Toggle(isOn: $waypoints) {
|
||||
Label("Show Waypoints ", systemImage: "signpost.right.and.left")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
.onTapGesture {
|
||||
self.nodeHistory.toggle()
|
||||
UserDefaults.enableMapNodeHistoryPins = self.nodeHistory
|
||||
}
|
||||
Toggle(isOn: $routeLines) {
|
||||
Label("Route Lines", systemImage: "road.lanes")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
.onTapGesture {
|
||||
self.routeLines.toggle()
|
||||
UserDefaults.enableMapRouteLines = self.routeLines
|
||||
UserDefaults.enableMapWaypoints = !waypoints
|
||||
}
|
||||
}
|
||||
|
||||
Toggle(isOn: $nodeHistory) {
|
||||
Label("Node History", systemImage: "building.columns.fill")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
.onTapGesture {
|
||||
self.nodeHistory.toggle()
|
||||
UserDefaults.enableMapNodeHistoryPins = self.nodeHistory
|
||||
}
|
||||
Toggle(isOn: $routeLines) {
|
||||
Label("Route Lines", systemImage: "road.lanes")
|
||||
}
|
||||
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
.onTapGesture {
|
||||
self.routeLines.toggle()
|
||||
UserDefaults.enableMapRouteLines = self.routeLines
|
||||
}
|
||||
Toggle(isOn: $convexHull) {
|
||||
Label("Convex Hull", systemImage: "button.angledbottom.horizontal.right")
|
||||
}
|
||||
|
|
@ -97,7 +121,7 @@ Spacer()
|
|||
.padding(.bottom)
|
||||
#endif
|
||||
}
|
||||
.presentationDetents([.fraction(0.45), .fraction(0.65)])
|
||||
.presentationDetents([.fraction(meshMap ? 0.55 : 0.45), .fraction(0.65)])
|
||||
.presentationDragIndicator(.visible)
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import CoreLocation
|
|||
#if canImport(MapKit)
|
||||
import MapKit
|
||||
#endif
|
||||
import WeatherKit
|
||||
|
||||
@available(iOS 17.0, macOS 14.0, *)
|
||||
struct NodeMapSwiftUI: View {
|
||||
|
|
@ -21,25 +20,21 @@ struct NodeMapSwiftUI: View {
|
|||
@State var showUserLocation: Bool = false
|
||||
@State var positions: [PositionEntity] = []
|
||||
/// Map State User Defaults
|
||||
@AppStorage("meshMapShowNodeHistory") private var showNodeHistory = false
|
||||
@AppStorage("meshMapShowRouteLines") private var showRouteLines = false
|
||||
@AppStorage("enableMapConvexHull") private var showConvexHull = false
|
||||
@AppStorage("enableMapTraffic") private var showTraffic: Bool = false
|
||||
@AppStorage("enableMapPointsOfInterest") private var showPointsOfInterest: Bool = false
|
||||
@AppStorage("mapLayer") private var selectedMapLayer: MapLayer = .hybrid
|
||||
// Map Configuration
|
||||
@Namespace var mapScope
|
||||
@State var mapStyle: MapStyle = MapStyle.hybrid(elevation: .realistic, pointsOfInterest: .all, showsTraffic: true)
|
||||
@State var mapStyle: MapStyle = MapStyle.hybrid(elevation: .flat, pointsOfInterest: .all, showsTraffic: true)
|
||||
@State var position = MapCameraPosition.automatic
|
||||
@State var scene: MKLookAroundScene?
|
||||
@State var isLookingAround = false
|
||||
@State var isShowingAltitude = false
|
||||
@State var isEditingSettings = false
|
||||
@State var selectedPosition: PositionEntity?
|
||||
@State var showWaypoints = false
|
||||
@State var selectedWaypoint: WaypointEntity?
|
||||
@State var isMeshMap = false
|
||||
|
||||
@State private var mapRegion = MKCoordinateRegion.init()
|
||||
|
||||
@FetchRequest(sortDescriptors: [NSSortDescriptor(key: "name", ascending: false)],
|
||||
predicate: NSPredicate(
|
||||
format: "expire == nil || expire >= %@", Date() as NSDate
|
||||
|
|
@ -47,139 +42,13 @@ struct NodeMapSwiftUI: View {
|
|||
private var waypoints: FetchedResults<WaypointEntity>
|
||||
|
||||
var body: some View {
|
||||
|
||||
let positionArray = node.positions?.array as? [PositionEntity] ?? []
|
||||
var mostRecent = node.positions?.lastObject as? PositionEntity
|
||||
let lineCoords = positionArray.compactMap({(position) -> CLLocationCoordinate2D in
|
||||
return position.nodeCoordinate ?? LocationsHandler.DefaultLocation
|
||||
})
|
||||
|
||||
if node.hasPositions {
|
||||
ZStack {
|
||||
MapReader { reader in
|
||||
Map(position: $position, bounds: MapCameraBounds(minimumDistance: 1, maximumDistance: .infinity), scope: mapScope) {
|
||||
/// Node Color from node.num
|
||||
let nodeColor = UIColor(hex: UInt32(node.num))
|
||||
/// Route Lines
|
||||
if showRouteLines {
|
||||
let gradient = LinearGradient(
|
||||
colors: [Color(nodeColor.lighter().lighter().lighter()), Color(nodeColor.lighter()), Color(nodeColor)],
|
||||
startPoint: .leading, endPoint: .trailing
|
||||
)
|
||||
let dashed = StrokeStyle(
|
||||
lineWidth: 3,
|
||||
lineCap: .round, lineJoin: .round, dash: [10, 10]
|
||||
)
|
||||
MapPolyline(coordinates: lineCoords)
|
||||
.stroke(gradient, style: dashed)
|
||||
}
|
||||
/// Convex Hull
|
||||
if showConvexHull {
|
||||
if lineCoords.count > 0 {
|
||||
let hull = lineCoords.getConvexHull()
|
||||
MapPolygon(coordinates: hull)
|
||||
.stroke(Color(nodeColor.darker()), lineWidth: 3)
|
||||
.foregroundStyle(Color(nodeColor).opacity(0.4))
|
||||
}
|
||||
}
|
||||
|
||||
/// Waypoint Annotations
|
||||
if waypoints.count > 0 && showWaypoints {
|
||||
ForEach(Array(waypoints), id: \.id) { waypoint in
|
||||
Annotation(waypoint.name ?? "?", coordinate: waypoint.coordinate) {
|
||||
LazyVStack {
|
||||
CircleText(text: String(UnicodeScalar(Int(waypoint.icon)) ?? "📍"), color: Color.orange, circleSize: 35)
|
||||
.onTapGesture(coordinateSpace: .named("nodemap")) { location in
|
||||
selectedWaypoint = (selectedWaypoint == waypoint ? nil : waypoint)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Node Annotations
|
||||
ForEach(positionArray, id: \.id) { position in
|
||||
let pf = PositionFlags(rawValue: Int(position.nodePosition?.metadata?.positionFlags ?? 771))
|
||||
let headingDegrees = Angle.degrees(Double(position.heading))
|
||||
/// Reduced Precision Map Circle
|
||||
if position.latest && 11...16 ~= position.precisionBits {
|
||||
let pp = PositionPrecision(rawValue: Int(position.precisionBits))
|
||||
let radius : CLLocationDistance = pp?.precisionMeters ?? 0
|
||||
if radius > 0.0 {
|
||||
MapCircle(center: position.coordinate, radius: radius)
|
||||
.foregroundStyle(Color(nodeColor).opacity(0.25))
|
||||
}
|
||||
}
|
||||
Annotation(position.latest ? node.user?.shortName ?? "?": "", coordinate: position.coordinate) {
|
||||
LazyVStack {
|
||||
if position.latest {
|
||||
ZStack {
|
||||
Circle()
|
||||
.fill(Color(nodeColor.lighter()).opacity(0.4).shadow(.drop(color: Color(nodeColor).isLight() ? .black : .white, radius: 5)))
|
||||
.foregroundStyle(Color(nodeColor.lighter()).opacity(0.3))
|
||||
.frame(width: 50, height: 50)
|
||||
if pf.contains(.Heading) {
|
||||
Image(systemName: pf.contains(.Speed) && position.speed > 1 ? "location.north" : "octagon")
|
||||
.symbolEffect(.pulse.byLayer)
|
||||
.padding(5)
|
||||
.foregroundStyle(Color(nodeColor).isLight() ? .black : .white)
|
||||
.background(Color(nodeColor.darker()))
|
||||
.clipShape(Circle())
|
||||
.rotationEffect(headingDegrees)
|
||||
.onTapGesture {
|
||||
selectedPosition = (selectedPosition == position ? nil : position)
|
||||
}
|
||||
.popover(item: $selectedPosition) { selection in
|
||||
PositionPopover(position: selection)
|
||||
.padding()
|
||||
.opacity(0.8)
|
||||
.presentationCompactAdaptation(.popover)
|
||||
}
|
||||
|
||||
} else {
|
||||
Image(systemName: "flipphone")
|
||||
.symbolEffect(.pulse.byLayer)
|
||||
.padding(5)
|
||||
.foregroundStyle(Color(nodeColor).isLight() ? .black : .white)
|
||||
.background(Color(UIColor(hex: UInt32(node.num)).darker()))
|
||||
.clipShape(Circle())
|
||||
.onTapGesture {
|
||||
selectedPosition = (selectedPosition == position ? nil : position)
|
||||
}
|
||||
.popover(item: $selectedPosition) { selection in
|
||||
PositionPopover(position: selection)
|
||||
.padding()
|
||||
.opacity(0.8)
|
||||
.presentationCompactAdaptation(.popover)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if showNodeHistory {
|
||||
if pf.contains(.Heading) {
|
||||
Image(systemName: "location.north.circle")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.foregroundStyle(Color(UIColor(hex: UInt32(node.num))).isLight() ? .black : .white)
|
||||
.background(Color(UIColor(hex: UInt32(node.num))))
|
||||
.clipShape(Circle())
|
||||
.rotationEffect(headingDegrees)
|
||||
.frame(width: 16, height: 16)
|
||||
|
||||
} else {
|
||||
Circle()
|
||||
.fill(Color(UIColor(hex: UInt32(node.num))))
|
||||
.strokeBorder(Color(UIColor(hex: UInt32(node.num))).isLight() ? .black : .white ,lineWidth: 2)
|
||||
.frame(width: 12, height: 12)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.tag(position.time)
|
||||
.annotationTitles(.automatic)
|
||||
.annotationSubtitles(.automatic)
|
||||
}
|
||||
NodeMapContent(node: node)
|
||||
}
|
||||
.mapScope(mapScope)
|
||||
.mapStyle(mapStyle)
|
||||
|
|
@ -212,12 +81,8 @@ struct NodeMapSwiftUI: View {
|
|||
.padding(.horizontal, 20)
|
||||
}
|
||||
}
|
||||
.sheet(item: $selectedWaypoint) { selection in
|
||||
WaypointForm(waypoint: selection)
|
||||
.padding()
|
||||
}
|
||||
.sheet(isPresented: $isEditingSettings) {
|
||||
MapSettingsForm(nodeHistory: $showNodeHistory, routeLines: $showRouteLines, convexHull: $showConvexHull, traffic: $showTraffic, pointsOfInterest: $showPointsOfInterest, mapLayer: $selectedMapLayer, meshMap: $isMeshMap)
|
||||
MapSettingsForm(traffic: $showTraffic, pointsOfInterest: $showPointsOfInterest, mapLayer: $selectedMapLayer, meshMap: $isMeshMap)
|
||||
.onChange(of: (selectedMapLayer)) { newMapLayer in
|
||||
switch selectedMapLayer {
|
||||
case .standard:
|
||||
|
|
@ -241,7 +106,7 @@ struct NodeMapSwiftUI: View {
|
|||
if node.positions?.count ?? 0 > 1 {
|
||||
position = .automatic
|
||||
} else {
|
||||
position = .camera(MapCamera(centerCoordinate: mostRecent!.coordinate, distance: 150, heading: 0, pitch: 60))
|
||||
position = .camera(MapCamera(centerCoordinate: mostRecent!.coordinate, distance: 8000, heading: 0, pitch: 60))
|
||||
}
|
||||
if let mostRecent {
|
||||
Task {
|
||||
|
|
@ -265,7 +130,7 @@ struct NodeMapSwiftUI: View {
|
|||
if node.positions?.count ?? 0 > 1 {
|
||||
position = .automatic
|
||||
} else {
|
||||
position = .camera(MapCamera(centerCoordinate: mostRecent!.coordinate, distance: 5000, heading: 0, pitch: 60))
|
||||
position = .camera(MapCamera(centerCoordinate: mostRecent!.coordinate, distance: 8000, heading: 0, pitch: 60))
|
||||
}
|
||||
if self.scene == nil {
|
||||
Task {
|
||||
|
|
@ -286,21 +151,6 @@ struct NodeMapSwiftUI: View {
|
|||
.tint(Color(UIColor.secondarySystemBackground))
|
||||
.foregroundColor(.accentColor)
|
||||
.buttonStyle(.borderedProminent)
|
||||
/// Show / Hide Waypoints Button
|
||||
if waypoints.count > 0 {
|
||||
|
||||
Button(action: {
|
||||
withAnimation {
|
||||
showWaypoints = !showWaypoints
|
||||
}
|
||||
}) {
|
||||
Image(systemName: showWaypoints ? "signpost.right.and.left.fill" : "signpost.right.and.left")
|
||||
.padding(.vertical, 5)
|
||||
}
|
||||
.tint(Color(UIColor.secondarySystemBackground))
|
||||
.foregroundColor(.accentColor)
|
||||
.buttonStyle(.borderedProminent)
|
||||
}
|
||||
/// Look Around Button
|
||||
if self.scene != nil {
|
||||
Button(action: {
|
||||
|
|
|
|||
|
|
@ -130,6 +130,19 @@ struct PositionPopover: View {
|
|||
.frame(width: 35)
|
||||
}
|
||||
.padding(.bottom, 5)
|
||||
if position.nodePosition?.viaMqtt ?? false {
|
||||
|
||||
Label {
|
||||
let heading = Measurement(value: degrees.degrees, unit: UnitAngle.degrees)
|
||||
Text("MQTT")
|
||||
} icon: {
|
||||
Image(systemName: "network")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.frame(width: 35)
|
||||
.rotationEffect(degrees)
|
||||
}
|
||||
.padding(.bottom, 5)
|
||||
}
|
||||
if let lastLocation = locationsHandler.locationsArray.last {
|
||||
/// Distance
|
||||
if lastLocation.distance(from: CLLocation(latitude: LocationsHandler.DefaultLocation.latitude, longitude: LocationsHandler.DefaultLocation.longitude)) > 0.0 {
|
||||
|
|
@ -181,8 +194,7 @@ struct PositionPopover: View {
|
|||
}
|
||||
BatteryGauge(node: position.nodePosition!)
|
||||
}
|
||||
let mpInt = Int(position.nodePosition?.loRaConfig?.modemPreset ?? 0)
|
||||
LoRaSignalStrengthMeter(snr: position.nodePosition?.snr ?? 0.0, rssi: position.nodePosition?.rssi ?? 0, preset: ModemPresets(rawValue: mpInt) ?? ModemPresets.longFast, compact: false)
|
||||
LoRaSignalStrengthMeter(snr: position.nodePosition?.snr ?? 0.0, rssi: position.nodePosition?.rssi ?? 0, preset: ModemPresets(rawValue: UserDefaults.modemPreset) ?? ModemPresets.longFast, compact: false)
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
|
|
@ -202,7 +214,7 @@ struct PositionPopover: View {
|
|||
#endif
|
||||
}
|
||||
}
|
||||
.presentationDetents([.fraction(0.45), .fraction(0.55), .fraction(0.65)])
|
||||
.presentationDetents([.fraction(0.55), .fraction(0.65), .fraction(0.75)])
|
||||
.presentationDragIndicator(.visible)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -187,7 +187,7 @@ struct WaypointForm: View {
|
|||
.controlSize(.regular)
|
||||
.padding(.bottom)
|
||||
|
||||
if waypoint.id > 0 {
|
||||
if waypoint.id > 0 && bleManager.isConnected {
|
||||
|
||||
Menu {
|
||||
Button("For me", action: {
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import MapKit
|
|||
struct NodeInfoItem: View {
|
||||
|
||||
@ObservedObject var node: NodeInfoEntity
|
||||
var modemPreset: ModemPresets = ModemPresets(rawValue: UserDefaults.modemPreset) ?? ModemPresets.longFast
|
||||
|
||||
var body: some View {
|
||||
|
||||
|
|
@ -37,11 +38,11 @@ struct NodeInfoItem: View {
|
|||
if node.snr != 0 && !node.viaMqtt {
|
||||
Divider()
|
||||
VStack(alignment: .center) {
|
||||
let signalStrength = getLoRaSignalStrength(snr: node.snr, rssi: node.rssi, preset: ModemPresets.longModerate)
|
||||
let signalStrength = getLoRaSignalStrength(snr: node.snr, rssi: node.rssi, preset: modemPreset)
|
||||
LoRaSignalStrengthIndicator(signalStrength: signalStrength)
|
||||
Text("Signal \(signalStrength.description)").font(.footnote)
|
||||
Text("SNR \(String(format: "%.2f", node.snr))dB")
|
||||
.foregroundColor(getSnrColor(snr: node.snr, preset: ModemPresets.longModerate))
|
||||
.foregroundColor(getSnrColor(snr: node.snr, preset: modemPreset))
|
||||
.font(.caption2)
|
||||
Text("RSSI \(node.rssi)dB")
|
||||
.foregroundColor(getRssiColor(rssi: node.rssi))
|
||||
|
|
@ -78,5 +79,12 @@ struct NodeInfoItem: View {
|
|||
}
|
||||
}
|
||||
Divider()
|
||||
if node.metadata != nil {
|
||||
HStack(alignment: .center) {
|
||||
Text("firmware.version").font(.title2)+Text(": \(node.metadata?.firmwareVersion ?? "unknown".localized)")
|
||||
.font(.title3).foregroundColor(Color.gray)
|
||||
}
|
||||
Divider()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
131
Meshtastic/Views/Nodes/Helpers/NodeListFilter.swift
Normal file
131
Meshtastic/Views/Nodes/Helpers/NodeListFilter.swift
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
//
|
||||
// NodeListFilter.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Created by Garth Vander Houwen on 3/25/24.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
struct NodeListFilter: View {
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
/// Filters
|
||||
@Binding var viaLora: Bool
|
||||
@Binding var viaMqtt: Bool
|
||||
@Binding var isOnline: Bool
|
||||
@Binding var distanceFilter: Bool
|
||||
@Binding var maximumDistance: Double
|
||||
@Binding var hopsAway: Int
|
||||
@Binding var deviceRole: Int
|
||||
|
||||
var body: some View {
|
||||
|
||||
NavigationStack {
|
||||
Form {
|
||||
Section(header: Text("Node Filters")) {
|
||||
Toggle(isOn: $viaLora) {
|
||||
|
||||
Label {
|
||||
Text("Via Lora")
|
||||
} icon: {
|
||||
Image(systemName: "dot.radiowaves.left.and.right")
|
||||
.rotationEffect(.degrees(-90))
|
||||
}
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
Toggle(isOn: $viaMqtt) {
|
||||
|
||||
Label {
|
||||
Text("Via Mqtt")
|
||||
} icon: {
|
||||
Image(systemName: "dot.radiowaves.up.forward")
|
||||
}
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
.listRowSeparator(.visible)
|
||||
|
||||
Toggle(isOn: $isOnline) {
|
||||
|
||||
Label {
|
||||
Text("Online Only")
|
||||
} icon: {
|
||||
Image(systemName: "checkmark.circle.fill")
|
||||
.foregroundColor(.green)
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
}
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
.listRowSeparator(.visible)
|
||||
|
||||
// Toggle(isOn: $distanceFilter) {
|
||||
//
|
||||
// Label {
|
||||
// Text("Distance")
|
||||
// } icon: {
|
||||
// Image(systemName: "map")
|
||||
// }
|
||||
// }
|
||||
// .toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
//
|
||||
// .listRowSeparator(distanceFilter ? .hidden : .visible)
|
||||
// if distanceFilter {
|
||||
// HStack {
|
||||
// Label("Show nodes", systemImage: "lines.measurement.horizontal")
|
||||
// Picker("", selection: $maximumDistance) {
|
||||
// ForEach(MeshMapDistances.allCases) { di in
|
||||
// Text(di.description)
|
||||
// .tag(di.id)
|
||||
// }
|
||||
// }
|
||||
// .pickerStyle(DefaultPickerStyle())
|
||||
// }
|
||||
// }
|
||||
HStack {
|
||||
Label("Hops Away", systemImage: "hare")
|
||||
Picker("", selection: $hopsAway) {
|
||||
Text("Any")
|
||||
.tag(-1)
|
||||
Text("Direct")
|
||||
.tag(0)
|
||||
ForEach(1..<8) {
|
||||
Text("\($0)")
|
||||
.tag($0)
|
||||
}
|
||||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
}
|
||||
HStack {
|
||||
Label("Device Role", systemImage: "apps.iphone")
|
||||
Picker("", selection: $deviceRole) {
|
||||
Text("All Roles")
|
||||
.tag(-1)
|
||||
ForEach(DeviceRoles.allCases) { dr in
|
||||
Label {
|
||||
Text(" \(dr.name)")
|
||||
} icon: {
|
||||
Image(systemName: dr.systemName)
|
||||
}
|
||||
}
|
||||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
}
|
||||
}
|
||||
}
|
||||
#if targetEnvironment(macCatalyst)
|
||||
Spacer()
|
||||
Button {
|
||||
dismiss()
|
||||
} label: {
|
||||
Label("close", systemImage: "xmark")
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
.buttonBorderShape(.capsule)
|
||||
.controlSize(.large)
|
||||
.padding(.bottom)
|
||||
#endif
|
||||
}
|
||||
.presentationDetents([.fraction(0.40), .fraction(0.50)])
|
||||
.presentationDragIndicator(.visible)
|
||||
}
|
||||
}
|
||||
|
|
@ -13,7 +13,6 @@ struct NodeListItem: View {
|
|||
@ObservedObject var node: NodeInfoEntity
|
||||
var connected: Bool
|
||||
var connectedNode: Int64
|
||||
var modemPreset: Int
|
||||
|
||||
var body: some View {
|
||||
|
||||
|
|
@ -21,6 +20,7 @@ struct NodeListItem: View {
|
|||
LazyVStack(alignment: .leading) {
|
||||
HStack {
|
||||
VStack(alignment: .leading) {
|
||||
|
||||
CircleText(text: node.user?.shortName ?? "?", color: Color(UIColor(hex: UInt32(node.num))), circleSize: 70)
|
||||
.padding(.trailing, 5)
|
||||
BatteryLevelCompact(node: node, font: .caption, iconFont: .callout, color: .accentColor)
|
||||
|
|
@ -119,22 +119,39 @@ struct NodeListItem: View {
|
|||
}
|
||||
HStack {
|
||||
if node.channel > 0 {
|
||||
Image(systemName: "fibrechannel")
|
||||
.font(.callout)
|
||||
HStack {
|
||||
Image(systemName: "\(node.channel).circle.fill")
|
||||
.font(.title2)
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.frame(width: 30)
|
||||
.foregroundColor(.accentColor)
|
||||
Text("Channel")
|
||||
.foregroundColor(.gray)
|
||||
.font(UIDevice.current.userInterfaceIdiom == .phone ? .callout : .caption)
|
||||
}
|
||||
}
|
||||
|
||||
if node.viaMqtt && connectedNode != node.num {
|
||||
Image(systemName: "dot.radiowaves.up.forward")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.font(.callout)
|
||||
.frame(width: 30)
|
||||
Text("Channel: \(node.channel)")
|
||||
Text("MQTT")
|
||||
.foregroundColor(.gray)
|
||||
.font(UIDevice.current.userInterfaceIdiom == .phone ? .callout : .caption)
|
||||
}
|
||||
if node.viaMqtt && connectedNode != node.num {
|
||||
Image(systemName: "network")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
}
|
||||
if node.hopsAway > 0 {
|
||||
HStack {
|
||||
Image(systemName: "hare")
|
||||
.font(.callout)
|
||||
.frame(width: 30)
|
||||
Text("Via MQTT")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text("Hops Away:")
|
||||
.foregroundColor(.gray)
|
||||
.font(UIDevice.current.userInterfaceIdiom == .phone ? .callout : .caption)
|
||||
Image(systemName: "\(node.hopsAway).square")
|
||||
.font(.title2)
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
}
|
||||
}
|
||||
if node.hasPositions || node.hasEnvironmentMetrics || node.hasDetectionSensorMetrics || node.hasTraceRoutes {
|
||||
|
|
@ -170,26 +187,22 @@ struct NodeListItem: View {
|
|||
.font(.callout)
|
||||
.frame(width: 30)
|
||||
}
|
||||
if node.hasTraceRoutes {
|
||||
Image(systemName: "signpost.right.and.left")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.font(.callout)
|
||||
.frame(width: 30)
|
||||
if #available(iOS 17.0, macOS 14.0, *) {
|
||||
if node.hasTraceRoutes {
|
||||
Image(systemName: "signpost.right.and.left")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.font(.callout)
|
||||
.frame(width: 30)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !connected {
|
||||
HStack {
|
||||
let preset = ModemPresets(rawValue: Int(modemPreset))
|
||||
LoRaSignalStrengthMeter(snr: node.snr, rssi: node.rssi, preset: preset ?? ModemPresets.longFast, compact: true)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding([.top, .bottom])
|
||||
.padding(.top, 4)
|
||||
.padding(.bottom, 4)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ import Foundation
|
|||
import MapKit
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
@available(iOS 17.0, macOS 14.0, *)
|
||||
struct MeshMap: View {
|
||||
|
||||
|
|
@ -22,156 +24,28 @@ struct MeshMap: View {
|
|||
/// Parameters
|
||||
@State var showUserLocation: Bool = true
|
||||
/// Map State User Defaults
|
||||
@AppStorage("meshMapShowNodeHistory") private var showNodeHistory = false
|
||||
@AppStorage("meshMapShowRouteLines") private var showRouteLines = false
|
||||
@AppStorage("enableMapConvexHull") private var showConvexHull = false
|
||||
@AppStorage("enableMapTraffic") private var showTraffic: Bool = false
|
||||
@AppStorage("enableMapPointsOfInterest") private var showPointsOfInterest: Bool = false
|
||||
@AppStorage("mapLayer") private var selectedMapLayer: MapLayer = .hybrid
|
||||
@AppStorage("mapLayer") private var selectedMapLayer: MapLayer = .standard
|
||||
// Map Configuration
|
||||
@Namespace var mapScope
|
||||
@State var mapStyle: MapStyle = MapStyle.standard(elevation: .realistic, emphasis: MapStyle.StandardEmphasis.muted ,pointsOfInterest: .all, showsTraffic: true)
|
||||
@State var mapStyle: MapStyle = MapStyle.standard(elevation: .flat, emphasis: MapStyle.StandardEmphasis.muted ,pointsOfInterest: .excludingAll, showsTraffic: false)
|
||||
@State var position = MapCameraPosition.automatic
|
||||
@State var scene: MKLookAroundScene?
|
||||
@State var isLookingAround = false
|
||||
@State var isEditingSettings = false
|
||||
@State var selectedPosition: PositionEntity?
|
||||
@State var showWaypoints = true
|
||||
@State var editingWaypoint: WaypointEntity?
|
||||
@State var selectedWaypoint: WaypointEntity?
|
||||
@State var newWaypointCoord :CLLocationCoordinate2D?
|
||||
@State var newWaypointCoord: CLLocationCoordinate2D?
|
||||
@State var isMeshMap = true
|
||||
|
||||
var delay: Double = 0
|
||||
@State private var scale: CGFloat = 0.5
|
||||
/// && time >= %@
|
||||
@FetchRequest(sortDescriptors: [NSSortDescriptor(key: "time", ascending: true)],
|
||||
predicate: NSPredicate(format: "nodePosition != nil && latest == true", Calendar.current.date(byAdding: .day, value: -7, to: Date())! as NSDate), animation: .none)
|
||||
private var positions: FetchedResults<PositionEntity>
|
||||
|
||||
@FetchRequest(sortDescriptors: [NSSortDescriptor(key: "name", ascending: false)],
|
||||
predicate: NSPredicate(
|
||||
format: "expire == nil || expire >= %@", Date() as NSDate
|
||||
), animation: .none)
|
||||
private var waypoints: FetchedResults<WaypointEntity>
|
||||
|
||||
@FetchRequest(sortDescriptors: [NSSortDescriptor(key: "name", ascending: true)],
|
||||
predicate: NSPredicate(format: "enabled == true", ""), animation: .none)
|
||||
private var routes: FetchedResults<RouteEntity>
|
||||
|
||||
var body: some View {
|
||||
|
||||
let lineCoords = Array(positions).compactMap({(position) -> CLLocationCoordinate2D in
|
||||
return position.nodeCoordinate ?? LocationHelper.DefaultLocation
|
||||
})
|
||||
NavigationStack {
|
||||
ZStack {
|
||||
MapReader { reader in
|
||||
Map(position: $position, bounds: MapCameraBounds(minimumDistance: 1, maximumDistance: .infinity), scope: mapScope) {
|
||||
/// Convex Hull
|
||||
if showConvexHull {
|
||||
if lineCoords.count > 0 {
|
||||
let hull = lineCoords.getConvexHull()
|
||||
MapPolygon(coordinates: hull)
|
||||
.stroke(.blue, lineWidth: 3)
|
||||
.foregroundStyle(.indigo.opacity(0.4))
|
||||
}
|
||||
}
|
||||
/// Position Annotations
|
||||
ForEach(Array(positions), id: \.id) { position in
|
||||
/// Node color from node.num
|
||||
let nodeColor = UIColor(hex: UInt32(position.nodePosition?.num ?? 0))
|
||||
Annotation(position.nodePosition?.user?.longName ?? "?", coordinate: position.coordinate) {
|
||||
LazyVStack {
|
||||
ZStack {
|
||||
let nodeColor = UIColor(hex: UInt32(position.nodePosition?.num ?? 0))
|
||||
if position.nodePosition?.isOnline ?? false {
|
||||
Circle()
|
||||
.fill(Color(nodeColor.lighter()).opacity(0.4).shadow(.drop(color: Color(nodeColor).isLight() ? .black : .white, radius: 5)))
|
||||
.foregroundStyle(Color(nodeColor.lighter()).opacity(0.3))
|
||||
.scaleEffect(scale)
|
||||
.animation(
|
||||
Animation.easeInOut(duration: 0.6)
|
||||
.repeatForever().delay(delay), value: scale
|
||||
)
|
||||
.onAppear {
|
||||
self.scale = 1
|
||||
}
|
||||
.frame(width: 60, height: 60)
|
||||
}
|
||||
if position.nodePosition?.hasDetectionSensorMetrics ?? false {
|
||||
Image(systemName: "sensor.fill")
|
||||
.symbolRenderingMode(.palette)
|
||||
.symbolEffect(.variableColor)
|
||||
.padding()
|
||||
.foregroundStyle(.white)
|
||||
.background(Color(nodeColor))
|
||||
.clipShape(Circle())
|
||||
} else {
|
||||
CircleText(text: position.nodePosition?.user?.shortName ?? "?", color: Color(nodeColor), circleSize: 40)
|
||||
}
|
||||
}
|
||||
}
|
||||
.onTapGesture { location in
|
||||
selectedPosition = (selectedPosition == position ? nil : position)
|
||||
}
|
||||
}
|
||||
/// Reduced Precision Map Circles
|
||||
if 11...16 ~= position.precisionBits {
|
||||
let pp = PositionPrecision(rawValue: Int(position.precisionBits))
|
||||
let radius : CLLocationDistance = pp?.precisionMeters ?? 0
|
||||
if radius > 0.0 {
|
||||
MapCircle(center: position.coordinate, radius: radius)
|
||||
.foregroundStyle(Color(nodeColor).opacity(0.25))
|
||||
}
|
||||
}
|
||||
/// Routes
|
||||
ForEach(Array(routes), id: \.id) { route in
|
||||
let routeLocations = Array(route.locations!) as! [LocationEntity]
|
||||
let routeCoords = routeLocations.compactMap({(loc) -> CLLocationCoordinate2D in
|
||||
return loc.locationCoordinate ?? LocationHelper.DefaultLocation
|
||||
})
|
||||
Annotation("Start", coordinate: routeCoords.first ?? LocationHelper.DefaultLocation) {
|
||||
ZStack {
|
||||
Circle()
|
||||
.fill(Color(.green))
|
||||
.strokeBorder(.white, lineWidth: 3)
|
||||
.frame(width: 15, height: 15)
|
||||
}
|
||||
}
|
||||
.annotationTitles(.automatic)
|
||||
Annotation("Finish", coordinate: routeCoords.last ?? LocationHelper.DefaultLocation) {
|
||||
ZStack {
|
||||
Circle()
|
||||
.fill(Color(.black))
|
||||
.strokeBorder(.white, lineWidth: 3)
|
||||
.frame(width: 15, height: 15)
|
||||
}
|
||||
}
|
||||
.annotationTitles(.automatic)
|
||||
let solid = StrokeStyle(
|
||||
lineWidth: 3,
|
||||
lineCap: .round, lineJoin: .round
|
||||
)
|
||||
MapPolyline(coordinates: routeCoords)
|
||||
.stroke(Color(UIColor(hex: UInt32(route.color))), style: solid)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// Waypoint Annotations
|
||||
if waypoints.count > 0 && showWaypoints {
|
||||
ForEach(Array(waypoints), id: \.id) { waypoint in
|
||||
Annotation(waypoint.name ?? "?", coordinate: waypoint.coordinate) {
|
||||
LazyVStack {
|
||||
CircleText(text: String(UnicodeScalar(Int(waypoint.icon)) ?? "📍"), color: Color.orange, circleSize: 40)
|
||||
.onTapGesture(perform: { location in
|
||||
selectedWaypoint = (selectedWaypoint == waypoint ? nil : waypoint)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
MeshMapContent(showUserLocation: $showUserLocation, showTraffic: $showTraffic, showPointsOfInterest: $showPointsOfInterest, selectedMapLayer: $selectedMapLayer, selectedPosition: $selectedPosition, selectedWaypoint: $selectedWaypoint)
|
||||
|
||||
}
|
||||
.mapScope(mapScope)
|
||||
.mapStyle(mapStyle)
|
||||
|
|
@ -184,22 +58,39 @@ struct MeshMap: View {
|
|||
.mapControlVisibility(.automatic)
|
||||
}
|
||||
.controlSize(.regular)
|
||||
.onTapGesture(count: 1, perform: { location in
|
||||
newWaypointCoord = reader.convert(location , from: .local)
|
||||
.onTapGesture(count: 1, perform: { position in
|
||||
newWaypointCoord = reader.convert(position, from: .local) ?? CLLocationCoordinate2D.init()
|
||||
})
|
||||
.gesture(
|
||||
LongPressGesture(minimumDuration: 0.5)
|
||||
.sequenced(before: SpatialTapGesture(coordinateSpace: .local))
|
||||
.onEnded { value in
|
||||
switch value {
|
||||
case let .second(_, tapValue):
|
||||
guard let point = tapValue?.location else {
|
||||
print("Unable to retreive tap location from gesture data.")
|
||||
return
|
||||
}
|
||||
|
||||
guard let coordinate = reader.convert(point, from: .local) else {
|
||||
print("Unable to convert local point to coordinate on map.")
|
||||
return
|
||||
}
|
||||
|
||||
newWaypointCoord = coordinate
|
||||
editingWaypoint = WaypointEntity(context: context)
|
||||
editingWaypoint!.name = "Waypoint Pin"
|
||||
editingWaypoint!.expire = Date.now.addingTimeInterval(60 * 480)
|
||||
editingWaypoint!.latitudeI = Int32((newWaypointCoord?.latitude ?? 0) * 1e7)
|
||||
editingWaypoint!.longitudeI = Int32((newWaypointCoord?.longitude ?? 0) * 1e7)
|
||||
editingWaypoint!.expire = Date.now.addingTimeInterval(60 * 480)
|
||||
editingWaypoint!.id = 0
|
||||
print("Long press occured at: \(coordinate)")
|
||||
default: return
|
||||
}
|
||||
})
|
||||
.onLongPressGesture(minimumDuration: 0.5, maximumDistance: 10) {
|
||||
editingWaypoint = WaypointEntity(context: context)
|
||||
editingWaypoint!.name = "Waypoint Pin"
|
||||
editingWaypoint!.expire = Date.now.addingTimeInterval(60 * 480)
|
||||
editingWaypoint!.latitudeI = Int32((newWaypointCoord?.latitude ?? 0) * 1e7)
|
||||
editingWaypoint!.longitudeI = Int32((newWaypointCoord?.longitude ?? 0) * 1e7)
|
||||
editingWaypoint!.expire = Date.now.addingTimeInterval(60 * 480)
|
||||
editingWaypoint!.id = 0
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.sheet(item: $selectedPosition) { selection in
|
||||
PositionPopover(position: selection, popover: false)
|
||||
.padding()
|
||||
|
|
@ -213,7 +104,7 @@ struct MeshMap: View {
|
|||
.padding()
|
||||
}
|
||||
.sheet(isPresented: $isEditingSettings) {
|
||||
MapSettingsForm(nodeHistory: $showNodeHistory, routeLines: $showRouteLines, convexHull: $showConvexHull, traffic: $showTraffic, pointsOfInterest: $showPointsOfInterest, mapLayer: $selectedMapLayer, meshMap: $isMeshMap)
|
||||
MapSettingsForm(traffic: $showTraffic, pointsOfInterest: $showPointsOfInterest, mapLayer: $selectedMapLayer, meshMap: $isMeshMap)
|
||||
}
|
||||
.onChange(of: (appState.navigationPath)) { newPath in
|
||||
|
||||
|
|
@ -234,12 +125,12 @@ struct MeshMap: View {
|
|||
print("Waypoint id not found")
|
||||
return
|
||||
}
|
||||
guard let waypoint = waypoints.first(where: { $0.id == Int64(waypointId) }) else {
|
||||
print("Waypoint not found")
|
||||
return
|
||||
}
|
||||
showWaypoints = true
|
||||
position = .camera(MapCamera(centerCoordinate: waypoint.coordinate, distance: 1000, heading: 0, pitch: 60))
|
||||
// guard let waypoint = waypoints.first(where: { $0.id == Int64(waypointId) }) else {
|
||||
// print("Waypoint not found")
|
||||
// return
|
||||
// }
|
||||
//showWaypoints = true
|
||||
//position = .camera(MapCamera(centerCoordinate: waypoint.coordinate, distance: 1000, heading: 0, pitch: 60))
|
||||
}
|
||||
}
|
||||
.onChange(of: (selectedMapLayer)) { newMapLayer in
|
||||
|
|
@ -269,36 +160,7 @@ struct MeshMap: View {
|
|||
}
|
||||
.tint(Color(UIColor.secondarySystemBackground))
|
||||
.foregroundColor(.accentColor)
|
||||
.buttonStyle(.borderedProminent)
|
||||
/// Show / Hide Waypoints Button
|
||||
if waypoints.count > 0 {
|
||||
|
||||
Button(action: {
|
||||
withAnimation {
|
||||
showWaypoints = !showWaypoints
|
||||
}
|
||||
}) {
|
||||
Image(systemName: showWaypoints ? "signpost.right.and.left.fill" : "signpost.right.and.left")
|
||||
.padding(.vertical, 5)
|
||||
}
|
||||
.tint(Color(UIColor.secondarySystemBackground))
|
||||
.foregroundColor(.accentColor)
|
||||
.buttonStyle(.borderedProminent)
|
||||
}
|
||||
/// Look Around Button
|
||||
if self.scene != nil {
|
||||
Button(action: {
|
||||
withAnimation {
|
||||
isLookingAround = !isLookingAround
|
||||
}
|
||||
}) {
|
||||
Image(systemName: isLookingAround ? "binoculars.fill" : "binoculars")
|
||||
.padding(.vertical, 5)
|
||||
}
|
||||
.tint(Color(UIColor.secondarySystemBackground))
|
||||
.foregroundColor(.accentColor)
|
||||
.buttonStyle(.borderedProminent)
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
}
|
||||
.controlSize(.regular)
|
||||
.padding(5)
|
||||
|
|
|
|||
|
|
@ -14,19 +14,20 @@ struct NodeList: View {
|
|||
@State private var isPresentingTraceRouteSentAlert = false
|
||||
@State private var isPresentingClientHistorySentAlert = false
|
||||
@State private var isPresentingDeleteNodeAlert = false
|
||||
@State private var isPresentingPositionSentAlert = false
|
||||
@State private var deleteNodeId: Int64 = 0
|
||||
@State private var searchText = ""
|
||||
@State private var viaLora = true
|
||||
@State private var viaMqtt = true
|
||||
@State private var isOnline = false
|
||||
@State private var distanceFilter = false
|
||||
@State private var maxDistance: Double = 800000
|
||||
@State private var hopsAway: Int = -1
|
||||
@State private var deviceRole: Int = -1
|
||||
|
||||
@State var isEditingFilters = false
|
||||
|
||||
@SceneStorage("selectedDetailView") var selectedDetailView: String?
|
||||
|
||||
@State private var searchText = ""
|
||||
var nodesQuery: Binding<String> {
|
||||
Binding {
|
||||
searchText
|
||||
} set: { newValue in
|
||||
searchText = newValue
|
||||
nodes.nsPredicate = newValue.isEmpty ? nil : NSPredicate(format: "user.longName CONTAINS[c] %@ OR user.shortName CONTAINS[c] %@", newValue, newValue)
|
||||
}
|
||||
}
|
||||
|
||||
@Environment(\.managedObjectContext) var context
|
||||
@EnvironmentObject var bleManager: BLEManager
|
||||
|
|
@ -37,8 +38,6 @@ struct NodeList: View {
|
|||
|
||||
var nodes: FetchedResults<NodeInfoEntity>
|
||||
|
||||
|
||||
|
||||
var body: some View {
|
||||
NavigationSplitView(columnVisibility: $columnVisibility) {
|
||||
|
||||
|
|
@ -48,8 +47,7 @@ struct NodeList: View {
|
|||
|
||||
NodeListItem(node: node,
|
||||
connected: bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral?.num ?? -1 == node.num,
|
||||
connectedNode: (bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral?.num ?? -1 : -1),
|
||||
modemPreset: Int(connectedNode?.loRaConfig?.modemPreset ?? 0))
|
||||
connectedNode: (bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral?.num ?? -1 : -1))
|
||||
.contextMenu {
|
||||
if node.user != nil {
|
||||
Button {
|
||||
|
|
@ -76,7 +74,21 @@ struct NodeList: View {
|
|||
} label: {
|
||||
Label(node.user!.mute ? "Show Alerts" : "Hide Alerts", systemImage: node.user!.mute ? "bell" : "bell.slash")
|
||||
}
|
||||
if connectedNodeNum != node.num {
|
||||
if bleManager.connectedPeripheral != nil && node.num != connectedNodeNum {
|
||||
Button {
|
||||
let positionSent = bleManager.sendPosition(
|
||||
channel: node.channel,
|
||||
destNum: node.num,
|
||||
wantResponse: true
|
||||
)
|
||||
if positionSent {
|
||||
isPresentingPositionSentAlert = true
|
||||
}
|
||||
} label: {
|
||||
Label("Exchange Positions", systemImage: "arrow.triangle.2.circlepath")
|
||||
}
|
||||
}
|
||||
if bleManager.connectedPeripheral != nil && connectedNodeNum != node.num {
|
||||
Button {
|
||||
let success = bleManager.sendTraceRouteRequest(destNum: node.user?.num ?? 0, wantResponse: true)
|
||||
if success {
|
||||
|
|
@ -107,6 +119,14 @@ struct NodeList: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
.alert(
|
||||
"Position Sent",
|
||||
isPresented: $isPresentingPositionSentAlert
|
||||
) {
|
||||
Button("OK", role: .cancel) { }
|
||||
} message: {
|
||||
Text("Your position has been sent with a request for a response with their position.")
|
||||
}
|
||||
.alert(
|
||||
"Trace Route Sent",
|
||||
isPresented: $isPresentingTraceRouteSentAlert
|
||||
|
|
@ -124,7 +144,31 @@ struct NodeList: View {
|
|||
Text("Any missed messages will be delivered again.")
|
||||
}
|
||||
}
|
||||
.searchable(text: nodesQuery, prompt: "Find a node")
|
||||
.sheet(isPresented: $isEditingFilters) {
|
||||
NodeListFilter(viaLora: $viaLora, viaMqtt: $viaMqtt, isOnline: $isOnline, distanceFilter: $distanceFilter, maximumDistance: $maxDistance, hopsAway: $hopsAway, deviceRole: $deviceRole)
|
||||
}
|
||||
.safeAreaInset(edge: .bottom, alignment: .trailing) {
|
||||
HStack {
|
||||
Button(action: {
|
||||
withAnimation {
|
||||
isEditingFilters = !isEditingFilters
|
||||
}
|
||||
}) {
|
||||
Image(systemName: !isEditingFilters ? "line.3.horizontal.decrease.circle" : "line.3.horizontal.decrease.circle.fill")
|
||||
.padding(.vertical, 5)
|
||||
}
|
||||
.tint(Color(UIColor.secondarySystemBackground))
|
||||
.foregroundColor(.accentColor)
|
||||
.buttonStyle(.borderedProminent)
|
||||
|
||||
}
|
||||
.controlSize(.regular)
|
||||
.padding(5)
|
||||
}
|
||||
.padding(.bottom, 5)
|
||||
.searchable(text: $searchText, placement: .automatic, prompt: "Find a node")
|
||||
.disableAutocorrection(true)
|
||||
.scrollDismissesKeyboard(.immediately)
|
||||
.navigationTitle(String.localizedStringWithFormat("nodes %@".localized, String(nodes.count)))
|
||||
.listStyle(.plain)
|
||||
.confirmationDialog(
|
||||
|
|
@ -178,7 +222,7 @@ struct NodeList: View {
|
|||
name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?", phoneOnly: true)
|
||||
})
|
||||
}
|
||||
.padding(.bottom, 5)
|
||||
|
||||
} else {
|
||||
if #available (iOS 17, *) {
|
||||
ContentUnavailableView("select.node", systemImage: "flipphone")
|
||||
|
|
@ -195,33 +239,104 @@ struct NodeList: View {
|
|||
|
||||
}
|
||||
.navigationSplitViewStyle(.balanced)
|
||||
// .onChange(of: selectedNode) { _ in
|
||||
// if selectedNode == nil {
|
||||
// columnVisibility = .all
|
||||
// } else {
|
||||
// columnVisibility = .doubleColumn
|
||||
// }
|
||||
// }
|
||||
.onChange(of: searchText) { _ in
|
||||
searchNodeList()
|
||||
}
|
||||
.onChange(of: viaLora) { _ in
|
||||
if !viaLora && !viaMqtt {
|
||||
viaMqtt = true
|
||||
}
|
||||
searchNodeList()
|
||||
}
|
||||
.onChange(of: viaMqtt) { _ in
|
||||
if !viaLora && !viaMqtt {
|
||||
viaLora = true
|
||||
}
|
||||
searchNodeList()
|
||||
}
|
||||
.onChange(of: deviceRole) { _ in
|
||||
searchNodeList()
|
||||
}
|
||||
.onChange(of: hopsAway) { _ in
|
||||
searchNodeList()
|
||||
}
|
||||
.onChange(of: isOnline) { _ in
|
||||
searchNodeList()
|
||||
}
|
||||
.onAppear {
|
||||
if self.bleManager.context == nil {
|
||||
self.bleManager.context = context
|
||||
}
|
||||
searchNodeList()
|
||||
}
|
||||
}
|
||||
|
||||
private func searchNodeList() {
|
||||
/// Case Insensitive Search Text Predicates
|
||||
let searchPredicates = ["user.userId", "user.hwModel", "user.longName", "user.shortName"].map { property in
|
||||
return NSPredicate(format: "%K CONTAINS[c] %@", property, searchText)
|
||||
}
|
||||
/// Create a compound predicate using each text search preicate as an OR
|
||||
let textSearchPredicate = NSCompoundPredicate(type: .or, subpredicates: searchPredicates)
|
||||
/// Create an array of predicates to hold our AND predicates
|
||||
var predicates: [NSPredicate] = []
|
||||
/// Mqtt
|
||||
if !(viaLora && viaMqtt) {
|
||||
if viaLora {
|
||||
let loraPredicate = NSPredicate(format: "viaMqtt == NO")
|
||||
predicates.append(loraPredicate)
|
||||
} else {
|
||||
let mqttPredicate = NSPredicate(format: "viaMqtt == YES")
|
||||
predicates.append(mqttPredicate)
|
||||
}
|
||||
}
|
||||
/// Role
|
||||
if deviceRole > -1 {
|
||||
let rolePredicate = NSPredicate(format: "user.role == %i", Int32(deviceRole))
|
||||
predicates.append(rolePredicate)
|
||||
}
|
||||
/// Hops Away
|
||||
if hopsAway > 0 {
|
||||
let hopsAwayPredicate = NSPredicate(format: "hopsAway == %i", Int32(hopsAway))
|
||||
predicates.append(hopsAwayPredicate)
|
||||
}
|
||||
|
||||
/// Online
|
||||
if isOnline {
|
||||
let isOnlinePredicate = NSPredicate(format: "lastHeard >= %@", Calendar.current.date(byAdding: .minute, value: -15, to: Date())! as NSDate)
|
||||
predicates.append(isOnlinePredicate)
|
||||
}
|
||||
/// Distance
|
||||
if distanceFilter {
|
||||
let pointOfInterest = LocationHelper.currentLocation
|
||||
|
||||
if pointOfInterest.latitude != LocationHelper.DefaultLocation.latitude && pointOfInterest.longitude != LocationHelper.DefaultLocation.longitude {
|
||||
let D: Double = maxDistance * 1.1
|
||||
let R: Double = 6371009
|
||||
let meanLatitidue = pointOfInterest.latitude * .pi / 180
|
||||
let deltaLatitude = D / R * 180 / .pi
|
||||
let deltaLongitude = D / (R * cos(meanLatitidue)) * 180 / .pi
|
||||
let minLatitude: Double = pointOfInterest.latitude - deltaLatitude
|
||||
let maxLatitude: Double = pointOfInterest.latitude + deltaLatitude
|
||||
let minLongitude: Double = pointOfInterest.longitude - deltaLongitude
|
||||
let maxLongitude: Double = pointOfInterest.longitude + deltaLongitude
|
||||
let distancePredicate = NSPredicate(format: "(%lf <= (positions[first].longitudeI / 1e7))", minLongitude, maxLongitude,minLatitude, maxLatitude)
|
||||
//let distancePredicate = NSPredicate(format: "(%lf <= (positions[LAST].longitudeI / 1e7)) AND ((positions[LAST].longitudeI / 1e7) <= %lf) AND (%lf <= (positions[LAST].latitudeI / 1e7)) AND ((positions[LAST].latitudeI / 1e7) <= %lf)", minLongitude, maxLongitude,minLatitude, maxLatitude)
|
||||
|
||||
//predicates.append(distancePredicate)
|
||||
}
|
||||
}
|
||||
|
||||
if predicates.count > 0 || !searchText.isEmpty {
|
||||
|
||||
if !searchText.isEmpty {
|
||||
let filterPredicates = NSCompoundPredicate(type: .and, subpredicates: predicates)
|
||||
nodes.nsPredicate = NSCompoundPredicate(type: .and, subpredicates: [textSearchPredicate, filterPredicates])
|
||||
} else {
|
||||
nodes.nsPredicate = NSCompoundPredicate(type: .and, subpredicates: predicates)
|
||||
}
|
||||
} else {
|
||||
nodes.nsPredicate = nil
|
||||
}
|
||||
|
||||
// } detail: {
|
||||
// VStack {
|
||||
// Button("Detail Only") {
|
||||
// columnVisibility = .detailOnly
|
||||
// }
|
||||
//
|
||||
// Button("Content and Detail") {
|
||||
// columnVisibility = .doubleColumn
|
||||
// }
|
||||
//
|
||||
// Button("Show All") {
|
||||
// columnVisibility = .all
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -75,8 +75,8 @@ struct PaxCounterLog: View {
|
|||
.chartXAxis(.automatic)
|
||||
.chartYScale(domain: 0...maxValue)
|
||||
.chartForegroundStyleScale([
|
||||
"paxcounter.ble": .blue,
|
||||
"paxcounter.wifi": .orange,
|
||||
"paxcounter.ble".localized: .blue,
|
||||
"paxcounter.wifi".localized: .orange,
|
||||
"paxcounter.total".localized: .green
|
||||
])
|
||||
.chartLegend(position: .automatic, alignment: .bottom)
|
||||
|
|
@ -111,11 +111,11 @@ struct PaxCounterLog: View {
|
|||
} else {
|
||||
ScrollView {
|
||||
let columns = [
|
||||
GridItem(.flexible(minimum: 20, maximum: 55), spacing: 0.1),
|
||||
GridItem(.flexible(minimum: 20, maximum: 55), spacing: 0.1),
|
||||
GridItem(.flexible(minimum: 20, maximum: 55), spacing: 0.1),
|
||||
GridItem(.flexible(minimum: 60, maximum: 100), spacing: 0.1),
|
||||
GridItem(.flexible(minimum: 130, maximum: 200), spacing: 0.1)
|
||||
GridItem(.flexible(minimum: 20, maximum: 50), spacing: 0.1),
|
||||
GridItem(.flexible(minimum: 20, maximum: 50), spacing: 0.1),
|
||||
GridItem(.flexible(minimum: 20, maximum: 50), spacing: 0.1),
|
||||
GridItem(.flexible(minimum: 60, maximum: 140), spacing: 0.1),
|
||||
GridItem(.flexible(minimum: 100, maximum: 160), spacing: 0.1)
|
||||
]
|
||||
LazyVGrid(columns: columns, alignment: .leading, spacing: 1) {
|
||||
GridRow {
|
||||
|
|
|
|||
|
|
@ -24,9 +24,15 @@ struct AppSettings: View {
|
|||
Label("appsettings.provide.location", systemImage: "location.circle.fill")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
Text("Use your phone's gps to provide a location to your node. Must have location access and precise location enabled for Meshtastic in Settings.")
|
||||
.font(.caption2)
|
||||
.foregroundColor(.gray)
|
||||
if provideLocation {
|
||||
Toggle(isOn: $enableSmartPosition) {
|
||||
Label("appsettings.smartposition", systemImage: "brain")
|
||||
Text("Will only send a position to the phone if it is recent and of high horizontal accuracy.")
|
||||
.font(.caption2)
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
VStack {
|
||||
|
|
|
|||
|
|
@ -31,7 +31,6 @@ struct Channels: View {
|
|||
|
||||
@State var hasChanges = false
|
||||
@State var hasValidKey = true
|
||||
@State private var isPresentingEditView = false
|
||||
@State private var isPresentingSaveConfirm: Bool = false
|
||||
@State private var channelIndex: Int32 = 0
|
||||
@State private var channelName = ""
|
||||
|
|
@ -40,18 +39,16 @@ struct Channels: View {
|
|||
@State private var channelRole = 0
|
||||
@State private var uplink = false
|
||||
@State private var downlink = false
|
||||
|
||||
@State private var positionPrecision = 32.0
|
||||
@State private var preciseLocation = true
|
||||
@State private var positionsEnabled = true
|
||||
@State private var supportedVersion = true
|
||||
@State var selectedChannel: ChannelEntity?
|
||||
|
||||
/// Minimum Version for granular position configuration
|
||||
@State var minimumVersion = "2.2.24"
|
||||
|
||||
|
||||
var body: some View {
|
||||
|
||||
let supportedVersion = bleManager.connectedVersion == "0.0.0" || self.minimumVersion.compare(bleManager.connectedVersion, options: .numeric) == .orderedAscending || minimumVersion.compare(bleManager.connectedVersion, options: .numeric) == .orderedSame
|
||||
|
||||
VStack {
|
||||
List {
|
||||
|
|
@ -68,6 +65,8 @@ struct Channels: View {
|
|||
channelKeySize = 0
|
||||
} else if channelKey == "AQ==" {
|
||||
channelKeySize = -1
|
||||
} else if channelKey.count == 4 {
|
||||
channelKeySize = 1
|
||||
} else if channelKey.count == 24 {
|
||||
channelKeySize = 16
|
||||
} else if channelKey.count == 32 {
|
||||
|
|
@ -78,7 +77,7 @@ struct Channels: View {
|
|||
channelName = channel.name ?? ""
|
||||
uplink = channel.uplinkEnabled
|
||||
downlink = channel.downlinkEnabled
|
||||
hasChanges = false
|
||||
positionPrecision = Double(channel.positionPrecision)
|
||||
if !supportedVersion && channelRole == 1 {
|
||||
positionPrecision = 32
|
||||
preciseLocation = true
|
||||
|
|
@ -89,21 +88,20 @@ struct Channels: View {
|
|||
preciseLocation = false
|
||||
positionsEnabled = false
|
||||
} else {
|
||||
positionPrecision = Double(channel.positionPrecision)
|
||||
if positionPrecision == 32 {
|
||||
preciseLocation = true
|
||||
positionsEnabled = true
|
||||
} else {
|
||||
preciseLocation = false
|
||||
}
|
||||
|
||||
if positionPrecision == 0 {
|
||||
positionsEnabled = false
|
||||
} else {
|
||||
positionsEnabled = true
|
||||
}
|
||||
}
|
||||
isPresentingEditView = true
|
||||
hasChanges = false
|
||||
selectedChannel = channel
|
||||
}) {
|
||||
VStack(alignment: .leading) {
|
||||
HStack {
|
||||
|
|
@ -129,231 +127,15 @@ struct Channels: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
.sheet(isPresented: $isPresentingEditView) {
|
||||
|
||||
.sheet(item: $selectedChannel) { selection in
|
||||
#if targetEnvironment(macCatalyst)
|
||||
Text("channel")
|
||||
.font(.largeTitle)
|
||||
.padding()
|
||||
#endif
|
||||
Form {
|
||||
Section(header: Text("channel details")) {
|
||||
HStack {
|
||||
Text("name")
|
||||
Spacer()
|
||||
TextField(
|
||||
"Channel Name",
|
||||
text: $channelName
|
||||
)
|
||||
.disableAutocorrection(true)
|
||||
.keyboardType(.alphabet)
|
||||
.foregroundColor(Color.gray)
|
||||
.onChange(of: channelName, perform: { _ in
|
||||
channelName = channelName.replacing(" ", with: "")
|
||||
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
|
||||
}
|
||||
}
|
||||
hasChanges = true
|
||||
})
|
||||
}
|
||||
HStack {
|
||||
Picker("Key Size", selection: $channelKeySize) {
|
||||
Text("Empty").tag(0)
|
||||
Text("Default").tag(-1)
|
||||
Text("1 byte").tag(1)
|
||||
Text("128 bit").tag(16)
|
||||
Text("192 bit").tag(24)
|
||||
Text("256 bit").tag(32)
|
||||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
Spacer()
|
||||
Button {
|
||||
if channelKeySize == -1 {
|
||||
channelKey = "AQ=="
|
||||
} else {
|
||||
let key = generateChannelKey(size: channelKeySize)
|
||||
channelKey = key
|
||||
}
|
||||
} label: {
|
||||
Image(systemName: "lock.rotation")
|
||||
.font(.title)
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
.buttonBorderShape(.capsule)
|
||||
.controlSize(.small)
|
||||
}
|
||||
HStack(alignment: .center) {
|
||||
Text("Key")
|
||||
Spacer()
|
||||
TextField(
|
||||
"Key",
|
||||
text: $channelKey,
|
||||
axis: .vertical
|
||||
)
|
||||
.padding(6)
|
||||
.disableAutocorrection(true)
|
||||
.keyboardType(.alphabet)
|
||||
.foregroundColor(Color.gray)
|
||||
.textSelection(.enabled)
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 10.0)
|
||||
.stroke(
|
||||
hasValidKey ?
|
||||
Color.clear :
|
||||
Color.red
|
||||
, lineWidth: 2.0)
|
||||
|
||||
)
|
||||
.onChange(of: channelKey, perform: { _ in
|
||||
let tempKey = Data(base64Encoded: channelKey) ?? Data()
|
||||
if tempKey.count == channelKeySize || channelKeySize == -1{
|
||||
hasValidKey = true
|
||||
}
|
||||
else {
|
||||
hasValidKey = false
|
||||
}
|
||||
hasChanges = true
|
||||
})
|
||||
.disabled(channelKeySize <= 0)
|
||||
}
|
||||
HStack {
|
||||
if channelRole == 1 {
|
||||
Picker("Channel Role", selection: $channelRole) {
|
||||
Text("Primary").tag(1)
|
||||
}
|
||||
.pickerStyle(.automatic)
|
||||
.disabled(true)
|
||||
} else {
|
||||
Text("Channel Role")
|
||||
Spacer()
|
||||
Picker("Channel Role", selection: $channelRole) {
|
||||
Text("Disabled").tag(0)
|
||||
Text("Secondary").tag(2)
|
||||
}
|
||||
.pickerStyle(.segmented)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Section(header: Text("position")) {
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
Toggle(isOn: $positionsEnabled) {
|
||||
Label(channelRole == 1 ? "Positions Enabled" : "Allow Position Requests", systemImage: positionsEnabled ? "mappin" : "mappin.slash")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
.disabled(!supportedVersion)
|
||||
}
|
||||
|
||||
if positionsEnabled {
|
||||
VStack(alignment: .leading) {
|
||||
Toggle(isOn: $preciseLocation) {
|
||||
Label("Precise Location", systemImage: "scope")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
.disabled(!supportedVersion)
|
||||
.listRowSeparator(.visible)
|
||||
.onChange(of: preciseLocation) { pl in
|
||||
if pl == false {
|
||||
positionPrecision = 13
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !preciseLocation {
|
||||
VStack(alignment: .leading) {
|
||||
Label("Reduce Precision", systemImage: "location.viewfinder")
|
||||
Slider(
|
||||
value: $positionPrecision,
|
||||
in: 11...16,
|
||||
step: 1
|
||||
)
|
||||
{
|
||||
} minimumValueLabel: {
|
||||
Image(systemName: "minus")
|
||||
} maximumValueLabel: {
|
||||
Image(systemName: "plus")
|
||||
}
|
||||
Text(PositionPrecision(rawValue: Int(positionPrecision))?.description ?? "")
|
||||
.foregroundColor(.gray)
|
||||
.font(.callout)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Section(header: Text("mqtt")) {
|
||||
Toggle(isOn: $uplink) {
|
||||
Label("Uplink Enabled", systemImage: "arrowshape.up")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
.listRowSeparator(.visible)
|
||||
|
||||
Toggle(isOn: $downlink) {
|
||||
Label("Downlink Enabled", systemImage: "arrowshape.down")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
.listRowSeparator(.visible)
|
||||
}
|
||||
}
|
||||
ChannelForm(channelIndex: $channelIndex, channelName: $channelName, channelKeySize: $channelKeySize, channelKey: $channelKey, channelRole: $channelRole, uplink: $uplink, downlink: $downlink, positionPrecision: $positionPrecision, preciseLocation: $preciseLocation, positionsEnabled: $positionsEnabled, hasChanges: $hasChanges, hasValidKey: $hasValidKey, supportedVersion: $supportedVersion)
|
||||
.onAppear {
|
||||
let tempKey = Data(base64Encoded: channelKey) ?? Data()
|
||||
if tempKey.count == channelKeySize || channelKeySize == -1{
|
||||
hasValidKey = true
|
||||
}
|
||||
else {
|
||||
hasValidKey = false
|
||||
}
|
||||
}
|
||||
.onChange(of: channelName) { _ in
|
||||
hasChanges = true
|
||||
}
|
||||
.onChange(of: channelKeySize) { _ in
|
||||
if channelKeySize == -1 {
|
||||
channelKey = "AQ=="
|
||||
} else {
|
||||
let key = generateChannelKey(size: channelKeySize)
|
||||
channelKey = key
|
||||
}
|
||||
hasChanges = true
|
||||
}
|
||||
.onChange(of: channelKey) { _ in
|
||||
hasChanges = true
|
||||
}
|
||||
.onChange(of: channelRole) { _ in
|
||||
hasChanges = true
|
||||
}
|
||||
.onChange(of: preciseLocation) { loc in
|
||||
if loc {
|
||||
positionPrecision = 32
|
||||
} else {
|
||||
positionPrecision = 14
|
||||
}
|
||||
hasChanges = true
|
||||
}
|
||||
.onChange(of: positionPrecision) { _ in
|
||||
hasChanges = true
|
||||
}
|
||||
.onChange(of: positionsEnabled) { pe in
|
||||
if pe {
|
||||
if positionPrecision == 0 {
|
||||
positionPrecision = 32
|
||||
}
|
||||
} else {
|
||||
positionPrecision = 0
|
||||
}
|
||||
hasChanges = true
|
||||
}
|
||||
.onChange(of: uplink) { _ in
|
||||
hasChanges = true
|
||||
}
|
||||
.onChange(of: downlink) { _ in
|
||||
hasChanges = true
|
||||
supportedVersion = bleManager.connectedVersion == "0.0.0" || self.minimumVersion.compare(bleManager.connectedVersion, options: .numeric) == .orderedAscending || minimumVersion.compare(bleManager.connectedVersion, options: .numeric) == .orderedSame
|
||||
}
|
||||
HStack {
|
||||
Button {
|
||||
|
|
@ -420,11 +202,10 @@ struct Channels: View {
|
|||
let adminMessageId = bleManager.saveChannel(channel: channel, fromUser: node!.user!, toUser: node!.user!)
|
||||
|
||||
if adminMessageId > 0 {
|
||||
self.isPresentingEditView = false
|
||||
selectedChannel = nil
|
||||
channelName = ""
|
||||
channelRole = 2
|
||||
hasChanges = false
|
||||
//_ = bleManager.getChannel(channel: channel, fromUser: node!.user!, toUser: node!.user!)
|
||||
}
|
||||
} label: {
|
||||
Label("save", systemImage: "square.and.arrow.down")
|
||||
|
|
@ -436,7 +217,7 @@ struct Channels: View {
|
|||
.padding(.bottom)
|
||||
#if targetEnvironment(macCatalyst)
|
||||
Button {
|
||||
isPresentingEditView = false
|
||||
goBack()
|
||||
} label: {
|
||||
Label("close", systemImage: "xmark")
|
||||
}
|
||||
|
|
@ -468,7 +249,7 @@ struct Channels: View {
|
|||
uplink = false
|
||||
downlink = false
|
||||
hasChanges = true
|
||||
isPresentingEditView = true
|
||||
selectedChannel = ChannelEntity(context: context)
|
||||
|
||||
} label: {
|
||||
Label("Add Channel", systemImage: "plus.square")
|
||||
|
|
|
|||
251
Meshtastic/Views/Settings/Channels/ChannelForm.swift
Normal file
251
Meshtastic/Views/Settings/Channels/ChannelForm.swift
Normal file
|
|
@ -0,0 +1,251 @@
|
|||
//
|
||||
// ChannelForm.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Copyright(c) Garth Vander Houwen 3/17/24.
|
||||
//
|
||||
|
||||
|
||||
import SwiftUI
|
||||
#if canImport(MapKit)
|
||||
import MapKit
|
||||
#endif
|
||||
|
||||
struct ChannelForm: View {
|
||||
|
||||
@Binding var channelIndex: Int32
|
||||
@Binding var channelName: String
|
||||
@Binding var channelKeySize: Int
|
||||
@Binding var channelKey: String
|
||||
@Binding var channelRole: Int
|
||||
@Binding var uplink: Bool
|
||||
@Binding var downlink: Bool
|
||||
@Binding var positionPrecision: Double
|
||||
@Binding var preciseLocation: Bool
|
||||
@Binding var positionsEnabled: Bool
|
||||
@Binding var hasChanges: Bool
|
||||
@Binding var hasValidKey: Bool
|
||||
@Binding var supportedVersion: Bool
|
||||
|
||||
var body: some View {
|
||||
|
||||
NavigationStack {
|
||||
Form {
|
||||
Section(header: Text("channel details")) {
|
||||
HStack {
|
||||
Text("name")
|
||||
Spacer()
|
||||
TextField(
|
||||
"Channel Name",
|
||||
text: $channelName
|
||||
)
|
||||
.disableAutocorrection(true)
|
||||
.keyboardType(.alphabet)
|
||||
.foregroundColor(Color.gray)
|
||||
.onChange(of: channelName, perform: { _ in
|
||||
channelName = channelName.replacing(" ", with: "")
|
||||
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
|
||||
}
|
||||
}
|
||||
hasChanges = true
|
||||
})
|
||||
}
|
||||
HStack {
|
||||
Picker("Key Size", selection: $channelKeySize) {
|
||||
Text("Empty").tag(0)
|
||||
Text("Default").tag(-1)
|
||||
Text("1 byte").tag(1)
|
||||
Text("128 bit").tag(16)
|
||||
Text("192 bit").tag(24)
|
||||
Text("256 bit").tag(32)
|
||||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
Spacer()
|
||||
Button {
|
||||
if channelKeySize == -1 {
|
||||
channelKey = "AQ=="
|
||||
} else {
|
||||
let key = generateChannelKey(size: channelKeySize)
|
||||
channelKey = key
|
||||
}
|
||||
} label: {
|
||||
Image(systemName: "lock.rotation")
|
||||
.font(.title)
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
.buttonBorderShape(.capsule)
|
||||
.controlSize(.small)
|
||||
}
|
||||
HStack(alignment: .center) {
|
||||
Text("Key")
|
||||
Spacer()
|
||||
TextField(
|
||||
"Key",
|
||||
text: $channelKey,
|
||||
axis: .vertical
|
||||
)
|
||||
.padding(6)
|
||||
.disableAutocorrection(true)
|
||||
.keyboardType(.alphabet)
|
||||
.foregroundColor(Color.gray)
|
||||
.textSelection(.enabled)
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 10.0)
|
||||
.stroke(
|
||||
hasValidKey ?
|
||||
Color.clear :
|
||||
Color.red
|
||||
, lineWidth: 2.0)
|
||||
|
||||
)
|
||||
.onChange(of: channelKey, perform: { _ in
|
||||
let tempKey = Data(base64Encoded: channelKey) ?? Data()
|
||||
if tempKey.count == channelKeySize || channelKeySize == -1{
|
||||
hasValidKey = true
|
||||
}
|
||||
else {
|
||||
hasValidKey = false
|
||||
}
|
||||
hasChanges = true
|
||||
})
|
||||
.disabled(channelKeySize <= 0)
|
||||
}
|
||||
HStack {
|
||||
if channelRole == 1 {
|
||||
Picker("Channel Role", selection: $channelRole) {
|
||||
Text("Primary").tag(1)
|
||||
}
|
||||
.pickerStyle(.automatic)
|
||||
.disabled(true)
|
||||
} else {
|
||||
Text("Channel Role")
|
||||
Spacer()
|
||||
Picker("Channel Role", selection: $channelRole) {
|
||||
Text("Disabled").tag(0)
|
||||
Text("Secondary").tag(2)
|
||||
}
|
||||
.pickerStyle(.segmented)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Section(header: Text("position")) {
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
Toggle(isOn: $positionsEnabled) {
|
||||
Label(channelRole == 1 ? "Positions Enabled" : "Allow Position Requests", systemImage: positionsEnabled ? "mappin" : "mappin.slash")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
.disabled(!supportedVersion)
|
||||
}
|
||||
|
||||
if positionsEnabled {
|
||||
VStack(alignment: .leading) {
|
||||
Toggle(isOn: $preciseLocation) {
|
||||
Label("Precise Location", systemImage: "scope")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
.disabled(!supportedVersion)
|
||||
.listRowSeparator(.visible)
|
||||
.onChange(of: preciseLocation) { pl in
|
||||
if pl == false {
|
||||
positionPrecision = 13
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !preciseLocation {
|
||||
VStack(alignment: .leading) {
|
||||
Label("Approximate Location", systemImage: "location.slash.circle.fill")
|
||||
Slider(value: $positionPrecision, in: 11...16, step: 1) {
|
||||
} minimumValueLabel: {
|
||||
Image(systemName: "minus")
|
||||
} maximumValueLabel: {
|
||||
Image(systemName: "plus")
|
||||
}
|
||||
Text(PositionPrecision(rawValue: Int(positionPrecision))?.description ?? "")
|
||||
.foregroundColor(.gray)
|
||||
.font(.callout)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Section(header: Text("mqtt")) {
|
||||
Toggle(isOn: $uplink) {
|
||||
Label("Uplink Enabled", systemImage: "arrowshape.up")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
.listRowSeparator(.visible)
|
||||
|
||||
Toggle(isOn: $downlink) {
|
||||
Label("Downlink Enabled", systemImage: "arrowshape.down")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
.listRowSeparator(.visible)
|
||||
}
|
||||
}
|
||||
.onChange(of: channelName) { _ in
|
||||
hasChanges = true
|
||||
}
|
||||
.onChange(of: channelKeySize) { _ in
|
||||
if channelKeySize == -1 {
|
||||
channelKey = "AQ=="
|
||||
} else {
|
||||
let key = generateChannelKey(size: channelKeySize)
|
||||
channelKey = key
|
||||
}
|
||||
hasChanges = true
|
||||
}
|
||||
.onChange(of: channelKey) { _ in
|
||||
hasChanges = true
|
||||
}
|
||||
.onChange(of: channelRole) { _ in
|
||||
hasChanges = true
|
||||
}
|
||||
.onChange(of: preciseLocation) { loc in
|
||||
if loc == true {
|
||||
positionPrecision = 32
|
||||
} else {
|
||||
positionPrecision = 14
|
||||
}
|
||||
hasChanges = true
|
||||
}
|
||||
.onChange(of: positionPrecision) { _ in
|
||||
hasChanges = true
|
||||
}
|
||||
.onChange(of: positionsEnabled) { pe in
|
||||
if pe {
|
||||
if positionPrecision == 0 {
|
||||
positionPrecision = 32
|
||||
}
|
||||
} else {
|
||||
positionPrecision = 0
|
||||
}
|
||||
hasChanges = true
|
||||
}
|
||||
.onChange(of: uplink) { _ in
|
||||
hasChanges = true
|
||||
}
|
||||
.onChange(of: downlink) { _ in
|
||||
hasChanges = true
|
||||
}
|
||||
.onAppear {
|
||||
let tempKey = Data(base64Encoded: channelKey) ?? Data()
|
||||
if tempKey.count == channelKeySize || channelKeySize == -1 {
|
||||
hasValidKey = true
|
||||
}
|
||||
else {
|
||||
hasValidKey = false
|
||||
}
|
||||
}
|
||||
}
|
||||
.presentationDetents([.large])
|
||||
.presentationDragIndicator(.visible)
|
||||
}
|
||||
}
|
||||
|
|
@ -23,7 +23,7 @@ struct DeviceConfig: View {
|
|||
@State var serialEnabled = true
|
||||
@State var debugLogEnabled = false
|
||||
@State var rebroadcastMode = 0
|
||||
@State var nodeInfoBroadcastSecs = 900
|
||||
@State var nodeInfoBroadcastSecs = 10800
|
||||
@State var doubleTapAsButtonPress = false
|
||||
@State var isManaged = false
|
||||
|
||||
|
|
@ -179,6 +179,10 @@ struct DeviceConfig: View {
|
|||
dc.nodeInfoBroadcastSecs = UInt32(nodeInfoBroadcastSecs)
|
||||
dc.doubleTapAsButtonPress = doubleTapAsButtonPress
|
||||
dc.isManaged = isManaged
|
||||
if isManaged {
|
||||
serialEnabled = false
|
||||
debugLogEnabled = false
|
||||
}
|
||||
let adminMessageId = bleManager.saveDeviceConfig(config: dc, fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0)
|
||||
if adminMessageId > 0 {
|
||||
// Should show a saved successfully alert once I know that to be true
|
||||
|
|
@ -264,6 +268,9 @@ struct DeviceConfig: View {
|
|||
self.buzzerGPIO = Int(node?.deviceConfig?.buzzerGpio ?? 0)
|
||||
self.rebroadcastMode = Int(node?.deviceConfig?.rebroadcastMode ?? 0)
|
||||
self.nodeInfoBroadcastSecs = Int(node?.deviceConfig?.nodeInfoBroadcastSecs ?? 900)
|
||||
if nodeInfoBroadcastSecs < 3600 {
|
||||
nodeInfoBroadcastSecs = 3600
|
||||
}
|
||||
self.doubleTapAsButtonPress = node?.deviceConfig?.doubleTapAsButtonPress ?? false
|
||||
self.isManaged = node?.deviceConfig?.isManaged ?? false
|
||||
self.hasChanges = false
|
||||
|
|
|
|||
|
|
@ -130,12 +130,12 @@ struct LoRaConfig: View {
|
|||
}
|
||||
VStack(alignment: .leading) {
|
||||
Picker("Number of hops", selection: $hopLimit) {
|
||||
ForEach(1..<8) {
|
||||
ForEach(0..<8) {
|
||||
Text("\($0)")
|
||||
.tag($0 == 0 ? 3 : $0)
|
||||
.tag($0)
|
||||
}
|
||||
}
|
||||
Text("Sets the maximum number of hops, default is 3. Increasing hops also increases congestion and should be used carefully.")
|
||||
Text("Sets the maximum number of hops, default is 3. Increasing hops also increases congestion and should be used carefully. O hop broadcast messages will not get ACKs.")
|
||||
.foregroundColor(.gray)
|
||||
.font(.callout)
|
||||
}
|
||||
|
|
@ -181,7 +181,7 @@ struct LoRaConfig: View {
|
|||
HStack {
|
||||
Image(systemName: "antenna.radiowaves.left.and.right")
|
||||
.foregroundColor(.accentColor)
|
||||
Stepper("\(txPower)db Transmit Power", value: $txPower, in: 1...30, step: 1)
|
||||
Stepper("\(txPower)dBm Transmit Power", value: $txPower, in: 1...30, step: 1)
|
||||
.padding(5)
|
||||
}
|
||||
}
|
||||
|
|
@ -205,6 +205,9 @@ struct LoRaConfig: View {
|
|||
lc.sx126XRxBoostedGain = rxBoostedGain
|
||||
lc.overrideFrequency = overrideFrequency
|
||||
lc.ignoreMqtt = ignoreMqtt
|
||||
if connectedNode?.num ?? -1 == node?.user?.num ?? 0 {
|
||||
UserDefaults.modemPreset = modemPreset
|
||||
}
|
||||
let adminMessageId = bleManager.saveLoRaConfig(config: lc, fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0)
|
||||
if adminMessageId > 0 {
|
||||
// Should show a saved successfully alert once I know that to be true
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
//
|
||||
import SwiftUI
|
||||
|
||||
enum DetectionSensorRole: String, CaseIterable, Equatable {
|
||||
enum DetectionSensorRole: String, CaseIterable, Equatable, Decodable {
|
||||
case sensor
|
||||
case client
|
||||
var description: String {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
// Copyright (c) Garth Vander Houwen 9/4/22.
|
||||
//
|
||||
import SwiftUI
|
||||
import CoreLocation
|
||||
|
||||
struct MQTTConfig: View {
|
||||
|
||||
|
|
@ -23,9 +24,17 @@ struct MQTTConfig: View {
|
|||
@State var jsonEnabled = false
|
||||
@State var tlsEnabled = true
|
||||
@State var root = "msh"
|
||||
@State var selectedTopic = ""
|
||||
@State var mqttConnected: Bool = false
|
||||
@State var defaultTopic = "msh/US"
|
||||
@State var nearbyTopics = [String]()
|
||||
@State var mapReportingEnabled = false
|
||||
@State var mapPublishIntervalSecs = 3600
|
||||
@State var preciseLocation: Bool = false
|
||||
@State var mapPositionPrecision: Double = 13.0
|
||||
|
||||
|
||||
let locale = Locale.current
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
|
|
@ -44,18 +53,18 @@ struct MQTTConfig: View {
|
|||
Section(header: Text("options")) {
|
||||
|
||||
Toggle(isOn: $enabled) {
|
||||
Label("enabled", systemImage: "dot.radiowaves.right")
|
||||
Label("enabled", systemImage: "dot.radiowaves.up.forward")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
|
||||
Toggle(isOn: $proxyToClientEnabled) {
|
||||
|
||||
Label("mqtt.clientproxy", systemImage: "iphone.radiowaves.left.and.right")
|
||||
Text("If both MQTT and the client proxy are enabled your mobile device will utilize an available network connection to connect to the specified MQTT server.")
|
||||
Text("Utilizes the network connection on your phone to connect to MQTT.")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
|
||||
if enabled && proxyToClientEnabled {
|
||||
if enabled && proxyToClientEnabled && node!.mqttConfig!.proxyToClientEnabled == true {
|
||||
Toggle(isOn: $mqttConnected) {
|
||||
Label(mqttConnected ? "mqtt.disconnect".localized : "mqtt.connect".localized, systemImage: "server.rack")
|
||||
}
|
||||
|
|
@ -69,18 +78,99 @@ struct MQTTConfig: View {
|
|||
|
||||
Toggle(isOn: $jsonEnabled) {
|
||||
Label("JSON Enabled", systemImage: "ellipsis.curlybraces")
|
||||
Text("JSON mode is a limited, unencrypted MQTT output that can crash your node it should not be enabled unless you are locally integrating with home assistant")
|
||||
Text("JSON mode is a limited, unencrypted MQTT output for locally integrating with home assistant")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
|
||||
Toggle(isOn: $tlsEnabled) {
|
||||
Label("TLS Enabled", systemImage: "checkmark.shield.fill")
|
||||
Text("Your MQTT Server must support TLS.")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
|
||||
}
|
||||
Section(header: Text("Custom Server")) {
|
||||
|
||||
Section(header: Text("Map Report")) {
|
||||
|
||||
Toggle(isOn: $mapReportingEnabled) {
|
||||
Label("enabled", systemImage: "map")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
if mapReportingEnabled {
|
||||
Picker("Map Publish Interval", selection: $mapPublishIntervalSecs ) {
|
||||
ForEach(UpdateIntervals.allCases) { ui in
|
||||
if ui.rawValue >= 3600 {
|
||||
Text(ui.description)
|
||||
}
|
||||
}
|
||||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
Toggle(isOn: $preciseLocation) {
|
||||
Label("Precise Location", systemImage: "scope")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
.listRowSeparator(.visible)
|
||||
.onChange(of: preciseLocation) { pl in
|
||||
if pl == false {
|
||||
mapPositionPrecision = 12
|
||||
} else {
|
||||
mapPositionPrecision = 32
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !preciseLocation {
|
||||
VStack(alignment: .leading) {
|
||||
Label("Approximate Location", systemImage: "location.slash.circle.fill")
|
||||
Slider(value: $mapPositionPrecision, in: 11...16, step: 1) {
|
||||
} minimumValueLabel: {
|
||||
Image(systemName: "minus")
|
||||
} maximumValueLabel: {
|
||||
Image(systemName: "plus")
|
||||
}
|
||||
Text(PositionPrecision(rawValue: Int(mapPositionPrecision))?.description ?? "")
|
||||
.foregroundColor(.gray)
|
||||
.font(.callout)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Section(header: Text("Root Topic")) {
|
||||
HStack {
|
||||
Label("Root Topic", systemImage: "tree")
|
||||
TextField("Root Topic", text: $root)
|
||||
.foregroundColor(.gray)
|
||||
.onChange(of: root, perform: { _ in
|
||||
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
|
||||
}
|
||||
}
|
||||
})
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
.keyboardType(.asciiCapable)
|
||||
.scrollDismissesKeyboard(.interactively)
|
||||
.disableAutocorrection(true)
|
||||
.listRowSeparator(.hidden)
|
||||
Text("The root topic to use for MQTT.")
|
||||
.foregroundColor(.gray)
|
||||
.font(.callout)
|
||||
|
||||
if nearbyTopics.count > 0 {
|
||||
Picker("Nearby Topics", selection: $selectedTopic ) {
|
||||
ForEach(nearbyTopics, id: \.self) { nt in
|
||||
Text(nt)
|
||||
}
|
||||
}
|
||||
.pickerStyle(InlinePickerStyle())
|
||||
.listRowSeparator(.hidden)
|
||||
Text("If the default region topic is too busy you can choose a more local topic.")
|
||||
.foregroundColor(.gray)
|
||||
.font(.callout)
|
||||
}
|
||||
}
|
||||
|
||||
Section(header: Text("Server")) {
|
||||
HStack {
|
||||
Label("Address", systemImage: "server.rack")
|
||||
TextField("Server Address", text: $address)
|
||||
|
|
@ -157,31 +247,14 @@ struct MQTTConfig: View {
|
|||
}
|
||||
.keyboardType(.default)
|
||||
.scrollDismissesKeyboard(.interactively)
|
||||
HStack {
|
||||
Label("Root Topic", systemImage: "tree")
|
||||
TextField("Root Topic", text: $root)
|
||||
.foregroundColor(.gray)
|
||||
.onChange(of: root, perform: { _ in
|
||||
let totalBytes = root.utf8.count
|
||||
// Only mess with the value if it is too big
|
||||
if totalBytes > 14 {
|
||||
let firstNBytes = Data(root.utf8.prefix(14))
|
||||
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
|
||||
}
|
||||
}
|
||||
})
|
||||
.foregroundColor(.gray)
|
||||
.listRowSeparator(/*@START_MENU_TOKEN@*/.visible/*@END_MENU_TOKEN@*/)
|
||||
Toggle(isOn: $tlsEnabled) {
|
||||
Label("TLS Enabled", systemImage: "checkmark.shield.fill")
|
||||
Text("Your MQTT Server must support TLS.")
|
||||
}
|
||||
.keyboardType(.asciiCapable)
|
||||
.scrollDismissesKeyboard(.interactively)
|
||||
.disableAutocorrection(true)
|
||||
Text("The root topic to use for MQTT messages. Default is \"msh\". This is useful if you want to use a single MQTT server for multiple meshtastic networks and separate them via ACLs")
|
||||
.foregroundColor(.gray)
|
||||
.font(.callout)
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
}
|
||||
Text("You can set uplink and downlink for each channel.")
|
||||
Text("For all Mqtt functionality other than the map report you must also set uplink and downlink for each channel you want to bridge over Mqtt.")
|
||||
.font(.callout)
|
||||
}
|
||||
.scrollDismissesKeyboard(.interactively)
|
||||
|
|
@ -201,6 +274,9 @@ struct MQTTConfig: View {
|
|||
mqtt.encryptionEnabled = self.encryptionEnabled
|
||||
mqtt.jsonEnabled = self.jsonEnabled
|
||||
mqtt.tlsEnabled = self.tlsEnabled
|
||||
mqtt.mapReportingEnabled = self.mapReportingEnabled
|
||||
mqtt.mapReportSettings.positionPrecision = UInt32(self.mapPositionPrecision)
|
||||
mqtt.mapReportSettings.publishIntervalSecs = UInt32(self.mapPublishIntervalSecs)
|
||||
let adminMessageId = bleManager.saveMQTTConfig(config: mqtt, fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0)
|
||||
if adminMessageId > 0 {
|
||||
// Should show a saved successfully alert once I know that to be true
|
||||
|
|
@ -215,20 +291,6 @@ struct MQTTConfig: View {
|
|||
ZStack {
|
||||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?", mqttProxyConnected: bleManager.mqttProxyConnected)
|
||||
})
|
||||
.onAppear {
|
||||
if self.bleManager.context == nil {
|
||||
self.bleManager.context = context
|
||||
}
|
||||
setMqttValues()
|
||||
// Need to request a TelemetryModuleConfig from the remote node before allowing changes
|
||||
if bleManager.connectedPeripheral != nil && node?.mqttConfig == nil {
|
||||
print("empty mqtt module config")
|
||||
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context)
|
||||
if node != nil && connectedNode != nil {
|
||||
_ = bleManager.requestMqttModuleConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
.onChange(of: address) { newAddress in
|
||||
if node != nil && node?.mqttConfig != nil {
|
||||
if newAddress != node!.mqttConfig!.address { hasChanges = true }
|
||||
|
|
@ -249,12 +311,18 @@ struct MQTTConfig: View {
|
|||
if newRoot != node!.mqttConfig!.root { hasChanges = true }
|
||||
}
|
||||
}
|
||||
.onChange(of: selectedTopic) { newSelectedTopic in
|
||||
root = newSelectedTopic
|
||||
}
|
||||
.onChange(of: enabled) { newEnabled in
|
||||
if node != nil && node?.mqttConfig != nil {
|
||||
if newEnabled != node!.mqttConfig!.enabled { hasChanges = true }
|
||||
}
|
||||
}
|
||||
.onChange(of: proxyToClientEnabled) { newProxyToClientEnabled in
|
||||
if newProxyToClientEnabled {
|
||||
jsonEnabled = false
|
||||
}
|
||||
if node != nil && node?.mqttConfig != nil {
|
||||
if newProxyToClientEnabled != node!.mqttConfig!.proxyToClientEnabled { hasChanges = true }
|
||||
if newProxyToClientEnabled {
|
||||
|
|
@ -268,6 +336,9 @@ struct MQTTConfig: View {
|
|||
}
|
||||
}
|
||||
.onChange(of: jsonEnabled) { newJsonEnabled in
|
||||
if newJsonEnabled {
|
||||
proxyToClientEnabled = false
|
||||
}
|
||||
if node != nil && node?.mqttConfig != nil {
|
||||
if newJsonEnabled != node!.mqttConfig!.jsonEnabled { hasChanges = true }
|
||||
}
|
||||
|
|
@ -288,18 +359,102 @@ struct MQTTConfig: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
.onChange(of: mapReportingEnabled) { newMapReportingEnabled in
|
||||
if node != nil && node?.mqttConfig != nil {
|
||||
if newMapReportingEnabled != node!.mqttConfig!.mapReportingEnabled { hasChanges = true }
|
||||
}
|
||||
}
|
||||
.onChange(of: preciseLocation) { _ in
|
||||
hasChanges = true
|
||||
}
|
||||
.onChange(of: mapPublishIntervalSecs) { newMapPublishIntervalSecs in
|
||||
if node != nil && node?.mqttConfig != nil {
|
||||
if newMapPublishIntervalSecs != node!.mqttConfig!.mapPublishIntervalSecs { hasChanges = true }
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
if self.bleManager.context == nil {
|
||||
self.bleManager.context = context
|
||||
}
|
||||
setMqttValues()
|
||||
// Need to request a TelemetryModuleConfig from the remote node before allowing changes
|
||||
if bleManager.connectedPeripheral != nil && node?.mqttConfig == nil {
|
||||
print("empty mqtt module config")
|
||||
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context)
|
||||
if node != nil && connectedNode != nil {
|
||||
_ = bleManager.requestMqttModuleConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
func setMqttValues() {
|
||||
self.enabled = (node?.mqttConfig?.enabled ?? false)
|
||||
self.proxyToClientEnabled = (node?.mqttConfig?.proxyToClientEnabled ?? false)
|
||||
|
||||
if #available(iOS 17.0, macOS 14.0, *) {
|
||||
|
||||
nearbyTopics = []
|
||||
let geocoder = CLGeocoder()
|
||||
if LocationsHandler.shared.locationsArray.count > 0 {
|
||||
let region = RegionCodes(rawValue: Int(node?.loRaConfig?.regionCode ?? 0))?.topic
|
||||
defaultTopic = "msh/" + (region ?? "UNSET")
|
||||
geocoder.reverseGeocodeLocation(LocationsHandler.shared.locationsArray.first!, completionHandler: {(placemarks, error) -> Void in
|
||||
if error != nil {
|
||||
print("Failed to reverse geocode location")
|
||||
return
|
||||
}
|
||||
|
||||
if let placemarks = placemarks, let placemark = placemarks.first {
|
||||
let cc = locale.region?.identifier ?? "UNK"
|
||||
/// Country Topic unless you are US
|
||||
if placemark.isoCountryCode ?? "unknown" != cc {
|
||||
let countryTopic = defaultTopic + "/" + (placemark.isoCountryCode ?? "")
|
||||
if !countryTopic.isEmpty {
|
||||
nearbyTopics.append(countryTopic)
|
||||
}
|
||||
}
|
||||
let stateTopic = defaultTopic + "/" + (placemark.administrativeArea ?? "")
|
||||
if !stateTopic.isEmpty {
|
||||
nearbyTopics.append(stateTopic)
|
||||
}
|
||||
let countyTopic = defaultTopic + "/" + (placemark.subAdministrativeArea?.lowercased().replacingOccurrences(of: " ", with: "") ?? "")
|
||||
if !countyTopic.isEmpty {
|
||||
nearbyTopics.append(countyTopic)
|
||||
}
|
||||
let cityTopic = defaultTopic + "/" + (placemark.locality?.lowercased().replacingOccurrences(of: " ", with: "") ?? "")
|
||||
if !cityTopic.isEmpty {
|
||||
nearbyTopics.append(cityTopic)
|
||||
}
|
||||
let neightborhoodTopic = defaultTopic + "/" + (placemark.subLocality?.lowercased()
|
||||
.replacingOccurrences(of: " ", with: "")
|
||||
.replacingOccurrences(of: "'", with: "") ?? "")
|
||||
if !neightborhoodTopic.isEmpty {
|
||||
nearbyTopics.append(neightborhoodTopic)
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
print("No Location")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
self.enabled = node?.mqttConfig?.enabled ?? false
|
||||
self.proxyToClientEnabled = node?.mqttConfig?.proxyToClientEnabled ?? false
|
||||
self.address = node?.mqttConfig?.address ?? ""
|
||||
self.username = node?.mqttConfig?.username ?? ""
|
||||
self.password = node?.mqttConfig?.password ?? ""
|
||||
self.root = node?.mqttConfig?.root ?? "msh"
|
||||
self.encryptionEnabled = (node?.mqttConfig?.encryptionEnabled ?? false)
|
||||
self.jsonEnabled = (node?.mqttConfig?.jsonEnabled ?? false)
|
||||
self.tlsEnabled = (node?.mqttConfig?.tlsEnabled ?? false)
|
||||
self.encryptionEnabled = node?.mqttConfig?.encryptionEnabled ?? false
|
||||
self.jsonEnabled = node?.mqttConfig?.jsonEnabled ?? false
|
||||
self.tlsEnabled = node?.mqttConfig?.tlsEnabled ?? false
|
||||
self.mqttConnected = bleManager.mqttProxyConnected
|
||||
self.mapReportingEnabled = node?.mqttConfig?.mapReportingEnabled ?? false
|
||||
self.mapPublishIntervalSecs = Int(node?.mqttConfig?.mapPublishIntervalSecs ?? 3600)
|
||||
self.mapPositionPrecision = Double(node?.mqttConfig?.mapPositionPrecision ?? 12)
|
||||
if mapPositionPrecision == 0.0 {
|
||||
self.mapPositionPrecision = 12
|
||||
}
|
||||
self.preciseLocation = mapPositionPrecision == 32
|
||||
self.hasChanges = false
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,10 @@ struct TelemetryConfig: View {
|
|||
@State var environmentMeasurementEnabled = false
|
||||
@State var environmentScreenEnabled = false
|
||||
@State var environmentDisplayFahrenheit = false
|
||||
@State var powerMeasurementEnabled = false
|
||||
@State var powerUpdateInterval = 0
|
||||
@State var powerScreenEnabled = false
|
||||
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
|
|
@ -30,7 +34,9 @@ struct TelemetryConfig: View {
|
|||
Section(header: Text("update.interval")) {
|
||||
Picker("Device Metrics", selection: $deviceUpdateInterval ) {
|
||||
ForEach(UpdateIntervals.allCases) { ui in
|
||||
Text(ui.description)
|
||||
if ui.rawValue >= 900 {
|
||||
Text(ui.description)
|
||||
}
|
||||
}
|
||||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
|
|
@ -41,7 +47,9 @@ struct TelemetryConfig: View {
|
|||
.listRowSeparator(.visible)
|
||||
Picker("Sensor Metrics", selection: $environmentUpdateInterval ) {
|
||||
ForEach(UpdateIntervals.allCases) { ui in
|
||||
Text(ui.description)
|
||||
if ui.rawValue >= 900 {
|
||||
Text(ui.description)
|
||||
}
|
||||
}
|
||||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
|
|
@ -67,6 +75,30 @@ struct TelemetryConfig: View {
|
|||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
}
|
||||
Section(header: Text("Power Options")) {
|
||||
Toggle(isOn: $powerMeasurementEnabled) {
|
||||
Label("enabled", systemImage: "bolt")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
.listRowSeparator(.visible)
|
||||
Picker("Power Metrics", selection: $powerUpdateInterval ) {
|
||||
ForEach(UpdateIntervals.allCases) { ui in
|
||||
if ui.rawValue >= 900 {
|
||||
Text(ui.description)
|
||||
}
|
||||
}
|
||||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
.listRowSeparator(.hidden)
|
||||
Text("How often power metrics are sent out over the mesh. Default is 15 minutes.")
|
||||
.foregroundColor(.gray)
|
||||
.font(.callout)
|
||||
.listRowSeparator(.visible)
|
||||
Toggle(isOn: $powerScreenEnabled) {
|
||||
Label("Power Screen", systemImage: "tv")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
}
|
||||
}
|
||||
.disabled(self.bleManager.connectedPeripheral == nil || node?.telemetryConfig == nil)
|
||||
|
||||
|
|
@ -79,6 +111,9 @@ struct TelemetryConfig: View {
|
|||
tc.environmentMeasurementEnabled = environmentMeasurementEnabled
|
||||
tc.environmentScreenEnabled = environmentScreenEnabled
|
||||
tc.environmentDisplayFahrenheit = environmentDisplayFahrenheit
|
||||
tc.powerMeasurementEnabled = powerMeasurementEnabled
|
||||
tc.powerUpdateInterval = UInt32(powerUpdateInterval)
|
||||
tc.powerScreenEnabled = powerScreenEnabled
|
||||
let adminMessageId = bleManager.saveTelemetryModuleConfig(config: tc, fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0)
|
||||
if adminMessageId > 0 {
|
||||
// Should show a saved successfully alert once I know that to be true
|
||||
|
|
@ -132,14 +167,32 @@ struct TelemetryConfig: View {
|
|||
if newEnvDisplayF != node!.telemetryConfig!.environmentDisplayFahrenheit { hasChanges = true }
|
||||
}
|
||||
}
|
||||
.onChange(of: powerMeasurementEnabled) { newPowerMeasurementEnabled in
|
||||
if node != nil && node?.telemetryConfig != nil {
|
||||
if newPowerMeasurementEnabled != node!.telemetryConfig!.powerMeasurementEnabled { hasChanges = true }
|
||||
}
|
||||
}
|
||||
.onChange(of: powerUpdateInterval) { newPowerUpdateInterval in
|
||||
if node != nil && node?.telemetryConfig != nil {
|
||||
if newPowerUpdateInterval != node!.telemetryConfig!.powerUpdateInterval { hasChanges = true }
|
||||
}
|
||||
}
|
||||
.onChange(of: powerScreenEnabled) { newPowerScreenEnabled in
|
||||
if node != nil && node?.telemetryConfig != nil {
|
||||
if newPowerScreenEnabled != node!.telemetryConfig!.powerScreenEnabled { hasChanges = true }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
func setTelemetryValues() {
|
||||
self.deviceUpdateInterval = Int(node?.telemetryConfig?.deviceUpdateInterval ?? 0)
|
||||
self.environmentUpdateInterval = Int(node?.telemetryConfig?.environmentUpdateInterval ?? 0)
|
||||
self.deviceUpdateInterval = Int(node?.telemetryConfig?.deviceUpdateInterval ?? 900)
|
||||
self.environmentUpdateInterval = Int(node?.telemetryConfig?.environmentUpdateInterval ?? 900)
|
||||
self.environmentMeasurementEnabled = node?.telemetryConfig?.environmentMeasurementEnabled ?? false
|
||||
self.environmentScreenEnabled = node?.telemetryConfig?.environmentScreenEnabled ?? false
|
||||
self.environmentDisplayFahrenheit = node?.telemetryConfig?.environmentDisplayFahrenheit ?? false
|
||||
self.powerMeasurementEnabled = node?.telemetryConfig?.powerMeasurementEnabled ?? false
|
||||
self.powerUpdateInterval = Int(node?.telemetryConfig?.powerUpdateInterval ?? 900)
|
||||
self.powerScreenEnabled = node?.telemetryConfig?.powerScreenEnabled ?? false
|
||||
self.hasChanges = false
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,6 +72,12 @@ struct PositionConfig: View {
|
|||
/// walking speeds are likely to be error prone like the compass
|
||||
@State var includeHeading = false
|
||||
|
||||
/// Minimum Version for fixed postion admin messages
|
||||
@State var minimumVersion = "2.3.3"
|
||||
@State private var supportedVersion = true
|
||||
@State private var showingSetFixedAlert = false
|
||||
//@State private var showingRemoveFixedAlert = false
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
Form {
|
||||
|
|
@ -152,14 +158,13 @@ struct PositionConfig: View {
|
|||
.foregroundColor(.gray)
|
||||
.font(.callout)
|
||||
}
|
||||
} else {
|
||||
VStack(alignment: .leading) {
|
||||
Toggle(isOn: $fixedPosition) {
|
||||
Label("Fixed Position", systemImage: "location.square.fill")
|
||||
Text("If enabled your current phone location will be sent to the device and will broadcast over the mesh on the position interval. Fixed position will always use the most recent position the device has.")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
}
|
||||
VStack(alignment: .leading) {
|
||||
Toggle(isOn: $fixedPosition) {
|
||||
Label("Fixed Position", systemImage: "location.square.fill")
|
||||
Text("If enabled your current phone location will be sent to the device and will broadcast over the mesh on the position interval.")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
}
|
||||
}
|
||||
Section(header: Text("Position Flags")) {
|
||||
|
|
@ -263,9 +268,49 @@ struct PositionConfig: View {
|
|||
}
|
||||
}
|
||||
.disabled(self.bleManager.connectedPeripheral == nil || node?.positionConfig == nil)
|
||||
.alert(node?.positionConfig?.fixedPosition ?? false ? "Remove Fixed Position" : "Set Fixed Position", isPresented: $showingSetFixedAlert) {
|
||||
Button("Cancel", role: .cancel) {
|
||||
fixedPosition = !fixedPosition
|
||||
}
|
||||
if node?.positionConfig?.fixedPosition ?? false {
|
||||
Button("Remove", role: .destructive) {
|
||||
if !bleManager.removeFixedPosition(fromUser: node!.user!, channel: 0) {
|
||||
print("Set Position Failed")
|
||||
}
|
||||
print("Remove a fixed position here")
|
||||
node?.positionConfig?.fixedPosition = false
|
||||
do {
|
||||
try context.save()
|
||||
print("💾 Updated Position Config with Fixed Position = false")
|
||||
} catch {
|
||||
context.rollback()
|
||||
let nsError = error as NSError
|
||||
print("💥 Error Saving Position Config Entity \(nsError)")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Button("Set") {
|
||||
if !bleManager.setFixedPosition(fromUser: node!.user!, channel: 0) {
|
||||
print("Set Position Failed")
|
||||
}
|
||||
print("Set a fixed position")
|
||||
node?.positionConfig?.fixedPosition = true
|
||||
do {
|
||||
try context.save()
|
||||
print("💾 Updated Position Config with Fixed Position = true")
|
||||
} catch {
|
||||
context.rollback()
|
||||
let nsError = error as NSError
|
||||
print("💥 Error Saving Position Config Entity \(nsError)")
|
||||
}
|
||||
}
|
||||
}
|
||||
} message: {
|
||||
Text(node?.positionConfig?.fixedPosition ?? false ? "This will disable fixed position and remove the currently set position." : "This will send a current position from your phone and enable fixed position.")
|
||||
}
|
||||
|
||||
SaveConfigButton(node: node, hasChanges: $hasChanges) {
|
||||
if fixedPosition {
|
||||
if fixedPosition && !supportedVersion {
|
||||
_ = bleManager.sendPosition(channel: 0, destNum: node?.num ?? 0, wantResponse: true)
|
||||
}
|
||||
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context)
|
||||
|
|
@ -316,6 +361,7 @@ struct PositionConfig: View {
|
|||
self.bleManager.context = context
|
||||
}
|
||||
setPositionValues()
|
||||
supportedVersion = bleManager.connectedVersion == "0.0.0" || self.minimumVersion.compare(bleManager.connectedVersion, options: .numeric) == .orderedAscending || minimumVersion.compare(bleManager.connectedVersion, options: .numeric) == .orderedSame
|
||||
// Need to request a PositionConfig from the remote node before allowing changes
|
||||
if bleManager.connectedPeripheral != nil && node?.positionConfig == nil {
|
||||
print("empty position config")
|
||||
|
|
@ -325,6 +371,23 @@ struct PositionConfig: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
.onChange(of: fixedPosition) { newFixed in
|
||||
print("Changing Fixed Position Value")
|
||||
if supportedVersion {
|
||||
if node != nil && node!.positionConfig != nil {
|
||||
print("We have a node and position config")
|
||||
print("We have turned on fixed position \(!node!.positionConfig!.fixedPosition && newFixed)")
|
||||
/// Fixed Position is off to start
|
||||
if !node!.positionConfig!.fixedPosition && newFixed {
|
||||
print("fire alert")
|
||||
showingSetFixedAlert = true
|
||||
} else if node!.positionConfig!.fixedPosition && !newFixed {
|
||||
/// Fixed Position is on to start
|
||||
showingSetFixedAlert = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.onChange(of: deviceGpsEnabled) { newDeviceGps in
|
||||
if node != nil && node!.positionConfig != nil {
|
||||
if newDeviceGps != node!.positionConfig!.deviceGpsEnabled { hasChanges = true }
|
||||
|
|
@ -355,11 +418,6 @@ struct PositionConfig: View {
|
|||
if newSmartPositionEnabled != node!.positionConfig!.smartPositionEnabled { hasChanges = true }
|
||||
}
|
||||
}
|
||||
.onChange(of: fixedPosition) { newFixed in
|
||||
if node != nil && node!.positionConfig != nil {
|
||||
if newFixed != node!.positionConfig!.fixedPosition { hasChanges = true }
|
||||
}
|
||||
}
|
||||
.onChange(of: positionBroadcastSeconds) { newPositionBroadcastSeconds in
|
||||
if node != nil && node!.positionConfig != nil {
|
||||
if newPositionBroadcastSeconds != node!.positionConfig!.positionBroadcastSeconds { hasChanges = true }
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ struct Firmware: View {
|
|||
@Environment(\.managedObjectContext) var context
|
||||
@EnvironmentObject var bleManager: BLEManager
|
||||
var node: NodeInfoEntity?
|
||||
@State var minimumVersion = "2.2.21"
|
||||
@State var minimumVersion = "2.3.2"
|
||||
@State var version = ""
|
||||
@State private var currentDevice: DeviceHardware?
|
||||
@State private var latestStable: FirmwareRelease?
|
||||
|
|
|
|||
|
|
@ -6,6 +6,9 @@
|
|||
//
|
||||
|
||||
import SwiftUI
|
||||
#if canImport(TipKit)
|
||||
import TipKit
|
||||
#endif
|
||||
|
||||
struct Settings: View {
|
||||
@Environment(\.managedObjectContext) var context
|
||||
|
|
@ -50,34 +53,43 @@ struct Settings: View {
|
|||
NavigationLink {
|
||||
AboutMeshtastic()
|
||||
} label: {
|
||||
Image(systemName: "questionmark.app")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text("about.meshtastic")
|
||||
Label {
|
||||
Text("about.meshtastic")
|
||||
} icon: {
|
||||
Image(systemName: "questionmark.app")
|
||||
}
|
||||
}
|
||||
.tag(SettingsSidebar.about)
|
||||
NavigationLink {
|
||||
AppSettings()
|
||||
} label: {
|
||||
Image(systemName: "gearshape")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text("appsettings")
|
||||
Label {
|
||||
Text("appsettings")
|
||||
} icon: {
|
||||
Image(systemName: "gearshape")
|
||||
}
|
||||
}
|
||||
.tag(SettingsSidebar.appSettings)
|
||||
if #available(iOS 17.0, macOS 14.0, *) {
|
||||
NavigationLink {
|
||||
Routes()
|
||||
} label: {
|
||||
Image(systemName: "road.lanes.curved.right")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text("routes")
|
||||
Label {
|
||||
Text("routes")
|
||||
} icon: {
|
||||
Image(systemName: "road.lanes.curved.right")
|
||||
}
|
||||
}
|
||||
.tag(SettingsSidebar.routes)
|
||||
NavigationLink {
|
||||
RouteRecorder()
|
||||
} label: {
|
||||
Image(systemName: "record.circle")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text("route.recorder")
|
||||
Label {
|
||||
Text("route.recorder")
|
||||
} icon: {
|
||||
Image(systemName: "record.circle")
|
||||
.foregroundColor(.red)
|
||||
}
|
||||
}
|
||||
.tag(SettingsSidebar.routeRecorder)
|
||||
}
|
||||
|
|
@ -122,6 +134,9 @@ struct Settings: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
if #available(iOS 17.0, macOS 14.0, *) {
|
||||
TipView(AdminChannelTip(), arrowEdge: .top)
|
||||
}
|
||||
} else {
|
||||
if bleManager.connectedPeripheral != nil {
|
||||
Text("Connected Node \(node?.user?.longName ?? "unknown".localized)")
|
||||
|
|
@ -144,7 +159,7 @@ struct Settings: View {
|
|||
Text("Your region has a \(rc?.dutyCycle ?? 0)% hourly duty cycle, your radio will stop sending packets when it reaches the hourly limit.")
|
||||
.foregroundColor(.orange)
|
||||
.font(.caption)
|
||||
Text("Limit all periodic broadcasts intervals especially telemetry and position. If you need to increase hops, do it on nodes at the edges, not the ones in the middle. MQTT is not advised when you are duty cycle restricted because the gateway node is then doing all the work.")
|
||||
Text("Limit all periodic broadcast intervals especially telemetry and position. If you need to increase hops, do it on nodes at the edges, not the ones in the middle. MQTT is not advised when you are duty cycle restricted because the gateway node is then doing all the work.")
|
||||
.font(.caption2)
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
|
|
@ -152,26 +167,33 @@ struct Settings: View {
|
|||
NavigationLink {
|
||||
LoRaConfig(node: nodes.first(where: { $0.num == selectedNode }))
|
||||
} label: {
|
||||
Image(systemName: "dot.radiowaves.left.and.right")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text("lora")
|
||||
Label {
|
||||
Text("lora")
|
||||
} icon: {
|
||||
Image(systemName: "dot.radiowaves.left.and.right")
|
||||
.rotationEffect(.degrees(-90))
|
||||
}
|
||||
}
|
||||
.tag(SettingsSidebar.loraConfig)
|
||||
NavigationLink {
|
||||
Channels(node: nodes.first(where: { $0.num == preferredNodeNum }))
|
||||
} label: {
|
||||
Image(systemName: "fibrechannel")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text("channels")
|
||||
Label {
|
||||
Text("channels")
|
||||
} icon: {
|
||||
Image(systemName: "fibrechannel")
|
||||
}
|
||||
}
|
||||
.tag(SettingsSidebar.channelConfig)
|
||||
.disabled(selectedNode > 0 && selectedNode != preferredNodeNum)
|
||||
NavigationLink {
|
||||
ShareChannels(node: nodes.first(where: { $0.num == preferredNodeNum }))
|
||||
} label: {
|
||||
Image(systemName: "qrcode")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text("share.channels")
|
||||
Label {
|
||||
Text("share.channels")
|
||||
} icon: {
|
||||
Image(systemName: "qrcode")
|
||||
}
|
||||
}
|
||||
.tag(SettingsSidebar.shareChannels)
|
||||
.disabled(selectedNode > 0 && selectedNode != preferredNodeNum)
|
||||
|
|
@ -180,58 +202,72 @@ struct Settings: View {
|
|||
NavigationLink {
|
||||
UserConfig(node: nodes.first(where: { $0.num == selectedNode }))
|
||||
} label: {
|
||||
Image(systemName: "person.crop.rectangle.fill")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text("user")
|
||||
Label {
|
||||
Text("user")
|
||||
} icon: {
|
||||
Image(systemName: "person.crop.rectangle.fill")
|
||||
}
|
||||
}
|
||||
.tag(SettingsSidebar.userConfig)
|
||||
NavigationLink {
|
||||
BluetoothConfig(node: nodes.first(where: { $0.num == selectedNode }))
|
||||
} label: {
|
||||
Image(systemName: "antenna.radiowaves.left.and.right")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text("bluetooth")
|
||||
Label {
|
||||
Text("bluetooth")
|
||||
} icon: {
|
||||
Image(systemName: "antenna.radiowaves.left.and.right")
|
||||
}
|
||||
}
|
||||
.tag(SettingsSidebar.bluetoothConfig)
|
||||
NavigationLink {
|
||||
DeviceConfig(node: nodes.first(where: { $0.num == selectedNode }))
|
||||
} label: {
|
||||
Image(systemName: "flipphone")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text("device")
|
||||
Label {
|
||||
Text("device")
|
||||
} icon: {
|
||||
Image(systemName: "flipphone")
|
||||
}
|
||||
}
|
||||
.tag(SettingsSidebar.deviceConfig)
|
||||
NavigationLink {
|
||||
DisplayConfig(node: nodes.first(where: { $0.num == selectedNode }))
|
||||
} label: {
|
||||
Image(systemName: "display")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text("display")
|
||||
Label {
|
||||
Text("display")
|
||||
} icon: {
|
||||
Image(systemName: "display")
|
||||
}
|
||||
}
|
||||
.tag(SettingsSidebar.displayConfig)
|
||||
NavigationLink {
|
||||
NetworkConfig(node: nodes.first(where: { $0.num == selectedNode }))
|
||||
} label: {
|
||||
Image(systemName: "network")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text("network")
|
||||
Label {
|
||||
Text("network")
|
||||
} icon: {
|
||||
Image(systemName: "network")
|
||||
}
|
||||
}
|
||||
.tag(SettingsSidebar.networkConfig)
|
||||
NavigationLink {
|
||||
PositionConfig(node: nodes.first(where: { $0.num == selectedNode }))
|
||||
} label: {
|
||||
Image(systemName: "location")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text("position")
|
||||
Label {
|
||||
Text("position")
|
||||
} icon: {
|
||||
Image(systemName: "location")
|
||||
}
|
||||
}
|
||||
.tag(SettingsSidebar.positionConfig)
|
||||
|
||||
NavigationLink {
|
||||
PowerConfig(node: nodes.first(where: { $0.num == selectedNode }))
|
||||
} label: {
|
||||
Image(systemName: "bolt.fill")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text("config.power.settings")
|
||||
Label {
|
||||
Text("config.power.settings")
|
||||
} icon: {
|
||||
Image(systemName: "bolt.fill")
|
||||
}
|
||||
}
|
||||
.tag(SettingsSidebar.powerConfig)
|
||||
}
|
||||
|
|
@ -240,92 +276,114 @@ struct Settings: View {
|
|||
NavigationLink {
|
||||
AmbientLightingConfig(node: nodes.first(where: { $0.num == selectedNode }))
|
||||
} label: {
|
||||
Image(systemName: "light.max")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text("ambient.lighting")
|
||||
Label {
|
||||
Text("ambient.lighting")
|
||||
} icon: {
|
||||
Image(systemName: "light.max")
|
||||
}
|
||||
}
|
||||
.tag(SettingsSidebar.ambientLightingConfig)
|
||||
}
|
||||
NavigationLink {
|
||||
CannedMessagesConfig(node: nodes.first(where: { $0.num == selectedNode }))
|
||||
} label: {
|
||||
Image(systemName: "list.bullet.rectangle.fill")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text("canned.messages")
|
||||
Label {
|
||||
Text("canned.messages")
|
||||
} icon: {
|
||||
Image(systemName: "list.bullet.rectangle.fill")
|
||||
}
|
||||
}
|
||||
.tag(SettingsSidebar.cannedMessagesConfig)
|
||||
NavigationLink {
|
||||
DetectionSensorConfig(node: nodes.first(where: { $0.num == selectedNode }))
|
||||
} label: {
|
||||
Image(systemName: "sensor")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text("detection.sensor")
|
||||
Label {
|
||||
Text("detection.sensor")
|
||||
} icon: {
|
||||
Image(systemName: "sensor")
|
||||
}
|
||||
}
|
||||
.tag(SettingsSidebar.detectionSensorConfig)
|
||||
NavigationLink {
|
||||
ExternalNotificationConfig(node: nodes.first(where: { $0.num == selectedNode }))
|
||||
} label: {
|
||||
Image(systemName: "megaphone")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text("external.notification")
|
||||
Label {
|
||||
Text("external.notification")
|
||||
} icon: {
|
||||
Image(systemName: "megaphone")
|
||||
}
|
||||
}
|
||||
.tag(SettingsSidebar.externalNotificationConfig)
|
||||
NavigationLink {
|
||||
MQTTConfig(node: nodes.first(where: { $0.num == selectedNode }))
|
||||
} label: {
|
||||
Image(systemName: "dot.radiowaves.right")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text("mqtt")
|
||||
Label {
|
||||
Text("mqtt")
|
||||
} icon: {
|
||||
Image(systemName: "dot.radiowaves.up.forward")
|
||||
}
|
||||
}
|
||||
.tag(SettingsSidebar.mqttConfig)
|
||||
NavigationLink {
|
||||
RangeTestConfig(node: nodes.first(where: { $0.num == selectedNode }))
|
||||
} label: {
|
||||
Image(systemName: "point.3.connected.trianglepath.dotted")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text("range.test")
|
||||
Label {
|
||||
Text("range.test")
|
||||
} icon: {
|
||||
Image(systemName: "point.3.connected.trianglepath.dotted")
|
||||
}
|
||||
}
|
||||
.tag(SettingsSidebar.rangeTestConfig)
|
||||
if node?.metadata?.hasWifi ?? false {
|
||||
NavigationLink {
|
||||
PaxCounterConfig(node: nodes.first(where: { $0.num == selectedNode }))
|
||||
} label: {
|
||||
Image(systemName: "figure.walk.motion")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text("config.module.paxcounter.settings")
|
||||
Label {
|
||||
Text("config.module.paxcounter.settings")
|
||||
} icon: {
|
||||
Image(systemName: "figure.walk.motion")
|
||||
}
|
||||
}
|
||||
.tag(SettingsSidebar.paxCounterConfig)
|
||||
}
|
||||
NavigationLink {
|
||||
RtttlConfig(node: nodes.first(where: { $0.num == selectedNode }))
|
||||
} label: {
|
||||
Image(systemName: "music.note.list")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text("ringtone")
|
||||
Label {
|
||||
Text("ringtone")
|
||||
} icon: {
|
||||
Image(systemName: "music.note.list")
|
||||
}
|
||||
}
|
||||
.tag(SettingsSidebar.ringtoneConfig)
|
||||
NavigationLink {
|
||||
SerialConfig(node: nodes.first(where: { $0.num == selectedNode }))
|
||||
} label: {
|
||||
Image(systemName: "terminal")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text("serial")
|
||||
Label {
|
||||
Text("serial")
|
||||
} icon: {
|
||||
Image(systemName: "terminal")
|
||||
}
|
||||
}
|
||||
.tag(SettingsSidebar.serialConfig)
|
||||
NavigationLink {
|
||||
StoreForwardConfig(node: nodes.first(where: { $0.num == selectedNode }))
|
||||
} label: {
|
||||
Image(systemName: "envelope.arrow.triangle.branch")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text("storeforward")
|
||||
Label {
|
||||
Text("storeforward")
|
||||
} icon: {
|
||||
Image(systemName: "envelope.arrow.triangle.branch")
|
||||
}
|
||||
}
|
||||
.tag(SettingsSidebar.storeAndForwardConfig)
|
||||
NavigationLink {
|
||||
TelemetryConfig(node: nodes.first(where: { $0.num == selectedNode }))
|
||||
} label: {
|
||||
Image(systemName: "chart.xyaxis.line")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text("telemetry")
|
||||
Label {
|
||||
Text("telemetry")
|
||||
} icon: {
|
||||
Image(systemName: "chart.xyaxis.line")
|
||||
}
|
||||
}
|
||||
.tag(SettingsSidebar.telemetryConfig)
|
||||
}
|
||||
|
|
@ -333,18 +391,22 @@ struct Settings: View {
|
|||
NavigationLink {
|
||||
MeshLog()
|
||||
} label: {
|
||||
Image(systemName: "list.bullet.rectangle")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text("mesh.log")
|
||||
Label {
|
||||
Text("mesh.log")
|
||||
} icon: {
|
||||
Image(systemName: "list.bullet.rectangle")
|
||||
}
|
||||
}
|
||||
.tag(SettingsSidebar.meshLog)
|
||||
NavigationLink {
|
||||
let connectedNode = nodes.first(where: { $0.num == preferredNodeNum })
|
||||
AdminMessageList(user: connectedNode?.user)
|
||||
} label: {
|
||||
Image(systemName: "building.columns")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text("admin.log")
|
||||
Label {
|
||||
Text("admin.log")
|
||||
} icon: {
|
||||
Image(systemName: "building.columns")
|
||||
}
|
||||
}
|
||||
.tag(SettingsSidebar.adminMessageLog)
|
||||
}
|
||||
|
|
@ -352,9 +414,11 @@ struct Settings: View {
|
|||
NavigationLink {
|
||||
Firmware(node: nodes.first(where: { $0.num == preferredNodeNum }))
|
||||
} label: {
|
||||
Image(systemName: "arrow.up.arrow.down.square")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text("Firmware Updates")
|
||||
Label {
|
||||
Text("Firmware Updates")
|
||||
} icon: {
|
||||
Image(systemName: "arrow.up.arrow.down.square")
|
||||
}
|
||||
}
|
||||
.tag(SettingsSidebar.about)
|
||||
.disabled(selectedNode > 0 && selectedNode != preferredNodeNum)
|
||||
|
|
|
|||
|
|
@ -98,6 +98,10 @@
|
|||
"device.role.routerclient"="Router Client - Mesh Pakete werden bevorzugt über diesen Node gerouted. Der Router Client kann parallel auch von einer Client-App genutzt werden.";
|
||||
"device.role.repeater"="Repeater - Mesh packets will prefer to be routed over this node. This role eliminates unnecessary overhead such as NodeInfo, DeviceTelemetry, and any other mesh packet, resulting in the device not appearing as part of the network. Please see Rebroadcast Mode for additional settings specific to this role.";
|
||||
"device.role.tracker"="Tracker - For use with devices intended as a GPS tracker. Position packets sent from this device will be higher priority, with position broadcasting every two minutes. Smart Position Broadcast will default to off.";
|
||||
"device.role.lostandfound"="Broadcasts location as message to default channel regularly for to assist with device recovery.";
|
||||
"device.role.sensor"="Broadcasts telemetry packets as priority.";
|
||||
"device.role.tak"="Optimized for ATAK system communication, reduces routine broadcasts.";
|
||||
"device.role.taktracker"="Enables automatic TAK PLI broadcasts and reduces routine broadcasts.";
|
||||
"direct.messages"="Direktnachrichten";
|
||||
"dismiss.keyboard"="Dismiss Keyboard";
|
||||
"display"="Display (Device Screen)";
|
||||
|
|
@ -309,12 +313,15 @@
|
|||
"tapback.exclamation"="Ausrufezeichen";
|
||||
"tapback.question"="Fragezeichen";
|
||||
"tapback.poop"="Kacke";
|
||||
"tapback.wave"="Wave";
|
||||
"telemetry"="Telemetrie (Sensoren)";
|
||||
"telemetry.config"="Telemetrie Einstellungen";
|
||||
"timeout"="Zeitlimit erreicht";
|
||||
"timestamp"="Timestamp";
|
||||
"tip.bluetooth.connect.title"="Connected LoRa Radio";
|
||||
"tip.bluetooth.connect.message"="Shows information for the Lora radio currently connected via bluetooth. You can swipe left to disconnect the radio and long press to view stats or start the live activity.";
|
||||
"tip.channel.admin.title"="Admin Channel";
|
||||
"tip.channel.admin.message"="Admin channel detected: Select a node from the drop down to manage connected or remote devices.";
|
||||
"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/radio/channels/)";
|
||||
"tip.channels.share.title"="Sharing Meshtastic Channels";
|
||||
|
|
|
|||
|
|
@ -99,6 +99,7 @@
|
|||
"device.role.lostandfound"="Broadcasts location as message to default channel regularly for to assist with device recovery.";
|
||||
"device.role.sensor"="Broadcasts telemetry packets as priority.";
|
||||
"device.role.tak"="Optimized for ATAK system communication, reduces routine broadcasts.";
|
||||
"device.role.taktracker"="Enables automatic TAK PLI broadcasts and reduces routine broadcasts.";
|
||||
"device.role.repeater"="Infrastructure node for extending network coverage by relaying messages with minimal overhead. Not visible in Nodes list.";
|
||||
"device.role.router"="Infrastructure node for extending network coverage by relaying messages. Visible in Nodes list.";
|
||||
"device.role.routerclient"="Combination of both ROUTER and CLIENT. Not for mobile devices.";
|
||||
|
|
@ -326,12 +327,15 @@
|
|||
"tapback.exclamation"="Exclamation Mark";
|
||||
"tapback.question"="Question Mark";
|
||||
"tapback.poop"="Poop";
|
||||
"tapback.wave"="Wave";
|
||||
"telemetry"="Telemetry (Sensors)";
|
||||
"telemetry.config"="Telemetry Config";
|
||||
"timeout"="Timeout";
|
||||
"timestamp"="Timestamp";
|
||||
"tip.bluetooth.connect.title"="Connected Radio";
|
||||
"tip.bluetooth.connect.message"="Shows information for the Lora radio connected via bluetooth. You can swipe left to disconnect the radio and long press to view stats or start the live activity.";
|
||||
"tip.channel.admin.title"="Admin Channel";
|
||||
"tip.channel.admin.message"="Admin channel detected: Select a node from the drop down to manage connected or remote devices.";
|
||||
"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";
|
||||
|
|
|
|||
320
fr.lproj/Localizable.strings
Normal file
320
fr.lproj/Localizable.strings
Normal file
|
|
@ -0,0 +1,320 @@
|
|||
/*
|
||||
Localizable.strings
|
||||
Meshtastic
|
||||
|
||||
Copyright(c) Garth Vander Houwen on 12/12/22.
|
||||
|
||||
*/
|
||||
"about"="À propos";
|
||||
"about.meshtastic"="À propos de Meshtastic";
|
||||
"admin"="Administrateur";
|
||||
"admin.log"="Journal des messages d'administration";
|
||||
"ago"="auparavant";
|
||||
"airtime"="Temps d'émission";
|
||||
"always.on"="En permanence";
|
||||
"ambient.lighting"="Lumière ambiante";
|
||||
"ambient.lighting.config"="Configuration de la lumière ambiante";
|
||||
"appsettings"="Réglages de l'application";
|
||||
"appsettings.provide.location"="Partager la position";
|
||||
"appsettings.smartposition"="Position intelligente";
|
||||
"are.you.sure"="Êtes-vous sûr ?";
|
||||
"ascii.capable"="ASCII Compatible";
|
||||
"available.radios"="Radios disponibles";
|
||||
"automatic.detection"="Détection automatique";
|
||||
"battery.level"="Niveau de batterie";
|
||||
"ble.name"="Nom du BLE";
|
||||
"ble.connection.timeout %d %@"="Connexion impossible après %d essais avec %@. Allez dans Réglages > Bluetooth et essayez de faire de faire > Oublier cet appareil.";
|
||||
"ble.errorcode.6 %@"="%@ L'application se reconnectera automatiquement à la radio en favori dès qu'elle sera à nouveau disponible.";
|
||||
"ble.errorcode.14 %@"="%@ Cette erreur ne peut généralement pas être corrigée sans aller dans Réglages > Bluetooth et faire > Oublier cet appareil, puis reconnecter la radio.";
|
||||
"ble.errorcode.pin %@"="%@ Merci d'essayer à nouveau en vérifiant bien le code PIN.";
|
||||
"bluetooth"="Bluetooth";
|
||||
"bluetooth.off"="Le Bluetooth est arrêté";
|
||||
"bluetooth.config"="Configuration Bluetooth";
|
||||
"bluetooth.mode.randompin"="Code PIN aléatoire";
|
||||
"bluetooth.mode.fixedpin"="Code PIN fixe";
|
||||
"bluetooth.mode.nopin"="Sans code PIN (connexion directe)";
|
||||
"bluetooth.pairingmode"="Mode d'appairage";
|
||||
"bluetooth.pin.validation"="Le code pin BLE doit avoir 6 chiffres.";
|
||||
"bytes"="Octets";
|
||||
"cancel"="Annuler";
|
||||
"canned.messages"="Messages préformatés";
|
||||
"canned.messages.config"="Configuration des messages préformatés";
|
||||
"canned.messages.preset.manual"="Configuration manuelle";
|
||||
"canned.messages.preset.rakrotary"="Module d'encodage rotatif RAK";
|
||||
"canned.messages.preset.cardkb"="Clavier M5 Stack Card KB / RAK";
|
||||
"channel"="Canal";
|
||||
"channel.role.disabled"="Désactivé";
|
||||
"channel.role.primary"="Principal";
|
||||
"channel.role.secondary"="Secondaire";
|
||||
"channel.utilization"="Utilisation du canal";
|
||||
"channels"="Canaux";
|
||||
"clear.app.data"="Effacer les données de l'application";
|
||||
"clear.log"="Effacer";
|
||||
"close"="Fermer";
|
||||
"config.save.confirm"="Une fois la configuration sauvegardée, le noeud redémarrera.";
|
||||
"communicating"="Communication avec l'appareil en cours. .";
|
||||
"connected.radio"="Radio connectée";
|
||||
"connected"="Bluetooth connecté";
|
||||
"connecting"="Connexion . .";
|
||||
"contacts"="Contacts";
|
||||
"contacts %@"="Contacts (%@)";
|
||||
"copy"="Copier";
|
||||
"current"="Actuel";
|
||||
"default"="Par défaut";
|
||||
"delete"="Effacer";
|
||||
"detection.sensor"="Capteur de détection";
|
||||
"detection.sensor.config"="Configuration du capteur de détection";
|
||||
"detection.sensor.log"="Journal du capteur de détection";
|
||||
"device"="Appareil";
|
||||
"device.config"="Configuration de l'appareil";
|
||||
"device.metrics.delete"="Effacer toutes les mesures de l’appareil?";
|
||||
"device.metrics.log"="Journal des mesures de l'appareil";
|
||||
"device.role.client"="Application connectée ou appareil de messagerie autonome.";
|
||||
"device.role.clientmute"="Appareil ne transmettant pas les paquets provenant d'autres appareils.";
|
||||
"device.role.clienthidden"="Appareil ne diffusant que si nécessaire pour la discrétion et l'économie d'énergie.";
|
||||
"device.role.tracker"="Transmet les paquets de positions GPS en priorité.";
|
||||
"device.role.lostandfound"="Transmet régulièrement la position par message dans le canal par défaut pour vous aider à retrouver l'appareil.";
|
||||
"device.role.sensor"="Transmet les paquets de télémétrie en priorité.";
|
||||
"device.role.tak"="Optimisé pour le système de communication ATAK, diminue les émissions de routine.";
|
||||
"device.role.taktracker"="Enables automatic TAK PLI broadcasts and reduces routine broadcasts.";
|
||||
"device.role.repeater"="Noeud d'infrastructure qui étend la couverture du réseau en relayant les messages avec un minimum de surcharge. Invisible dans la liste des noeuds.";
|
||||
"device.role.router"="Noeud d'infrastructure qui étend la couverture du réseau en relayant les messages. Visible dans la liste des noeuds.";
|
||||
"device.role.routerclient"="Combinaison des modes ROUTER et CLIENT. Pas pour les appareils mobiles.";
|
||||
"direct.messages"="Messages directs";
|
||||
"dismiss.keyboard"="Annuler";
|
||||
"display"="Écran";
|
||||
"display.config"="Configuration de l'écran";
|
||||
"distance"="Distance";
|
||||
"disconnect"="Déconnecter";
|
||||
"echo"="Écho";
|
||||
"email.address"="Adresse mail";
|
||||
"enabled"="Activé";
|
||||
"encrypted"="Encrypté";
|
||||
"external.notification"="Notification extérieure";
|
||||
"external.notification.config"="Configuration de la notification extérieure";
|
||||
"finish"="Terminer";
|
||||
"firmware.version"="Version du firmware";
|
||||
"firmware.version.unsupported"="Version non supportée du firmware détectée, impossible de se connecter à l'appareil.";
|
||||
"gas"="Gaz";
|
||||
"gas.resistance"="Résistence du gaz";
|
||||
"generate.qr.code"="Générer un QR Code";
|
||||
"gpsformat.dec"="Format décimal pour les degrés";
|
||||
"gpsformat.dms"="Degrés Minutes Secondes";
|
||||
"gpsformat.utm"="Projection Mercator Transverse Universelle";
|
||||
"gpsformat.mgrs"="Military Grid Reference System";
|
||||
"gpsformat.olc"="Open Location Code (alias Plus Codes)";
|
||||
"gpsformat.osgr"="Ordnance Survey Grid Reference";
|
||||
"gpsmode.disabled"="Désactivé";
|
||||
"gpsmode.enabled"="Activé";
|
||||
"gpsmode.notPresent"="Absent";
|
||||
"heard"="Capté";
|
||||
"heard.last"="Capté pour la dernière fois";
|
||||
"hybrid"="Hybride";
|
||||
"hybrid.flyover"="Flyover hybride";
|
||||
"include"="Inclure";
|
||||
"inputevent.none"="Aucun";
|
||||
"inputevent.up"="Haut";
|
||||
"inputevent.down"="Bas";
|
||||
"inputevent.left"="Gauche";
|
||||
"inputevent.right"="Droite";
|
||||
"inputevent.select"="Sélectionner";
|
||||
"inputevent.back"="Retour";
|
||||
"inputevent.cancel"="Annuler";
|
||||
"interval.one.second"="Une seconde";
|
||||
"interval.two.seconds"="Deux secondes";
|
||||
"interval.three.seconds"="Trois secondes";
|
||||
"interval.four.seconds"="Quatre secondes";
|
||||
"interval.five.seconds"="Cinq secondes";
|
||||
"interval.ten.seconds"="Dix secondes";
|
||||
"interval.fifteen.seconds"="Quinze secondes";
|
||||
"interval.twenty.seconds"="Vingt secondes";
|
||||
"interval.twentyfive.seconds"="Vingt cinq secondes";
|
||||
"interval.thirty.seconds"="Trente secondes";
|
||||
"interval.fortyfive.seconds"="Quarante cinq secondes";
|
||||
"interval.one.minute"="Une minute";
|
||||
"interval.two.minutes"="Deux minutes";
|
||||
"interval.five.minutes"="Cinq minutes";
|
||||
"interval.ten.minutes"="Dix minutes";
|
||||
"interval.fifteen.minutes"="Quinze minutes";
|
||||
"interval.thirty.minutes"="Trente minutes";
|
||||
"interval.one.hour"="Une heure";
|
||||
"interval.two.hours"="Deux heures";
|
||||
"interval.three.hours"="Trois heures";
|
||||
"interval.four.hours"="Quatre heures";
|
||||
"interval.five.hours"="Cinq heures";
|
||||
"interval.six.hours"="Six heures";
|
||||
"interval.twelve.hours"="Douze heures";
|
||||
"interval.eighteen.hours"="Dix huit heures";
|
||||
"interval.twentyfour.hours"="Vingt quatre heures";
|
||||
"interval.thirtysix.hours"="Trente six heures";
|
||||
"interval.fortyeight.hours"="Quarante huit heures";
|
||||
"interval.seventytwo.hours"="Soixante douze heures";
|
||||
"keyboard.type"="Type de clavier";
|
||||
"logging"="Enregistrement";
|
||||
"lora"="LoRa";
|
||||
"lora.config"="Configuration LoRa";
|
||||
"map"="Carte de maillage";
|
||||
"map.type"="Type par défaut";
|
||||
"map.centering"="Mode centré";
|
||||
"map.tiles.delete"="Supprimer toutes les tuiles de carte";
|
||||
"map.recentering"="Recentrage automatique";
|
||||
"map.use.legacy"="Utiliser l'ancienne génération de carte de maillage";
|
||||
"map.usertrackingmode"="Mode suivre l'utilisateur";
|
||||
"map.usertrackingmode.follow"="Suivre";
|
||||
"map.usertrackingmode.followwithheading"="Suivre avec le cap";
|
||||
"map.usertrackingmode.none"="Aucun";
|
||||
"mesh.live.activity"="Activité en direct du maillage";
|
||||
"mesh.log"="Journal du maillage";
|
||||
"mesh.log.ambientlighting.config %@"="Configuration du module lumière ambiante reçue : %@";
|
||||
"mesh.log.bluetooth.config %@"="Configuration Bluetooth reçue : %@";
|
||||
"mesh.log.cannedmessage.config %@"="Configuration du module messages préformatés reçue : %@";
|
||||
"mesh.log.cannedmessages.messages.get %@"="Messages du module messages préformatés demandés pour le noeud : %@";
|
||||
"mesh.log.cannedmessages.messages.received %@"="Messages préformatés reçus pour : %@";
|
||||
"mesh.log.channel.sent %@ %d"="Canal envoyé pour : %@ Canal index %d";
|
||||
"mesh.log.channel.received %d %@"="Canal %d reçu de : %@";
|
||||
"mesh.log.device.config %@"="Configuration de l'appareil reçue : %@";
|
||||
"mesh.log.display.config %@"="Configuration de l'écran reçue : %@";
|
||||
"mesh.log.devicemetadata %@"="Demande des metadatas de l'appareil à %@";
|
||||
"mesh.log.device.metadata.received %@"="Metadatas de l'appareil reçues de : %@";
|
||||
"mesh.log.detectionsensor.config %@"="Configuration du module capteur de détection reçue : %@";
|
||||
"mesh.log.externalnotification.config %@"="Configuration du module notification extérieure reçue : %@";
|
||||
"mesh.log.lora.config %@"="Configuration LoRa reçue : %@";
|
||||
"mesh.log.lora.config.sent %@"="Configuration LoRa envoyée à : %@";
|
||||
"mesh.log.mqtt.config %@"="Configuration du module MQTT reçue : %@";
|
||||
"mesh.log.myinfo %@"="MesInfos reçues : %@";
|
||||
"mesh.log.network.config %@"="Configuration du réseau reçue : %@";
|
||||
"mesh.log.nodeinfo.received %@"="Information du noeud reçue pour : %@";
|
||||
"mesh.log.position.config %@"="Configuration de la position reçue : %@";
|
||||
"mesh.log.position.received %@"="Paquet de la position reçu du noeud : %@";
|
||||
"mesh.log.rangetest.config %@"="Configuration du module test deportée reçue : %@";
|
||||
"mesh.log.ringtone.config %@"="Configuration de la sonnerie RTTTL reçue : %@";
|
||||
"mesh.log.routing.message %@ %@"="Routage reçu pour la demande numéro : %@ Status de l'accusé de réception : %@";
|
||||
"mesh.log.serial.config %@"="Configuration du module série reçue : %@";
|
||||
"mesh.log.sharelocation %@"="Paquet envoyé avec la position GPS de l'appareil Apple vers le noeud : %@";
|
||||
"mesh.log.storeforward.config %@"="Configuration du module Stocker et Transmettre reçue : %@";
|
||||
"mesh.log.telemetry.config %@"="Configuration du module télémetrie reçue : %@";
|
||||
"mesh.log.telemetry.received %@"="Télémetrie reçue pour : %@";
|
||||
"mesh.log.textmessage.received"="Message reçu depuis l'application de messagerie texte.";
|
||||
"mesh.log.textmessage.send.failed %@"="Erreur d'envoi du message, mauvaise connexion à %@";
|
||||
"mesh.log.textmessage.sent %@ %@ %@"="Envoi du message %@ de %@ à %@";
|
||||
"mesh.log.traceroute.received.direct %@"="La demande de Trace Route envoyée au noeud : %@ a été directement reçue.";
|
||||
"mesh.log.traceroute.received.route %@"="La demande de Trace Route est revenue : %@";
|
||||
"mesh.log.traceroute.sent %@"="Envoi d'une demande de Trace Route au noeud : %@";
|
||||
"mesh.log.wantconfig %@"="Envoi d'un Want Config à %@";
|
||||
"mesh.log.waypoint.sent %@"="Paquet Waypoint envoyé depuis : %@";
|
||||
"mesh.log.waypoint.received %@"="Paquet Waypoint reçu du noeud : %@";
|
||||
"message"="Message";
|
||||
"message.details"="Détails du message";
|
||||
"messages"="Messages";
|
||||
"mode"="Mode";
|
||||
"module.configuration"="Configuration du module";
|
||||
"mqtt"="MQTT";
|
||||
"mqtt.connect"="Connecter à MQTT";
|
||||
"mqtt.config"="Configuration MQTT";
|
||||
"mqtt.clientproxy"="Proxy client MQTT";
|
||||
"mqtt.disconnect"="Déconnecter le MQTT";
|
||||
"mqtt.username"="Nom d'utilisateur";
|
||||
"name"="Nom";
|
||||
"network"="Réseau";
|
||||
"network.config"="Configuration du réseau";
|
||||
"nodes"="Noeuds";
|
||||
"nodes %@"="Noeuds (%@)";
|
||||
"no.nodes"="Aucun noeud Meshtastic trouvé";
|
||||
"not.connected"="Aucun appareil connecté";
|
||||
"numbers.punctuation"="Nombres and Ponctuation";
|
||||
"off"="Éteint";
|
||||
"offline"="Hors ligne";
|
||||
"on.boot"="Uniquement au démarrage";
|
||||
"options"="Options";
|
||||
"password"="Mot de passe";
|
||||
"pause"="Pause";
|
||||
"phone.gps"="GPS du téléphone";
|
||||
"phone.gps.interval.description"="La fréquence à laquelle votre téléphone envoie votre position à l'appareil, les mises à jour de la position vers le maillage sont gérées par l'appareil.";
|
||||
"position"="Position";
|
||||
"position.config"="Configuration de la position";
|
||||
"preferred.radio"="Radio favorie";
|
||||
"radio.configuration"="Configuration de la radio";
|
||||
"range.test"="Test de portée";
|
||||
"range.test.blocked"="Test de portée bloqué";
|
||||
"range.test.config"="Configuration du test de portée";
|
||||
"reply"="Répondre";
|
||||
"reboot"="Redémarrer";
|
||||
"reboot.node"="Redémarrer le noeud ?";
|
||||
"received.ack"="Accusé de réception reçu";
|
||||
"received.ack.real"="Accusé de réception du destinataire";
|
||||
"resume"="Reprendre";
|
||||
"ringtone"="Sonnerie";
|
||||
"ringtone.config"="Configuration de la sonnerie";
|
||||
"route.recorder"="Enregistreur de route";
|
||||
"routes"="Routes";
|
||||
"routing.acknowledged"="Confirmé";
|
||||
"routing.noroute"="Pas de route";
|
||||
"routing.gotnak"="Accusé de réception négatif reçu";
|
||||
"routing.timeout"="Délai d'expiration";
|
||||
"routing.nointerface"="Pas d'interface";
|
||||
"routing.maxretransmit"="Nombre maximum de retransmissions atteint";
|
||||
"routing.nochannel"="Pas de canal";
|
||||
"routing.toolarge"="Le paquet est trop grand";
|
||||
"routing.noresponse"="Pas de réponse";
|
||||
"routing.dutycyclelimit"="Limite du cycle de service régional atteinte";
|
||||
"routing.badRequest"="Requête incorrecte";
|
||||
"routing.notauthorized"="Non autorisé";
|
||||
"satellite"="Satellite";
|
||||
"satellite.flyover"="Flyover par satellite";
|
||||
"save"="Sauvegarder";
|
||||
"save.config %@"="Sauvegarder la configuration pour %@";
|
||||
"serial"="Série";
|
||||
"serial.config"="Configuration série";
|
||||
"serial.mode.default"="Défaut";
|
||||
"serial.mode.simple"="Simple";
|
||||
"serial.mode.proto"="Protobufs";
|
||||
"serial.mode.txtmsg"="Message texte";
|
||||
"serial.mode.nmea"="Positions NMEA";
|
||||
"settings"="Réglages";
|
||||
"share.channels"="Partager le QR Code";
|
||||
"share.position"="Partager la position";
|
||||
"subscribed"="Abonné au maillage";
|
||||
"select.contact"="Sélectioner un contact";
|
||||
"select.node"="Sélectioner un noeud";
|
||||
"select.menu.item"="Sélectioner un item du menu";
|
||||
"set.region"="Définir la région LoRa";
|
||||
"standard"="Standard";
|
||||
"standard.muted"="Standard en sourdine";
|
||||
"start"="Démarrer";
|
||||
"storeforward"="Stocker et Transmettre";
|
||||
"storeforward.config"="Configuration de Stocker et Transmettre";
|
||||
"storeforward.heartbeat"="Envoyer une impulsion";
|
||||
"ssid"="SSID";
|
||||
"tapback"="Réponse de Tapback";
|
||||
"tapback.heart"="Coeur";
|
||||
"tapback.thumbsup"="Pouce levé";
|
||||
"tapback.thumbsdown"="Pouce baissé";
|
||||
"tapback.haha"="HaHa";
|
||||
"tapback.exclamation"="Point d'exclamation";
|
||||
"tapback.question"="Point d'interrogation";
|
||||
"tapback.poop"="Caca";
|
||||
"tapback.wave"="Wave";
|
||||
"telemetry"="Télémetrie (Capteurs)";
|
||||
"telemetry.config"="Configuration de télémetrie";
|
||||
"timeout"="Délai d'expiration";
|
||||
"timestamp"="Horodatage";
|
||||
"tip.bluetooth.connect.title"="Radio connectée";
|
||||
"tip.bluetooth.connect.message"="Affiche les informations de la radio Lora connectée via le bluetooth. Vous pouvez faire un glissé vers la gauche pour déconnecter la radio et un appui long pour voir les statistiques ou démarrer l'activité en direct.";
|
||||
"tip.channels.create.title"="Gérer les canaux";
|
||||
"tip.channels.create.message"="La pluspart des données de votre maillage sont envoyées sur le canal principal. Vous pouvez définir des canaux secondaires pour créer des groupes de messagerie additionnelle sécurisés avec leur propre clé. [Conseils de configuration du canal](https://meshtastic.org/docs/configuration/tips/)";
|
||||
"tip.channel.admin.title"="Admin Channel";
|
||||
"tip.channel.admin.message"="Admin channel detected: Select a node from the drop down to manage connected or remote devices.";
|
||||
"tip.channels.share.title"="Partage des canaux Meshtastic";
|
||||
"tip.channels.share.message"="Un code QR Meshtastic contient la configuration LoRa et les valeurs de canal nécessaires pour communiquer. La plupart des activités du maillage ont lieu sur le canal principal requis. Si vous ne partagez pas votre canal principal, votre premier canal partagé devient le canal principal de l’autre réseau. Les autres canaux sont pour les groupes privés, chacun avec sa propre clé.";
|
||||
"tip.messages.title"="Messages";
|
||||
"tip.messages.message"="Vous pouvez envoyer et recevoir des canaux (chats de groupe) et des messages directs. Depuis n’importe quel message, vous pouvez faire un appui long pour voir les actions possibles comme copier, répondre, tapback et supprimer ainsi que les détails de l'envoi.";
|
||||
"twitter"="Twitter";
|
||||
"unknown"="Inconnu";
|
||||
"unknown.age"="Age inconnu";
|
||||
"unset"="Désactivé";
|
||||
"update.firmware"="Mettre à jour votre Firmware";
|
||||
"update.interval"="Intervale de mise à jour";
|
||||
"user"="Utilisateur";
|
||||
"user.details"="Détails de l'utilisateur";
|
||||
"voltage"="Tension";
|
||||
"waiting"="En attente . . .";
|
||||
|
|
@ -99,6 +99,7 @@
|
|||
"device.role.lostandfound"="משדר מיקום כהודעה לערוץ ברירת מחדל לעיתים קבועות בכדי לסייע במציאת המכשיר.";
|
||||
"device.role.sensor"="משדר טלמטריה בעדיפות גבוהה.";
|
||||
"device.role.tak"="מותאם למערכת ATAK, מקטין תקשורת קבועה.";
|
||||
"device.role.taktracker"="Enables automatic TAK PLI broadcasts and reduces routine broadcasts.";
|
||||
"device.role.repeater"="מכשיר תשתית להרחבת המש על ידי העברת הודעות עם דאטה נוסף מינימלי.";
|
||||
"device.role.router"="מכשיר תשתית להרחבת המש על ידי העברת הודעות. מופיע ברשימת מכשירים.";
|
||||
"device.role.routerclient"="קומבינציה של ROUTER וCLIENT. לא למכשירים ניידים.";
|
||||
|
|
@ -316,12 +317,15 @@
|
|||
"tapback.exclamation"="סימן קריאה";
|
||||
"tapback.question"="סימן שאלה";
|
||||
"tapback.poop"="חרא";
|
||||
"tapback.wave"="Wave";
|
||||
"telemetry"="טלמטריה (חיישנים)";
|
||||
"telemetry.config"="הגדרות טלמטריה";
|
||||
"timeout"="זמן קצוב";
|
||||
"timestamp"="שעה/תאריך";
|
||||
"tip.bluetooth.connect.title"="מכשיר מחובר";
|
||||
"tip.bluetooth.connect.message"="מראה מידע אודות מכשיר המשטסטיק המחובר כעת לבלוטוס. ניתן לגרור שמאלה להתנתקות או לחיצה ארוכה לראות סטטיסטיקה או להתחיל פעילות.";
|
||||
"tip.channel.admin.title"="Admin Channel";
|
||||
"tip.channel.admin.message"="Admin channel detected: Select a node from the drop down to manage connected or remote devices.";
|
||||
"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/radio/channels/)";
|
||||
"tip.channels.share.title"="משתף ערוצי משטסטיק";
|
||||
|
|
|
|||
|
|
@ -100,6 +100,10 @@
|
|||
"device.role.routerclient"="Router Client - Hybryda ról klienta i routera. Podobnie jak w przypadku routera, z tym że Router Client może być używany zarówno jako router, jak i klient połączony z aplikacją. Radia BLE/Wi-Fi i ekran OLED nie zostaną uśpione.";
|
||||
"device.role.repeater"="Przekaźnik - Pakiety siatki będą preferować trasowanie przez ten węzeł. Ta rola eliminuje niepotrzebny nadmiar, taki jak NodeInfo, DeviceTelemetry i inne pakiety siatki, skutkując tym, że urządzenie nie będzie widoczne jako część sieci. Proszę zobaczyć tryb Rebroadcast dla dodatkowych ustawień specyficznych dla tej roli.";
|
||||
"device.role.tracker"="Tracker - Do użytku z urządzeniami przeznaczonymi jako śledzenie GPS. Pakiety pozycyjne wysyłane z tego urządzenia będą miały wyższy priorytet, z nadawaniem pozycji co dwie minuty. Inteligentna transmisja pozycji będzie domyślnie wyłączona.";
|
||||
"device.role.lostandfound"="Broadcasts location as message to default channel regularly for to assist with device recovery.";
|
||||
"device.role.sensor"="Broadcasts telemetry packets as priority.";
|
||||
"device.role.tak"="Optimized for ATAK system communication, reduces routine broadcasts.";
|
||||
"device.role.taktracker"="Enables automatic TAK PLI broadcasts and reduces routine broadcasts.";
|
||||
"direct.messages"="Bezpośrednie Wiadomości";
|
||||
"dismiss.keyboard"="Zamknij";
|
||||
"display"="Wyświetlacz (Ekran Urządzenia)";
|
||||
|
|
@ -310,12 +314,15 @@
|
|||
"tapback.exclamation"="Wykrzyknik";
|
||||
"tapback.question"="Znak zapytania";
|
||||
"tapback.poop"="Kupa";
|
||||
"tapback.wave"="Wave";
|
||||
"telemetry"="Telemetria (czujniki)";
|
||||
"telemetry.config"="Konfiguracja telemetrii";
|
||||
"timeout"="Limit czasu";
|
||||
"timestamp"="Znacznik czasu";
|
||||
"tip.bluetooth.connect.title"="Connected LoRa Radio";
|
||||
"tip.bluetooth.connect.message"="Shows information for the Lora radio currently connected via bluetooth. You can swipe left to disconnect the radio and long press to view stats or start the live activity.";
|
||||
"tip.channel.admin.title"="Admin Channel";
|
||||
"tip.channel.admin.message"="Admin channel detected: Select a node from the drop down to manage connected or remote devices.";
|
||||
"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/radio/channels/)";
|
||||
"tip.channels.share.title"="Sharing Meshtastic Channels";
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit 5241583565ccbbb4986180bf4c6eb7f8a0dec285
|
||||
Subproject commit dea3a82ef2accd25112b4ef1c6f8991b579740f4
|
||||
|
|
@ -98,6 +98,10 @@
|
|||
"device.role.routerclient"="路由客户端模式 - 优先转发 Mesh 网络中其他节点的消息,App 也可以连接到电台进行收发操作。";
|
||||
"device.role.repeater"="中继模式 - Mesh 网络数据包将优先通过此节点路由。此模式可消除不必要的开销,如 NodeInfo、DeviceTelemetry 和任何其他 Mesh 数据包,从而使设备不显示为 Mesh 网络的一部分。有关此角色的其他特定设置,请参阅转播模式。";
|
||||
"device.role.tracker"="定位模式 - 用于作为 GPS 跟踪器。从该设备发送的定位数据包优先级较高,每两分钟广播一次。智能位置广播默认为关闭。";
|
||||
"device.role.lostandfound"="Broadcasts location as message to default channel regularly for to assist with device recovery.";
|
||||
"device.role.sensor"="Broadcasts telemetry packets as priority.";
|
||||
"device.role.tak"="Optimized for ATAK system communication, reduces routine broadcasts.";
|
||||
"device.role.taktracker"="Enables automatic TAK PLI broadcasts and reduces routine broadcasts.";
|
||||
"direct.messages"="直频消息";
|
||||
"dismiss.keyboard"="隐藏键盘";
|
||||
"display"="屏幕(电台屏幕)";
|
||||
|
|
@ -309,12 +313,15 @@
|
|||
"tapback.exclamation"="感叹号";
|
||||
"tapback.question"="问号";
|
||||
"tapback.poop"="便便";
|
||||
"tapback.wave"="Wave";
|
||||
"telemetry"="遥测(传感器)";
|
||||
"telemetry.config"="遥测配置";
|
||||
"timeout"="超时";
|
||||
"timestamp"="时间戳";
|
||||
"tip.bluetooth.connect.title"="连接到 LoRa 电台";
|
||||
"tip.bluetooth.connect.message"="显示当前通过蓝牙连接的 Lora 电台的信息。您可以向左滑动断开电台,长按查看统计信息或开始实时活动。";
|
||||
"tip.channel.admin.title"="Admin Channel";
|
||||
"tip.channel.admin.message"="Admin channel detected: Select a node from the drop down to manage connected or remote devices.";
|
||||
"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/radio/channels/)";
|
||||
"tip.channels.share.title"="共享 Meshtastic 频道";
|
||||
|
|
|
|||
339
zh-Hant-TW.lproj/Localizable.strings
Normal file
339
zh-Hant-TW.lproj/Localizable.strings
Normal file
|
|
@ -0,0 +1,339 @@
|
|||
/*
|
||||
Localizable.strings
|
||||
Meshtastic
|
||||
|
||||
Created by BM6HIP on 2024/3/2
|
||||
|
||||
*/
|
||||
"about"="關於";
|
||||
"about.meshtastic"="關於 Meshtastic";
|
||||
"admin"="管理員";
|
||||
"admin.log"="管理員消息紀錄檔";
|
||||
"ago"="ago";
|
||||
"airtime"="廣播時間";
|
||||
"always.on"="常亮";
|
||||
"ambient.lighting"="Ambient Lighting";
|
||||
"ambient.lighting.config"="Ambient Lighting Config";
|
||||
"appsettings"="設定";
|
||||
"appsettings.provide.location"="提供定位到 Mesh 網路";
|
||||
"appsettings.smartposition"="Smart Position";
|
||||
"are.you.sure"="是否確定?";
|
||||
"ascii.capable"="ASCII Capable";
|
||||
"available.radios"="可以連接的設備";
|
||||
"automatic.detection"="自動識別";
|
||||
"battery.level"="電池電量";
|
||||
"ble.name"="藍芽名稱";
|
||||
"ble.connection.timeout %d %@"="嘗試連接%@失敗,你可能需要在系统設定的藍芽選項中忽略該電台。";
|
||||
"ble.errorcode.6 %@"="%@ 如果在首選電台的旁邊,App 將會自動重連。";
|
||||
"ble.errorcode.14 %@"="%@ 這個錯誤通常無法自動修復,你需要在系統設定的藍芽選項中忽略該電台並重新配對。";
|
||||
"ble.errorcode.pin %@"="%@ 請再次嘗試連接並仔細檢查 PIN 碼。";
|
||||
"bluetooth"="藍芽";
|
||||
"bluetooth.off"="藍芽已關閉";
|
||||
"bluetooth.config"="藍芽設置";
|
||||
"bluetooth.mode.randompin"="隨機 PIN 碼";
|
||||
"bluetooth.mode.fixedpin"="固定 PIN 碼";
|
||||
"bluetooth.mode.nopin"="不使用 PIN 碼(直接配對)";
|
||||
"bluetooth.pairingmode"="配對模式";
|
||||
"bluetooth.pin.validation"="藍芽 PIN 碼必須是 6 位數字。";
|
||||
"bytes"="字節";
|
||||
"cancel"="取消";
|
||||
"canned.messages"="通知";
|
||||
"canned.messages.config"="通知設定";
|
||||
"canned.messages.preset.manual"="手動設定";
|
||||
"canned.messages.preset.rakrotary"="RAK 旋轉編碼器";
|
||||
"canned.messages.preset.cardkb"="M5Stack 卡片鍵盤 / RAK 鍵盤";
|
||||
"channel"="頻道";
|
||||
"channel.role.disabled"="關閉";
|
||||
"channel.role.primary"="主要";
|
||||
"channel.role.secondary"="次要";
|
||||
"channel.utilization"="頻道利用率";
|
||||
"channels"="頻道";
|
||||
"clear.app.data"="清除 App 資料";
|
||||
"clear.log"="清除紀錄檔";
|
||||
"close"="關閉";
|
||||
"config.power.settings"="電源";
|
||||
"config.power.title"="電源設定";
|
||||
"config.power.section.battery"="電池";
|
||||
"config.power.section.sleep"="休眠";
|
||||
"config.power.adc.override"="ADC Override";
|
||||
"config.power.adc.multiplier"="Multiplier";
|
||||
"config.power.ls.secs"="Light Sleep Interval";
|
||||
"config.power.min.wake.secs"="最小的喚醒間隔時間";
|
||||
"config.power.saving"="省電模式";
|
||||
"config.power.saving.description"="Will sleep everything as much as possible, for the tracker and sensor role this will also include the lora radio. Don't use this setting if you want to use your device with the phone apps or are using a device without a user button.";
|
||||
"config.power.shutdown.on.power.loss"="失去電源後關機";
|
||||
"config.power.shutdown.after.secs"="之後";
|
||||
"config.power.wait.bluetooth.secs"="等待藍芽";
|
||||
"config.ringtone"="RTTTL Ringtone";
|
||||
"config.ringtone.title"="鈴聲";
|
||||
"config.ringtone.label"="Ringtone Transfer Language";
|
||||
"config.ringtone.description"="Ringtone Transfer Language(RTTTL) Ringtone String used by supported buzzers in external notifications.";
|
||||
"config.module.paxcounter.settings"="PAX Counter";
|
||||
"config.module.paxcounter.title"="PAX Counter Config";
|
||||
"config.module.paxcounter.enabled.description"="When enabled the PAX Counter module counts the number of people passing by using WiFi and Bluetooth. Both WiFI and Bluetooth must be enabled for PAX counter to work.";
|
||||
"config.module.paxcounter.updateinterval"="Update Interval";
|
||||
"config.module.paxcounter.updateinterval.description"="How often we can send a message to the mesh when people are detected.";
|
||||
"config.save.confirm"="電台將會在設定儲存後重啟。";
|
||||
"connected.radio"="已連接的電台";
|
||||
"communicating"="與電台進行通訊中...";
|
||||
"connected"="已連接";
|
||||
"connecting"="連接中...";
|
||||
"contacts"="聯絡人";
|
||||
"contacts %@"="聯絡人 (%@)";
|
||||
"copy"="複製";
|
||||
"current"="目前";
|
||||
"default"="預設";
|
||||
"delete"="刪除";
|
||||
"detection.sensor"="檢測感測器";
|
||||
"device"="設備";
|
||||
"device.config"="電台設定";
|
||||
"device.configuration"="設備設定";
|
||||
"device.metrics.delete"="刪除所有電台指標??";
|
||||
"device.metrics.log"="電台指標紀錄檔";
|
||||
"device.role.client"="標準模式 - App 可以連接到電台進行收發操作,並且會自動轉發 Mesh 網路中其他中繼點的消息。";
|
||||
"device.role.clientmute"="靜音模式 - 與標準模式類似,App 可以連接到電台進行收發操作,但不會轉發 Mesh 網路中其他中繼點的消息。";
|
||||
"device.role.clienthidden"=" Used for nodes that \"only speak when spoken to\" Turns all of the routine broadcasts but allows for ad-hoc communication. Still rebroadcasts, but with local only rebroadcast mode (known meshes only). Can be used for private operation or to dramatically reduce airtime / power consumption.";
|
||||
"device.role.lostandfound"="Used to automatically send a text message to the mesh with the current position of the device on a frequent interval: \"I'm lost! Position: lat / long\"";
|
||||
"device.role.router"="纯路由模式 - 自動轉發 Mesh 網路中其他中繼點的消息,中繼模式下螢幕會熄滅,Wi-Fi 和藍芽將會進入睡眠模式,App 將無法連接到電台進行收發操作。";
|
||||
"device.role.routerclient"="路由客户端模式 - 優先轉發 Mesh 網路中其他中繼點的消息,App 也可以連接到電台進行收發操作。";
|
||||
"device.role.repeater"="中繼模式 - Mesh 網路數據包將優先通過此中繼點路由。此模式可消除不必要的開銷,如 NodeInfo、DeviceTelemetry 和任何其他 Mesh 數據包,從而使設備不顯示為 Mesh 網路的一部分。有關此角色的其他特定設置,請參閱轉播模式。";
|
||||
"device.role.tracker"="追蹤模式 - 用於作為 GPS 追蹤器。從該設備發送的定位數據包優先級較高,每兩分鐘廣播一次。智能位置廣播預設為關閉。";
|
||||
"device.role.sensor"="Broadcasts telemetry packets as priority.";
|
||||
"device.role.tak"="Optimized for ATAK system communication, reduces routine broadcasts.";
|
||||
"device.role.taktracker"="Enables automatic TAK PLI broadcasts and reduces routine broadcasts.";
|
||||
"direct.messages"="聊天";
|
||||
"dismiss.keyboard"="隱藏鍵盤";
|
||||
"display"="螢幕(電台螢幕)";
|
||||
"display.config"="螢幕設定";
|
||||
"distance"="距離";
|
||||
"disconnect"="斷開連接";
|
||||
"echo"="echo";
|
||||
"email.address"="電子信箱";
|
||||
"enabled"="啟用";
|
||||
"encrypted"="加密";
|
||||
"external.notification"="外部通知";
|
||||
"external.notification.config"="外部通知設定";
|
||||
"finish"="完成";
|
||||
"firmware.version"="韌體版本";
|
||||
"firmware.version.unsupported"="檢測到不支援的韌體版本,無法連接到電台。";
|
||||
"gas"="Gas";
|
||||
"gas.resistance"="Gas Resistance";
|
||||
"generate.qr.code"="生成QRcode";
|
||||
"gpsformat.dec"="十進制";
|
||||
"gpsformat.dms"="度分秒";
|
||||
"gpsformat.utm"="通用橫軸墨卡托投影";
|
||||
"gpsformat.mgrs"="軍事網格系統";
|
||||
"gpsformat.olc"="開放的位置代碼(又稱加碼)";
|
||||
"gpsformat.osgr"="英國國土測量局網格";
|
||||
"heard"="收到";
|
||||
"heard.last"="最後收到";
|
||||
"hybrid"="混合";
|
||||
"hybrid.flyover"="混合視圖";
|
||||
"include"="包含";
|
||||
"inputevent.none"="無";
|
||||
"inputevent.up"="上";
|
||||
"inputevent.down"="下";
|
||||
"inputevent.left"="左";
|
||||
"inputevent.right"="右";
|
||||
"inputevent.select"="選擇";
|
||||
"inputevent.back"="返回";
|
||||
"inputevent.cancel"="取消";
|
||||
"interval.one.second"="一秒";
|
||||
"interval.two.seconds"="兩秒";
|
||||
"interval.three.seconds"="三秒";
|
||||
"interval.four.seconds"="四秒";
|
||||
"interval.five.seconds"="五秒";
|
||||
"interval.ten.seconds"="十秒";
|
||||
"interval.fifteen.seconds"="十五秒";
|
||||
"interval.twenty.seconds"="二十秒";
|
||||
"interval.twentyfive.seconds"="二十五秒";
|
||||
"interval.thirty.seconds"="三十秒";
|
||||
"interval.fortyfive.seconds"="四十五秒";
|
||||
"interval.one.minute"="一分鐘";
|
||||
"interval.two.minutes"="兩分鐘";
|
||||
"interval.five.minutes"="五分鐘";
|
||||
"interval.ten.minutes"="十分鐘";
|
||||
"interval.fifteen.minutes"="十五分鐘";
|
||||
"interval.thirty.minutes"="三十分鐘";
|
||||
"interval.one.hour"="一小時";
|
||||
"interval.two.hours"="兩小時";
|
||||
"interval.three.hours"="三小時";
|
||||
"interval.four.hours"="四小時";
|
||||
"interval.five.hours"="五小時";
|
||||
"interval.six.hours"="六小時";
|
||||
"interval.twelve.hours"="十二小時";
|
||||
"interval.eighteen.hours"="十八小時";
|
||||
"interval.twentyfour.hours"="二十四小時";
|
||||
"interval.thirtysix.hours"="三十六小時";
|
||||
"interval.tyeight.hours"="四十八小时小時";
|
||||
"interval.eventytwo.hours"="七十二小時";
|
||||
"keyboard.type"="鍵盤類型";
|
||||
"logging"="加載中";
|
||||
"lora"="LoRa";
|
||||
"lora.config"="LoRa 設定";
|
||||
"map"="Mesh 地圖";
|
||||
"map.centering"="居中";
|
||||
"map.tiles.delete"="刪除已緩存的地圖區塊";
|
||||
"map.recentering"="自動重新居中";
|
||||
"map.use.legacy"="Use Legacy Mesh Map";
|
||||
"map.type"="地圖類型";
|
||||
"map.usertrackingmode"="使用者跟隨模式";
|
||||
"map.usertrackingmode.none"="無";
|
||||
"map.usertrackingmode.follow"="跟隨";
|
||||
"map.usertrackingmode.followwithheading"="Follow with heading";
|
||||
"mesh.live.activity"="Mesh 即時活動";
|
||||
"mesh.log"="Mesh 紀錄檔";
|
||||
"mesh.log.ambientlighting.config %@"="Ambient Lighting module config received: %@";
|
||||
"mesh.log.bluetooth.config %@"="Bluetooth config received: %@";
|
||||
"mesh.log.cannedmessage.config %@"="Canned Message module config received: %@";
|
||||
"mesh.log.cannedmessages.messages.get %@"="Requested Canned Messages Module Messages for node: %@";
|
||||
"mesh.log.cannedmessages.messages.received %@"="Canned Messages Messages Received For: %@";
|
||||
"mesh.log.channel.sent %@ %d"="Sent a Channel for: %@ Channel Index %d";
|
||||
"mesh.log.channel.received %d %@"="Channel %d received from: %@";
|
||||
"mesh.log.device.config %@"="收到裝置設定: %@";
|
||||
"mesh.log.display.config %@"="收到顯示模組設定: %@";
|
||||
"mesh.log.devicemetadata %@"="Requesting Device Metadata for %@";
|
||||
"mesh.log.device.metadata.received %@"="Device Metadata admin message received from: %@";
|
||||
"mesh.log.detectionsensor.config %@"="Detection Sensor module config received: %@";
|
||||
"mesh.log.externalnotification.config %@"="External Notification module config received: %@";
|
||||
"mesh.log.lora.config %@"="收到LoRa設定: %@";
|
||||
"mesh.log.lora.config.sent %@"="Sent a LoRa.Config for: %@";
|
||||
"mesh.log.mqtt.config %@"="MQTT module config received: %@";
|
||||
"mesh.log.myinfo %@"="MyInfo received: %@";
|
||||
"mesh.log.network.config %@"="收到網路設定: %@";
|
||||
"mesh.log.nodeinfo.received %@"="收到中繼點訊息: %@";
|
||||
"mesh.log.paxcounter %@"="PAX Counter message received for: %@";
|
||||
"mesh.log.position.config %@"="Positon config received: %@";
|
||||
"mesh.log.position.received %@"="從中繼點接收到定位封包: %@";
|
||||
"mesh.log.rangetest.config %@"="收到拉距測試模組設定: %@";
|
||||
"mesh.log.ringtone.config %@"="RTTTL Ringtone config received: %@";
|
||||
"mesh.log.routing.message %@ %@"="Routing received for RequestID: %@ Ack Status: %@";
|
||||
"mesh.log.serial.config %@"="Serial module config received: %@";
|
||||
"mesh.log.sharelocation %@"="傳送iOS裝置的GPS定位封包到中繼點上: %@";
|
||||
"mesh.log.storeforward.config %@"="Store & Forward module config received: %@";
|
||||
"mesh.log.telemetry.config %@"="收到遠測模組設定: %@";
|
||||
"mesh.log.telemetry.received %@"="收到遠測資料: %@";
|
||||
"mesh.log.textmessage.received"="Message received from the text message app.";
|
||||
"mesh.log.textmessage.send.failed %@"="訊息傳送失敗, 沒有正確連接到 %@";
|
||||
"mesh.log.textmessage.sent %@ %@ %@"="傳送訊息 %@ 從 %@ 到 %@";
|
||||
"mesh.log.traceroute.received.direct %@"="Trace Route request sent to node: %@ was recieived directly.";
|
||||
"mesh.log.traceroute.received.route %@"="Trace Route request returned: %@";
|
||||
"mesh.log.traceroute.sent %@"="Sent a Trace Route Request to node: %@";
|
||||
"mesh.log.wantconfig %@"="Issuing Want Config to %@";
|
||||
"mesh.log.waypoint.sent %@"="Sent a Waypoint Packet from: %@";
|
||||
"mesh.log.waypoint.received %@"="Waypoint Packet received from node: %@";
|
||||
"message"="訊息";
|
||||
"message.details"="詳細訊息";
|
||||
"messages"="訊息";
|
||||
"mode"="模式";
|
||||
"module.configuration"="模塊設定";
|
||||
"mqtt"="MQTT";
|
||||
"mqtt.connect"="Connect to MQTT";
|
||||
"mqtt.config"="MQTT 設定";
|
||||
"mqtt.clientproxy"="MQTT 客户端代理";
|
||||
"mqtt.disconnect"="Disconnect from MQTT";
|
||||
"mqtt.username"="用戶名稱";
|
||||
"name"="名稱";
|
||||
"network"="網路";
|
||||
"network.config"="網路設定";
|
||||
"nodes"="中繼點";
|
||||
"nodes %@"="中繼點 (%@)";
|
||||
"no.nodes"="未找到 Meshtastic 中繼點";
|
||||
"not.connected"="未連接到電台";
|
||||
"numbers.punctuation"="數字和標點符號";
|
||||
"off"="關閉";
|
||||
"offline"="離線";
|
||||
"on.boot"="只在啟動時";
|
||||
"options"="選項";
|
||||
"password"="密碼";
|
||||
"pause"="暫停";
|
||||
"phone.gps"="手機 GPS";
|
||||
"phone.gps.interval.description"="電台通過手機獲得定位的時間間隔,但是向 Mesh 網路中更新定位的時間間隔由電台控制。";
|
||||
"position"="定位";
|
||||
"position.config"="定位設定";
|
||||
"preferred.radio"="首選電台";
|
||||
"radio.configuration"="電台設定";
|
||||
"range.test"="拉距測試";
|
||||
"range.test.blocked"="區塊範圍測試";
|
||||
"range.test.config"="拉距測試設定";
|
||||
"reply"="回復";
|
||||
"reboot"="重新啟動";
|
||||
"reboot.node"="重啟中繼點";
|
||||
"received.ack"="收到確認";
|
||||
"received.ack.real"="收件人確認";
|
||||
"resume"="恢復";
|
||||
"ringtone"="鈴聲";
|
||||
"ringtone.config"="鈴聲設定";
|
||||
"route.recorder"="路線錄製";
|
||||
"routes"="路線";
|
||||
"routing.acknowledged"="確認";
|
||||
"routing.noroute"="找不到目標";
|
||||
"routing.gotnak"="收到否認";
|
||||
"routing.timeout"="逾時";
|
||||
"routing.nointerface"="無連接";
|
||||
"routing.maxretransmit"="已達到最大重試次數";
|
||||
"routing.nochannel"="没有頻道";
|
||||
"routing.toolarge"="數據包過大";
|
||||
"routing.noresponse"="無回應";
|
||||
"routing.dutycyclelimit"="已達到物錢區域循環週期發射上限";
|
||||
"routing.badRequest"="錯誤請求";
|
||||
"routing.notauthorized"="未授權";
|
||||
"satellite"="衛星";
|
||||
"satellite.flyover"="衛星識圖";
|
||||
"save"="儲存";
|
||||
"save.config %@"="儲存%@的設定";
|
||||
"serial"="串口";
|
||||
"serial.config"="串口設定";
|
||||
"serial.mode.default"="預設";
|
||||
"serial.mode.simple"="簡單";
|
||||
"serial.mode.proto"="Protobufs";
|
||||
"serial.mode.txtmsg"="文本訊息";
|
||||
"serial.mode.nmea"="NMEA 位置";
|
||||
"settings"="設定";
|
||||
"share.channels"="分享頻道QRcode";
|
||||
"share.position"="分享位置";
|
||||
"subscribed"="連接到 Mesh 網路";
|
||||
"select.contact"="選擇聯絡人";
|
||||
"select.node"="選擇中繼點";
|
||||
"select.menu.item"="從菜單選擇項目";
|
||||
"set.region"="設定 LoRa 區域";
|
||||
"standard"="標準";
|
||||
"standard.muted"="標準靜音";
|
||||
"start"="開始";
|
||||
"ssid"="SSID";
|
||||
"storeforward"="儲存 & 轉發";
|
||||
"storeforward.config"="儲存 & 轉發設定";
|
||||
"storeforward.heartbeat"="發送心跳包";
|
||||
"tapback"="響應";
|
||||
"tapback.heart"="心";
|
||||
"tapback.thumbsup"="豎大拇指";
|
||||
"tapback.thumbsdown"="倒大拇指";
|
||||
"tapback.haha"="哈哈";
|
||||
"tapback.exclamation"="驚嘆號";
|
||||
"tapback.question"="問號";
|
||||
"tapback.poop"="便便";
|
||||
"tapback.wave"="Wave";
|
||||
"telemetry"="遠測(傳感器)";
|
||||
"telemetry.config"="遠側設定";
|
||||
"timeout"="超時";
|
||||
"timestamp"="時間戳記";
|
||||
"tip.bluetooth.connect.title"="連接到 LoRa 電台";
|
||||
"tip.bluetooth.connect.message"="顯示目前通過藍芽連接的 Lora 電台的信息。您可以向左滑動斷開電台,長按查看統計訊息或開始即時活動。";
|
||||
"tip.channel.admin.title"="Admin Channel";
|
||||
"tip.channel.admin.message"="Admin channel detected: Select a node from the drop down to manage connected or remote devices.";
|
||||
"tip.channels.create.title"="管理頻道";
|
||||
"tip.channels.create.message"="現在 Mesh 上的資料會通過主通道發送。您可以設定輔助通道來建立由自己的金鑰保護的其他訊息組 [頻道設定提示](https://meshtastic.org/docs/configuration/radio/channels/)";
|
||||
"tip.channels.share.title"="共享 Meshtastic 頻道";
|
||||
"tip.channels.share.message"="在 Meshtastic 網路中最多有 8 個頻道。第一個頻道是主頻道,大多數活動都發生在這裡,也是必需的。如果您不共享主頻道,您的第一個共享頻道就會成為其他網路的主頻道。它會在其主頻道和您的輔助頻道上對話。名稱為 admin 的頻道可遠端控制中繼點。其他頻道用於私人群组,每個群組都有自己的密鑰。";
|
||||
"tip.messages.title"="消息";
|
||||
"tip.messages.message"="您可以發送和接收1對1聊天和群聊。在任何訊息中,您都可以長按查看可用的操作,如複製、回復、拍一拍、刪除以及詳情。";
|
||||
"twitter"="Twitter";
|
||||
"unknown"="未知";
|
||||
"unknown.age"="未知時間";
|
||||
"unset"="未設置";
|
||||
"update.firmware"="更新韌體";
|
||||
"update.interval"="更新間隔";
|
||||
"user"="使用者";
|
||||
"user.details"="使用者資料";
|
||||
"voltage"="電壓";
|
||||
"waiting"="等待中...";
|
||||
Loading…
Add table
Add a link
Reference in a new issue