mirror of
https://github.com/meshtastic/Meshtastic-Apple.git
synced 2026-04-20 22:13:56 +00:00
Merge pull request #278 from meshtastic/2.0.10_Working_Changes
2.0.10 working changes
This commit is contained in:
commit
c13aacafef
60 changed files with 2498 additions and 1789 deletions
|
|
@ -36,7 +36,6 @@
|
|||
DD457188293C7E63000C49FB /* SignalStrengthIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD457187293C7E63000C49FB /* SignalStrengthIndicator.swift */; };
|
||||
DD47E3CE26F103C600029299 /* NodeList.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD47E3CD26F103C600029299 /* NodeList.swift */; };
|
||||
DD47E3D626F17ED900029299 /* CircleText.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD47E3D526F17ED900029299 /* CircleText.swift */; };
|
||||
DD47E3D926F3093800029299 /* MessageBubble.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD47E3D826F3093800029299 /* MessageBubble.swift */; };
|
||||
DD4A911E2708C65400501B7E /* AppSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4A911D2708C65400501B7E /* AppSettings.swift */; };
|
||||
DD4C158C2824A91E0032668E /* module_config.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4C158B2824A91E0032668E /* module_config.pb.swift */; };
|
||||
DD4C158E2824AA7E0032668E /* config.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4C158D2824AA7E0032668E /* config.pb.swift */; };
|
||||
|
|
@ -69,6 +68,7 @@
|
|||
DD913639270DFF4C00D7ACF3 /* LocalNotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD913638270DFF4C00D7ACF3 /* LocalNotificationManager.swift */; };
|
||||
DD97E96628EFD9820056DDA4 /* MeshtasticLogo.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD97E96528EFD9820056DDA4 /* MeshtasticLogo.swift */; };
|
||||
DD97E96828EFE9A00056DDA4 /* About.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD97E96728EFE9A00056DDA4 /* About.swift */; };
|
||||
DD994B69295F88B60013760A /* IntervalEnums.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD994B68295F88B60013760A /* IntervalEnums.swift */; };
|
||||
DDA0B6B2294CDC55001356EC /* Channels.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA0B6B1294CDC55001356EC /* Channels.swift */; };
|
||||
DDA1C48E28DB49D3009933EC /* ChannelRoles.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA1C48D28DB49D3009933EC /* ChannelRoles.swift */; };
|
||||
DDA6B2E928419CF2003E8C16 /* MeshPackets.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA6B2E828419CF2003E8C16 /* MeshPackets.swift */; };
|
||||
|
|
@ -105,6 +105,7 @@
|
|||
DDD3BBD5292D763200D609B3 /* MeshtasticTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD3BBD4292D763200D609B3 /* MeshtasticTests.swift */; };
|
||||
DDD94A502845C8F5004A87A0 /* DateTimeText.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD94A4F2845C8F5004A87A0 /* DateTimeText.swift */; };
|
||||
DDD9E4E4284B208E003777C5 /* UserEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD9E4E3284B208E003777C5 /* UserEntityExtension.swift */; };
|
||||
DDE0F7C5295F77B700B8AAB3 /* AppSettingsEnums.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE0F7C4295F77B700B8AAB3 /* AppSettingsEnums.swift */; };
|
||||
DDF924CA26FBB953009FE055 /* ConnectedDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF924C926FBB953009FE055 /* ConnectedDevice.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
|
|
@ -152,9 +153,9 @@
|
|||
DD415827285859C4009B0E59 /* TelemetryConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TelemetryConfig.swift; sourceTree = "<group>"; };
|
||||
DD41582928585C32009B0E59 /* RangeTestConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RangeTestConfig.swift; sourceTree = "<group>"; };
|
||||
DD457187293C7E63000C49FB /* SignalStrengthIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignalStrengthIndicator.swift; sourceTree = "<group>"; };
|
||||
DD457BC4295D5E35004BCE4D /* MeshtasticDataModelV5.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV5.xcdatamodel; sourceTree = "<group>"; };
|
||||
DD47E3CD26F103C600029299 /* NodeList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeList.swift; sourceTree = "<group>"; };
|
||||
DD47E3D526F17ED900029299 /* CircleText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircleText.swift; sourceTree = "<group>"; };
|
||||
DD47E3D826F3093800029299 /* MessageBubble.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageBubble.swift; sourceTree = "<group>"; };
|
||||
DD4A911D2708C65400501B7E /* AppSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettings.swift; sourceTree = "<group>"; };
|
||||
DD4C158B2824A91E0032668E /* module_config.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = module_config.pb.swift; sourceTree = "<group>"; };
|
||||
DD4C158D2824AA7E0032668E /* config.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = config.pb.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -188,6 +189,7 @@
|
|||
DD913638270DFF4C00D7ACF3 /* LocalNotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalNotificationManager.swift; sourceTree = "<group>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
|
|
@ -232,6 +234,7 @@
|
|||
DDD3BBD4292D763200D609B3 /* MeshtasticTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MeshtasticTests.swift; sourceTree = "<group>"; };
|
||||
DDD94A4F2845C8F5004A87A0 /* DateTimeText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateTimeText.swift; sourceTree = "<group>"; };
|
||||
DDD9E4E3284B208E003777C5 /* UserEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserEntityExtension.swift; sourceTree = "<group>"; };
|
||||
DDE0F7C4295F77B700B8AAB3 /* AppSettingsEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettingsEnums.swift; sourceTree = "<group>"; };
|
||||
DDEE03EC29544A1000FCAD57 /* MeshtasticDataModelV4.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV4.xcdatamodel; sourceTree = "<group>"; };
|
||||
DDF924C926FBB953009FE055 /* ConnectedDevice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectedDevice.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
|
@ -340,10 +343,10 @@
|
|||
children = (
|
||||
DD6193762862F90F00E59241 /* CannedMessagesConfig.swift */,
|
||||
DD6193742862F6E600E59241 /* ExternalNotificationConfig.swift */,
|
||||
DD2160AE28C5552500C17253 /* MQTTConfig.swift */,
|
||||
DD41582928585C32009B0E59 /* RangeTestConfig.swift */,
|
||||
DD6193782863875F00E59241 /* SerialConfig.swift */,
|
||||
DD415827285859C4009B0E59 /* TelemetryConfig.swift */,
|
||||
DD2160AE28C5552500C17253 /* MQTTConfig.swift */,
|
||||
);
|
||||
path = Module;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -361,6 +364,7 @@
|
|||
DD8ED9C6289CE4A100B3B0AB /* Enums */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DDE0F7C4295F77B700B8AAB3 /* AppSettingsEnums.swift */,
|
||||
DDB6ABD828B0A4BA00384BA1 /* BluetoothModes.swift */,
|
||||
DD1925B628CDA5A400720036 /* CannedMessagesConfigEnums.swift */,
|
||||
DDA1C48D28DB49D3009933EC /* ChannelRoles.swift */,
|
||||
|
|
@ -372,6 +376,7 @@
|
|||
DDB6ABE128B13FB500384BA1 /* PositionConfigEnums.swift */,
|
||||
DD8ED9C7289CE4B900B3B0AB /* RoutingError.swift */,
|
||||
DD1925B828CDA93900720036 /* SerialConfigEnums.swift */,
|
||||
DD994B68295F88B60013760A /* IntervalEnums.swift */,
|
||||
);
|
||||
path = Enums;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -520,7 +525,6 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
DD47E3D526F17ED900029299 /* CircleText.swift */,
|
||||
DD47E3D826F3093800029299 /* MessageBubble.swift */,
|
||||
DDF924C926FBB953009FE055 /* ConnectedDevice.swift */,
|
||||
DDC3B273283F411B00AC321C /* LastHeardText.swift */,
|
||||
DDA6B2EA28420A7B003E8C16 /* NodeAnnotation.swift */,
|
||||
|
|
@ -755,6 +759,7 @@
|
|||
DD3CC6B528E33FD100FA9159 /* ShareChannels.swift in Sources */,
|
||||
DD1BF2F92776FE2E008C8D2F /* UserMessageList.swift in Sources */,
|
||||
DD3CC6C228EB9D4900FA9159 /* UpdateCoreData.swift in Sources */,
|
||||
DDE0F7C5295F77B700B8AAB3 /* AppSettingsEnums.swift in Sources */,
|
||||
DDB6ABE628B1406100384BA1 /* LoraConfigEnums.swift in Sources */,
|
||||
DDB2CC6E27F3EB47009C5FCC /* telemetry.pb.swift in Sources */,
|
||||
DD23A50F26FD1B4400D9B90C /* PeripheralModel.swift in Sources */,
|
||||
|
|
@ -795,8 +800,8 @@
|
|||
DDD94A502845C8F5004A87A0 /* DateTimeText.swift in Sources */,
|
||||
C9A88B57278B559900BD810A /* apponly.pb.swift in Sources */,
|
||||
DD4C158E2824AA7E0032668E /* config.pb.swift in Sources */,
|
||||
DD47E3D926F3093800029299 /* MessageBubble.swift in Sources */,
|
||||
DDB6ABE228B13FB500384BA1 /* PositionConfigEnums.swift in Sources */,
|
||||
DD994B69295F88B60013760A /* IntervalEnums.swift in Sources */,
|
||||
DD415828285859C4009B0E59 /* TelemetryConfig.swift in Sources */,
|
||||
DD73FD1128750779000852D6 /* PositionLog.swift in Sources */,
|
||||
DD3CC6C028E7A60700FA9159 /* MessagingEnums.swift in Sources */,
|
||||
|
|
@ -1001,7 +1006,7 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.0.9;
|
||||
MARKETING_VERSION = 2.0.10;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
|
|
@ -1034,7 +1039,7 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.0.9;
|
||||
MARKETING_VERSION = 2.0.10;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
|
|
@ -1210,12 +1215,13 @@
|
|||
DD3CC6BA28E366DF00FA9159 /* Meshtastic.xcdatamodeld */ = {
|
||||
isa = XCVersionGroup;
|
||||
children = (
|
||||
DD457BC4295D5E35004BCE4D /* MeshtasticDataModelV5.xcdatamodel */,
|
||||
DDEE03EC29544A1000FCAD57 /* MeshtasticDataModelV4.xcdatamodel */,
|
||||
DDCDC69A29467643004C1DDA /* MeshtasticDataModelV3.xcdatamodel */,
|
||||
DD5D0A9A2931AD6B00F7EA61 /* MeshtasticDataModelV2.xcdatamodel */,
|
||||
DD3CC6BB28E366DF00FA9159 /* MeshtasticDataModel.xcdatamodel */,
|
||||
);
|
||||
currentVersion = DDEE03EC29544A1000FCAD57 /* MeshtasticDataModelV4.xcdatamodel */;
|
||||
currentVersion = DD457BC4295D5E35004BCE4D /* MeshtasticDataModelV5.xcdatamodel */;
|
||||
name = Meshtastic.xcdatamodeld;
|
||||
path = Meshtastic/Meshtastic.xcdatamodeld;
|
||||
sourceTree = "<group>";
|
||||
|
|
|
|||
93
Meshtastic/Enums/AppSettingsEnums.swift
Normal file
93
Meshtastic/Enums/AppSettingsEnums.swift
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
//
|
||||
// AppSettingsEnums.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Copyright(c) Garth Vander Houwen 12/30/22.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
enum KeyboardType: Int, CaseIterable, Identifiable {
|
||||
|
||||
case defaultKeyboard = 0
|
||||
case asciiCapable = 1
|
||||
case twitter = 9
|
||||
case emailAddress = 7
|
||||
case numbersAndPunctuation = 2
|
||||
|
||||
var id: Int { self.rawValue }
|
||||
var description: String {
|
||||
get {
|
||||
switch self {
|
||||
case .defaultKeyboard:
|
||||
return NSLocalizedString("default", comment: "Default Keyboard")
|
||||
case .asciiCapable:
|
||||
return NSLocalizedString("ascii.capable", comment: "ASCII Capable Keyboard")
|
||||
case .twitter:
|
||||
return NSLocalizedString("twitter", comment: "Twitter Keyboard")
|
||||
case .emailAddress:
|
||||
return NSLocalizedString("email.address", comment: "Email Address Keyboard")
|
||||
case .numbersAndPunctuation:
|
||||
return NSLocalizedString("numbers.punctuation", comment: "Numbers and Punctuation Keyboard")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum MeshMapType: String, CaseIterable, Identifiable {
|
||||
|
||||
case satellite = "satellite"
|
||||
case hybrid = "hybrid"
|
||||
case standard = "standard"
|
||||
|
||||
var id: String { self.rawValue }
|
||||
|
||||
var description: String {
|
||||
get {
|
||||
switch self {
|
||||
case .satellite:
|
||||
return NSLocalizedString("satellite", comment: "Satellite Map Type")
|
||||
case .standard:
|
||||
return NSLocalizedString("standard", comment: "Standard Map Type")
|
||||
case .hybrid:
|
||||
return NSLocalizedString("hybrid", comment: "Hybrid Map Type")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum LocationUpdateInterval: Int, CaseIterable, Identifiable {
|
||||
|
||||
case fiveSeconds = 5
|
||||
case tenSeconds = 10
|
||||
case fifteenSeconds = 15
|
||||
case thirtySeconds = 30
|
||||
case oneMinute = 60
|
||||
case fiveMinutes = 300
|
||||
case tenMinutes = 600
|
||||
case fifteenMinutes = 900
|
||||
|
||||
var id: Int { self.rawValue }
|
||||
var description: String {
|
||||
get {
|
||||
switch self {
|
||||
case .fiveSeconds:
|
||||
return NSLocalizedString("interval.five.seconds", comment: "Five Seconds")
|
||||
case .tenSeconds:
|
||||
return NSLocalizedString("interval.ten.seconds", comment: "Ten Seconds")
|
||||
case .fifteenSeconds:
|
||||
return NSLocalizedString("interval.fifteen.seconds", comment: "Fifteen Seconds")
|
||||
case .thirtySeconds:
|
||||
return NSLocalizedString("interval.thirty.seconds", comment: "Thirty Seconds")
|
||||
case .oneMinute:
|
||||
return NSLocalizedString("interval.one.minute", comment: "One Minute")
|
||||
case .fiveMinutes:
|
||||
return NSLocalizedString("interval.five.minutes", comment: "Five Minutes")
|
||||
case .tenMinutes:
|
||||
return NSLocalizedString("interval.ten.minutes", comment: "Ten Minutes")
|
||||
case .fifteenMinutes:
|
||||
return NSLocalizedString("interval.fifteen.minutes", comment: "Fifteen Minutes")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -24,14 +24,14 @@ enum EthernetMode: Int, CaseIterable, Identifiable {
|
|||
}
|
||||
}
|
||||
}
|
||||
func protoEnumValue() -> Config.NetworkConfig.EthMode {
|
||||
func protoEnumValue() -> Config.NetworkConfig.AddressMode {
|
||||
|
||||
switch self {
|
||||
|
||||
case .dhcp:
|
||||
return Config.NetworkConfig.EthMode.dhcp
|
||||
return Config.NetworkConfig.AddressMode.dhcp
|
||||
case .staticip:
|
||||
return Config.NetworkConfig.EthMode.static
|
||||
return Config.NetworkConfig.AddressMode.static
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
162
Meshtastic/Enums/IntervalEnums.swift
Normal file
162
Meshtastic/Enums/IntervalEnums.swift
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
//
|
||||
// UpdateIntervals.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Created by Garth Vander Houwen on 12/30/22.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
enum OutputIntervals: Int, CaseIterable, Identifiable {
|
||||
|
||||
case unset = 0
|
||||
case oneSecond = 1000
|
||||
case twoSeconds = 2000
|
||||
case threeSeconds = 3000
|
||||
case fourSeconds = 4000
|
||||
case fiveSeconds = 5000
|
||||
case tenSeconds = 10000
|
||||
case fifteenSeconds = 15000
|
||||
case thirtySeconds = 30000
|
||||
case oneMinute = 60000
|
||||
|
||||
var id: Int { self.rawValue }
|
||||
var description: String {
|
||||
get {
|
||||
switch self {
|
||||
|
||||
case .unset:
|
||||
return NSLocalizedString("unset", comment: "Unset")
|
||||
case .oneSecond:
|
||||
return NSLocalizedString("interval.one.second", comment: "One Second")
|
||||
case .twoSeconds:
|
||||
return NSLocalizedString("interval.two.seconds", comment: "Two Seconds")
|
||||
case .threeSeconds:
|
||||
return NSLocalizedString("interval.three.seconds", comment: "Three Seconds")
|
||||
case .fourSeconds:
|
||||
return NSLocalizedString("interval.four.seconds", comment: "Four Seconds")
|
||||
case .fiveSeconds:
|
||||
return NSLocalizedString("interval.five.seconds", comment: "Five Seconds")
|
||||
case .tenSeconds:
|
||||
return NSLocalizedString("interval.ten.seconds", comment: "Ten Seconds")
|
||||
case .fifteenSeconds:
|
||||
return NSLocalizedString("interval.fifteen.seconds", comment: "Fifteen Seconds")
|
||||
case .thirtySeconds:
|
||||
return NSLocalizedString("interval.thirty.seconds", comment: "Thirty Seconds")
|
||||
case .oneMinute:
|
||||
return NSLocalizedString("interval.one.minute", comment: "One Minute")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Default of 0 is off
|
||||
enum SenderIntervals: Int, CaseIterable, Identifiable {
|
||||
|
||||
case off = 0
|
||||
case fifteenSeconds = 15
|
||||
case thirtySeconds = 30
|
||||
case oneMinute = 60
|
||||
case fiveMinutes = 300
|
||||
case tenMinutes = 600
|
||||
case fifteenMinutes = 900
|
||||
case thirtyMinutes = 1800
|
||||
case oneHour = 3600
|
||||
|
||||
|
||||
var id: Int { self.rawValue }
|
||||
var description: String {
|
||||
get {
|
||||
switch self {
|
||||
case .off:
|
||||
return NSLocalizedString("off", comment: "Off")
|
||||
case .fifteenSeconds:
|
||||
return NSLocalizedString("interval.fifteen.seconds", comment: "Fifteen Seconds")
|
||||
case .thirtySeconds:
|
||||
return NSLocalizedString("interval.thirty.seconds", comment: "Thirty Seconds")
|
||||
case .oneMinute:
|
||||
return NSLocalizedString("interval.one.minute", comment: "One Minute")
|
||||
case .fiveMinutes:
|
||||
return NSLocalizedString("interval.five.minutes", comment: "Five Minutes")
|
||||
case .tenMinutes:
|
||||
return NSLocalizedString("interval.ten.minutes", comment: "Ten Minutes")
|
||||
case .fifteenMinutes:
|
||||
return NSLocalizedString("interval.fifteen.minutes", comment: "Fifteen Minutes")
|
||||
case .thirtyMinutes:
|
||||
return NSLocalizedString("interval.thirty.minutes", comment: "Thirty Minutes")
|
||||
case .oneHour:
|
||||
return NSLocalizedString("interval.one.hour", comment: "One Hour")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum UpdateIntervals: Int, CaseIterable, Identifiable {
|
||||
|
||||
case fifteenSeconds = 15
|
||||
case thirtySeconds = 30
|
||||
case oneMinute = 60
|
||||
case fiveMinutes = 300
|
||||
case tenMinutes = 600
|
||||
case fifteenMinutes = 900
|
||||
case thirtyMinutes = 1800
|
||||
case oneHour = 3600
|
||||
case twoHours = 7200
|
||||
case threeHours = 10800
|
||||
case fourHours = 14400
|
||||
case fiveHours = 18000
|
||||
case sixHours = 21600
|
||||
case twelveHours = 43200
|
||||
case eighteenHours = 64800
|
||||
case twentyFourHours = 86400
|
||||
case thirtySixHours = 129600
|
||||
case fortyeightHours = 172800
|
||||
case seventyTwoHours = 259200
|
||||
|
||||
var id: Int { self.rawValue }
|
||||
var description: String {
|
||||
get {
|
||||
switch self {
|
||||
|
||||
case .fifteenSeconds:
|
||||
return NSLocalizedString("interval.fifteen.seconds", comment: "Fifteen Seconds")
|
||||
case .thirtySeconds:
|
||||
return NSLocalizedString("interval.thirty.seconds", comment: "Thirty Seconds")
|
||||
case .oneMinute:
|
||||
return NSLocalizedString("interval.one.minute", comment: "One Minute")
|
||||
case .fiveMinutes:
|
||||
return NSLocalizedString("interval.five.minutes", comment: "Five Minutes")
|
||||
case .tenMinutes:
|
||||
return NSLocalizedString("interval.ten.minutes", comment: "Ten Minutes")
|
||||
case .fifteenMinutes:
|
||||
return NSLocalizedString("interval.fifteen.minutes", comment: "Fifteen Minutes")
|
||||
case .thirtyMinutes:
|
||||
return NSLocalizedString("interval.thirty.minutes", comment: "Thirty Minutes")
|
||||
case .oneHour:
|
||||
return NSLocalizedString("interval.one.hour", comment: "One Hour")
|
||||
case .twoHours:
|
||||
return NSLocalizedString("interval.two.hours", comment: "Two Hours")
|
||||
case .threeHours:
|
||||
return NSLocalizedString("interval.three.hours", comment: "Three Hours")
|
||||
case .fourHours:
|
||||
return NSLocalizedString("interval.four.hours", comment: "Four Hours")
|
||||
case .fiveHours:
|
||||
return NSLocalizedString("interval.five.hours", comment: "Five Hours")
|
||||
case .sixHours:
|
||||
return NSLocalizedString("interval.six.hours", comment: "Six Hours")
|
||||
case .twelveHours:
|
||||
return NSLocalizedString("interval.twelve.hours", comment: "Twelve Hours")
|
||||
case .eighteenHours:
|
||||
return NSLocalizedString("interval.eighteen.hours", comment: "Eighteen Hours")
|
||||
case .twentyFourHours:
|
||||
return NSLocalizedString("interval.twentyfour.hours", comment: "Twenty Four Hours")
|
||||
case .thirtySixHours:
|
||||
return NSLocalizedString("interval.thirtysix.hours", comment: "Thirty Six Hours")
|
||||
case .fortyeightHours:
|
||||
return NSLocalizedString("interval.fortyeight.hours", comment: "Forty Eight Hours")
|
||||
case .seventyTwoHours:
|
||||
return NSLocalizedString("interval.seventytwo.hours", comment: "Seventy Two Hours")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,52 +7,6 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
enum PositionBroadcastIntervals: Int, CaseIterable, Identifiable {
|
||||
|
||||
case fifteenSeconds = 15
|
||||
case thirtySeconds = 30
|
||||
case oneMinute = 60
|
||||
case fiveMinutes = 300
|
||||
case tenMinutes = 600
|
||||
case fifteenMinutes = 900
|
||||
case thirtyMinutes = 1800
|
||||
case oneHour = 3600
|
||||
case sixHours = 21600
|
||||
case twelveHours = 43200
|
||||
case twentyFourHours = 86400
|
||||
|
||||
var id: Int { self.rawValue }
|
||||
var description: String {
|
||||
get {
|
||||
switch self {
|
||||
|
||||
case .fifteenSeconds:
|
||||
return NSLocalizedString("interval.fifteen.seconds", comment: "Fifteen Seconds")
|
||||
case .thirtySeconds:
|
||||
return NSLocalizedString("interval.thirty.seconds", comment: "Thirty Seconds")
|
||||
case .oneMinute:
|
||||
return NSLocalizedString("interval.one.minute", comment: "One Minute")
|
||||
case .fiveMinutes:
|
||||
return NSLocalizedString("interval.five.minutes", comment: "Five Minutes")
|
||||
case .tenMinutes:
|
||||
return NSLocalizedString("interval.ten.minutes", comment: "Ten Minutes")
|
||||
case .fifteenMinutes:
|
||||
return NSLocalizedString("interval.fifteen.minutes", comment: "Fifteen Minutes")
|
||||
case .thirtyMinutes:
|
||||
return NSLocalizedString("interval.thirty.minutes", comment: "Thirty Minutes")
|
||||
case .oneHour:
|
||||
return NSLocalizedString("interval.one.hour", comment: "One Hour")
|
||||
case .sixHours:
|
||||
return NSLocalizedString("interval.six.hours", comment: "Six Hours")
|
||||
case .twelveHours:
|
||||
return NSLocalizedString("interval.twelve.hours", comment: "Twelve Hours")
|
||||
case .twentyFourHours:
|
||||
return NSLocalizedString("interval.twentyfour.hours", comment: "Twenty Four Hours")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum GpsFormats: Int, CaseIterable, Identifiable {
|
||||
|
||||
case gpsFormatDec = 0
|
||||
|
|
@ -178,6 +132,8 @@ enum GpsAttemptTimes: Int, CaseIterable, Identifiable {
|
|||
case oneMinute = 60
|
||||
case twoMinutes = 120
|
||||
case fiveMinutes = 300
|
||||
case tenMinutes = 600
|
||||
case fifteenMinutes = 900
|
||||
|
||||
var id: Int { self.rawValue }
|
||||
var description: String {
|
||||
|
|
@ -204,6 +160,10 @@ enum GpsAttemptTimes: Int, CaseIterable, Identifiable {
|
|||
return NSLocalizedString("interval.two.minutes", comment: "Two Minutes")
|
||||
case .fiveMinutes:
|
||||
return NSLocalizedString("interval.five.minutes", comment: "Five Minutes")
|
||||
case .tenMinutes:
|
||||
return NSLocalizedString("interval.ten.minutes", comment: "Ten Minutes")
|
||||
case .fifteenMinutes:
|
||||
return NSLocalizedString("interval.fifteen.minutes", comment: "Fifteen Minutes")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -165,7 +165,7 @@ enum SerialTimeoutIntervals: Int, CaseIterable, Identifiable {
|
|||
get {
|
||||
switch self {
|
||||
case .unset:
|
||||
return "Unset"
|
||||
return NSLocalizedString("unset", comment: "Unset")
|
||||
case .oneSecond:
|
||||
return NSLocalizedString("interval.one.second", comment: "One Second")
|
||||
case .fiveSeconds:
|
||||
|
|
|
|||
|
|
@ -9,9 +9,11 @@ import SwiftUI
|
|||
|
||||
func TelemetryToCsvFile(telemetry: [TelemetryEntity], metricsType: Int) -> String {
|
||||
var csvString: String = ""
|
||||
let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMddjmma", options: 0, locale: Locale.current)
|
||||
let dateFormatString = (localeDateFormat ?? "MM/dd/YY j:mma")
|
||||
if metricsType == 0 {
|
||||
// Create Device Metrics Header
|
||||
csvString = "Battery Level, Voltage, Channel Utilization, Airtime, Timestamp"
|
||||
csvString = "\(NSLocalizedString("battery.level", comment: "")), \(NSLocalizedString("voltage", comment: "")), \(NSLocalizedString("channel.utilization", comment: "")), \(NSLocalizedString("airtime", comment: "")), \(NSLocalizedString("timestamp", comment: ""))"
|
||||
for dm in telemetry{
|
||||
if dm.metricsType == 0 {
|
||||
csvString += "\n"
|
||||
|
|
@ -23,12 +25,12 @@ func TelemetryToCsvFile(telemetry: [TelemetryEntity], metricsType: Int) -> Strin
|
|||
csvString += ", "
|
||||
csvString += String(dm.airUtilTx)
|
||||
csvString += ", "
|
||||
csvString += dm.time?.formattedDate(format: "yyyy-MM-dd HH:mm:ss") ?? NSLocalizedString("unknown.age", comment: "")
|
||||
csvString += dm.time?.formattedDate(format: dateFormatString) ?? NSLocalizedString("unknown.age", comment: "")
|
||||
}
|
||||
}
|
||||
} else if metricsType == 1 {
|
||||
// Create Environment Telemetry Header
|
||||
csvString = "Temperature, Relative Humidity, Barometric Pressure, Gas Resistance, Voltage, Current"
|
||||
csvString = "Temperature, Relative Humidity, Barometric Pressure, Gas Resistance, \(NSLocalizedString("voltage", comment: "")), \(NSLocalizedString("current", comment: "")), \(NSLocalizedString("timestamp", comment: ""))"
|
||||
for dm in telemetry{
|
||||
if dm.metricsType == 1 {
|
||||
csvString += "\n"
|
||||
|
|
@ -44,7 +46,7 @@ func TelemetryToCsvFile(telemetry: [TelemetryEntity], metricsType: Int) -> Strin
|
|||
csvString += ", "
|
||||
csvString += String(dm.current)
|
||||
csvString += ", "
|
||||
csvString += dm.time?.formattedDate(format: "yyyy-MM-dd HH:mm:ss") ?? NSLocalizedString("unknown.age", comment: "")
|
||||
csvString += dm.time?.formattedDate(format: dateFormatString) ?? NSLocalizedString("unknown.age", comment: "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -53,8 +55,10 @@ func TelemetryToCsvFile(telemetry: [TelemetryEntity], metricsType: Int) -> Strin
|
|||
|
||||
func PositionToCsvFile(positions: [PositionEntity]) -> String {
|
||||
var csvString: String = ""
|
||||
let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMddjmma", options: 0, locale: Locale.current)
|
||||
let dateFormatString = (localeDateFormat ?? "MM/dd/YY j:mma")
|
||||
// Create Position Header
|
||||
csvString = "SeqNo, Latitude, Longitude, Alt, Sats, Speed, Heading, SNR, Timestamp"
|
||||
csvString = "SeqNo, Latitude, Longitude, Altitude, Sats, Speed, Heading, SNR, \(NSLocalizedString("timestamp", comment: ""))"
|
||||
for pos in positions {
|
||||
csvString += "\n"
|
||||
csvString += String(pos.seqNo)
|
||||
|
|
@ -73,7 +77,7 @@ func PositionToCsvFile(positions: [PositionEntity]) -> String {
|
|||
csvString += ", "
|
||||
csvString += String(pos.snr)
|
||||
csvString += ", "
|
||||
csvString += pos.time?.formattedDate(format: "yyyy-MM-dd HH:mm:ss") ?? NSLocalizedString("unknown.age", comment: "")
|
||||
csvString += pos.time?.formattedDate(format: dateFormatString) ?? NSLocalizedString("unknown.age", comment: "")
|
||||
}
|
||||
return csvString
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
@Published var invalidVersion = false
|
||||
@Published var preferredPeripheral = false
|
||||
@Published var isSwitchedOn: Bool = false
|
||||
@Published var automaticallyReconnect: Bool = true
|
||||
|
||||
public var minimumVersion = "1.3.48"
|
||||
public var connectedVersion: String
|
||||
|
|
@ -104,13 +105,16 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
}
|
||||
self.isConnected = false
|
||||
self.isConnecting = false
|
||||
self.lastConnectionError = "🚨 Connection failed after \(timeoutTimerCount) attempts to connect to \(name). You may need to forget your device under Settings > Bluetooth."
|
||||
self.lastConnectionError = "🚨 " + String.localizedStringWithFormat(NSLocalizedString("ble.connection.timeout %d %@",
|
||||
comment: "Connection failed after %d attempts to connect to %@. You may need to forget your device under Settings > Bluetooth."),
|
||||
timeoutTimerCount, name)
|
||||
|
||||
MeshLogger.log(lastConnectionError)
|
||||
self.timeoutTimerCount = 0
|
||||
self.timeoutTimerRuns += 1
|
||||
self.startScanning()
|
||||
} else {
|
||||
MeshLogger.log("🚨 BLE Connecting 2 Second Timeout Timer Fired \(timeoutTimerCount) Time(s): \(name)")
|
||||
print("🚨 BLE Connecting 2 Second Timeout Timer Fired \(timeoutTimerCount) Time(s): \(name)")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -120,9 +124,10 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
DispatchQueue.main.async {
|
||||
self.isConnecting = true
|
||||
self.lastConnectionError = ""
|
||||
self.automaticallyReconnect = true
|
||||
}
|
||||
if connectedPeripheral != nil {
|
||||
MeshLogger.log("ℹ️ BLE Disconnecting from: \(connectedPeripheral.name) to connect to \(peripheral.name ?? "Unknown")")
|
||||
print("ℹ️ BLE Disconnecting from: \(connectedPeripheral.name) to connect to \(peripheral.name ?? "Unknown")")
|
||||
disconnectPeripheral()
|
||||
}
|
||||
centralManager?.connect(peripheral)
|
||||
|
|
@ -135,13 +140,14 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
let context = ["name": "\(peripheral.name ?? "Unknown")"]
|
||||
timeoutTimer = Timer.scheduledTimer(timeInterval: 1.5, target: self, selector: #selector(timeoutTimerFired), userInfo: context, repeats: true)
|
||||
RunLoop.current.add(timeoutTimer!, forMode: .common)
|
||||
MeshLogger.log("ℹ️ BLE Connecting: \(peripheral.name ?? "Unknown")")
|
||||
print("ℹ️ BLE Connecting: \(peripheral.name ?? "Unknown")")
|
||||
}
|
||||
|
||||
// Disconnect Connected Peripheral
|
||||
func disconnectPeripheral() {
|
||||
func disconnectPeripheral(reconnect: Bool = true) {
|
||||
|
||||
guard let connectedPeripheral = connectedPeripheral else { return }
|
||||
automaticallyReconnect = reconnect
|
||||
centralManager?.cancelPeripheralConnection(connectedPeripheral.peripheral)
|
||||
FROMRADIO_characteristic = nil
|
||||
isConnected = false
|
||||
|
|
@ -186,13 +192,13 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
}
|
||||
// Discover Services
|
||||
peripheral.discoverServices([meshtasticServiceCBUUID])
|
||||
MeshLogger.log("✅ BLE Connected: \(peripheral.name ?? "Unknown")")
|
||||
print("✅ BLE Connected: \(peripheral.name ?? "Unknown")")
|
||||
}
|
||||
|
||||
// Called when a Peripheral fails to connect
|
||||
func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
|
||||
disconnectPeripheral()
|
||||
MeshLogger.log("🚫 BLE Failed to Connect: \(peripheral.name ?? "Unknown")")
|
||||
print("🚫 BLE Failed to Connect: \(peripheral.name ?? "Unknown")")
|
||||
}
|
||||
|
||||
// Disconnect Peripheral Event
|
||||
|
|
@ -206,26 +212,28 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
let errorCode = (e as NSError).code
|
||||
if errorCode == 6 { // CBError.Code.connectionTimeout The connection has timed out unexpectedly.
|
||||
// Happens when device is manually reset / powered off
|
||||
lastConnectionError = "🚨 \(e.localizedDescription) The app will automatically reconnect to the preferred radio if it come back in range."
|
||||
MeshLogger.log("🚨 BLE Disconnected: \(peripheral.name ?? "Unknown") Error Code: \(errorCode) Error: \(e.localizedDescription)")
|
||||
lastConnectionError = "🚨" + String.localizedStringWithFormat(NSLocalizedString("ble.errorcode.6 %@",
|
||||
comment: "The app will automatically reconnect to the preferred radio if it come back in range."),
|
||||
e.localizedDescription)
|
||||
print("🚨 BLE Disconnected: \(peripheral.name ?? "Unknown") Error Code: \(errorCode) Error: \(e.localizedDescription)")
|
||||
} else if errorCode == 7 { // CBError.Code.peripheralDisconnected The specified device has disconnected from us.
|
||||
// Seems to be what is received when a tbeam sleeps, immediately recconnecting does not work.
|
||||
lastConnectionError = e.localizedDescription
|
||||
MeshLogger.log("🚨 BLE Disconnected: \(peripheral.name ?? "Unknown") Error Code: \(errorCode) Error: \(e.localizedDescription)")
|
||||
|
||||
lastConnectionError = "🚨 \(e.localizedDescription)"
|
||||
print("🚨 BLE Disconnected: \(peripheral.name ?? "Unknown") Error Code: \(errorCode) Error: \(e.localizedDescription)")
|
||||
} else if errorCode == 14 { // Peer removed pairing information
|
||||
// Forgetting and reconnecting seems to be necessary so we need to show the user an error telling them to do that
|
||||
lastConnectionError = "🚨 \(e.localizedDescription) This error usually cannot be fixed without forgetting the device unders Settings > Bluetooth and re-connecting to the radio."
|
||||
MeshLogger.log("🚨 BLE Disconnected: \(peripheral.name ?? "Unknown") Error Code: \(errorCode) Error: \(lastConnectionError)")
|
||||
lastConnectionError = "🚨 " + String.localizedStringWithFormat(NSLocalizedString("ble.errorcode.14 %@",
|
||||
comment: "This error usually cannot be fixed without forgetting the device unders Settings > Bluetooth and re-connecting to the radio."),
|
||||
e.localizedDescription)
|
||||
print("🚨 BLE Disconnected: \(peripheral.name ?? "Unknown") Error Code: \(errorCode) Error: \(lastConnectionError)")
|
||||
} else {
|
||||
lastConnectionError = e.localizedDescription
|
||||
MeshLogger.log("🚨 BLE Disconnected: \(peripheral.name ?? "Unknown") Error Code: \(errorCode) Error: \(e.localizedDescription)")
|
||||
lastConnectionError = "🚨 \(e.localizedDescription)"
|
||||
print("🚨 BLE Disconnected: \(peripheral.name ?? "Unknown") Error Code: \(errorCode) Error: \(e.localizedDescription)")
|
||||
}
|
||||
|
||||
} else {
|
||||
// Disconnected without error which indicates user intent to disconnect
|
||||
// Happens when swiping to disconnect
|
||||
MeshLogger.log("ℹ️ BLE Disconnected: \(peripheral.name ?? "Unknown"): User Initiated Disconnect")
|
||||
print("ℹ️ BLE Disconnected: \(peripheral.name ?? "Unknown"): User Initiated Disconnect")
|
||||
}
|
||||
// Start a scan so the disconnected peripheral is moved to the peripherals[] if it is awake
|
||||
self.startScanning()
|
||||
|
|
@ -240,7 +248,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
for service in services {
|
||||
if service.uuid == meshtasticServiceCBUUID {
|
||||
peripheral.discoverCharacteristics([TORADIO_UUID, FROMRADIO_UUID, FROMNUM_UUID], for: service)
|
||||
MeshLogger.log("✅ BLE Service for Meshtastic discovered by \(peripheral.name ?? "Unknown")")
|
||||
print("✅ BLE Service for Meshtastic discovered by \(peripheral.name ?? "Unknown")")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -249,7 +257,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
|
||||
|
||||
if let e = error {
|
||||
MeshLogger.log("🚫 BLE Discover Characteristics error for \(peripheral.name ?? "Unknown") \(e) disconnecting device")
|
||||
print("🚫 BLE Discover Characteristics error for \(peripheral.name ?? "Unknown") \(e) disconnecting device")
|
||||
// Try and stop crashes when this error occurs
|
||||
disconnectPeripheral()
|
||||
return
|
||||
|
|
@ -261,16 +269,16 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
switch characteristic.uuid {
|
||||
|
||||
case TORADIO_UUID:
|
||||
MeshLogger.log("✅ BLE did discover TORADIO characteristic for Meshtastic by \(peripheral.name ?? "Unknown")")
|
||||
print("✅ BLE did discover TORADIO characteristic for Meshtastic by \(peripheral.name ?? "Unknown")")
|
||||
TORADIO_characteristic = characteristic
|
||||
|
||||
case FROMRADIO_UUID:
|
||||
MeshLogger.log("✅ BLE did discover FROMRADIO characteristic for Meshtastic by \(peripheral.name ?? "Unknown")")
|
||||
print("✅ BLE did discover FROMRADIO characteristic for Meshtastic by \(peripheral.name ?? "Unknown")")
|
||||
FROMRADIO_characteristic = characteristic
|
||||
peripheral.readValue(for: FROMRADIO_characteristic)
|
||||
|
||||
case FROMNUM_UUID:
|
||||
MeshLogger.log("✅ BLE did discover FROMNUM (Notify) characteristic for Meshtastic by \(peripheral.name ?? "Unknown")")
|
||||
print("✅ BLE did discover FROMNUM (Notify) characteristic for Meshtastic by \(peripheral.name ?? "Unknown")")
|
||||
FROMNUM_characteristic = characteristic
|
||||
peripheral.setNotifyValue(true, for: characteristic)
|
||||
|
||||
|
|
@ -284,31 +292,28 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
}
|
||||
|
||||
func requestDeviceMetadata() {
|
||||
|
||||
guard (connectedPeripheral!.peripheral.state == CBPeripheralState.connected) else { return }
|
||||
|
||||
MeshLogger.log("ℹ️ Requesting Device Metadata for \(connectedPeripheral!.peripheral.name ?? "Unknown")")
|
||||
|
||||
let nodeName = connectedPeripheral!.peripheral.name ?? NSLocalizedString("unknown", comment: NSLocalizedString("unknown", comment: "Unknown"))
|
||||
let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.devicemetadata %@",
|
||||
comment: "Requesting Device Metadata for %@"), nodeName)
|
||||
MeshLogger.log("🛎️ \(logString)")
|
||||
var adminPacket = AdminMessage()
|
||||
adminPacket.getDeviceMetadataRequest = true
|
||||
|
||||
var meshPacket: MeshPacket = MeshPacket()
|
||||
meshPacket.id = UInt32.random(in: UInt32(UInt8.max)..<UInt32.max)
|
||||
meshPacket.priority = MeshPacket.Priority.reliable
|
||||
meshPacket.wantAck = true
|
||||
|
||||
var dataMessage = DataMessage()
|
||||
dataMessage.payload = try! adminPacket.serializedData()
|
||||
dataMessage.portnum = PortNum.adminApp
|
||||
dataMessage.wantResponse = true
|
||||
|
||||
meshPacket.decoded = dataMessage
|
||||
|
||||
var toRadio: ToRadio = ToRadio()
|
||||
toRadio.packet = meshPacket
|
||||
|
||||
let binaryData: Data = try! toRadio.serializedData()
|
||||
connectedPeripheral!.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse)
|
||||
|
||||
// Either Read the config complete value or from num notify value
|
||||
connectedPeripheral!.peripheral.readValue(for: FROMRADIO_characteristic)
|
||||
}
|
||||
|
|
@ -337,7 +342,10 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
if connectedPeripheral!.peripheral.state == CBPeripheralState.connected {
|
||||
connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse)
|
||||
success = true
|
||||
MeshLogger.log("🪧 Sent a Trace Route Packet to node: \(destNum).")
|
||||
|
||||
let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.traceroute.sent %@",
|
||||
comment: "Sent a Trace Route Request to node: %@"), String(destNum))
|
||||
MeshLogger.log("🪧 \(logString)")
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
|
@ -346,11 +354,14 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
guard (connectedPeripheral!.peripheral.state == CBPeripheralState.connected) else { return }
|
||||
|
||||
if FROMRADIO_characteristic == nil {
|
||||
MeshLogger.log("🚨 Unsupported Firmware Version Detected, unable to connect to device.")
|
||||
MeshLogger.log("🚨 \(NSLocalizedString("firmware.version.unsupported", comment: "Unsupported Firmware Version Detected, unable to connect to device."))")
|
||||
invalidVersion = true
|
||||
return
|
||||
} else {
|
||||
MeshLogger.log("ℹ️ Issuing wantConfig to \(connectedPeripheral!.peripheral.name ?? "Unknown")")
|
||||
|
||||
let nodeName = connectedPeripheral!.peripheral.name ?? NSLocalizedString("unknown", comment: NSLocalizedString("unknown", comment: "Unknown"))
|
||||
let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.wantconfig %@", comment: "Issuing Want Config to %@"), nodeName)
|
||||
MeshLogger.log("🛎️ \(logString)")
|
||||
//BLE Characteristics discovered, issue wantConfig
|
||||
var toRadio: ToRadio = ToRadio()
|
||||
configNonce += 1
|
||||
|
|
@ -363,9 +374,8 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
}
|
||||
|
||||
func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {
|
||||
|
||||
if let errorText = error?.localizedDescription {
|
||||
MeshLogger.log("🚫 didUpdateNotificationStateFor error: \(errorText)")
|
||||
print("🚫 didUpdateNotificationStateFor error: \(errorText)")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -378,17 +388,15 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
|
||||
let errorCode = (e as NSError).code
|
||||
|
||||
if errorCode == 5 { // CBATTErrorDomain Code=5 "Authentication is insufficient."
|
||||
// BLE Pin connection error
|
||||
lastConnectionError = "🚫 BLE \(e.localizedDescription) Please try connecting again and check the PIN carefully."
|
||||
MeshLogger.log("🚫 BLE \(e.localizedDescription) Please try connecting again and check the PIN carefully.")
|
||||
self.centralManager?.cancelPeripheralConnection(peripheral)
|
||||
}
|
||||
if errorCode == 15 { // CBATTErrorDomain Code=15 "Encryption is insufficient."
|
||||
// BLE Pin connection error
|
||||
lastConnectionError = "🚫 BLE \(e.localizedDescription) Please try connecting again and check the PIN carefully."
|
||||
MeshLogger.log("🚫 BLE \(e.localizedDescription) Please try connecting again. You may need to forget the device under Settings > General > Bluetooth.")
|
||||
self.centralManager?.cancelPeripheralConnection(peripheral)
|
||||
if errorCode == 5 || errorCode == 15 {
|
||||
// BLE PIN connection errors
|
||||
// 5 CBATTErrorDomain Code=5 "Authentication is insufficient."
|
||||
// 15 CBATTErrorDomain Code=15 "Encryption is insufficient."
|
||||
lastConnectionError = "🚨" + String.localizedStringWithFormat(NSLocalizedString("ble.errorcode.pin %@",
|
||||
comment: "Please try connecting again and check the PIN carefully."),
|
||||
e.localizedDescription)
|
||||
print("🚨 \(e.localizedDescription) Please try connecting again and check the PIN carefully.")
|
||||
self.disconnectPeripheral(reconnect: false)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -399,11 +407,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
if characteristic.value == nil || characteristic.value!.isEmpty {
|
||||
return
|
||||
}
|
||||
|
||||
var decodedInfo = FromRadio()
|
||||
|
||||
do {
|
||||
|
||||
decodedInfo = try FromRadio(serializedData: characteristic.value!)
|
||||
|
||||
} catch {
|
||||
|
|
@ -414,10 +420,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
|
||||
// Handle Any local only packets we get over BLE
|
||||
case .unknownApp:
|
||||
|
||||
var nowKnown = false
|
||||
|
||||
// MyInfo
|
||||
// MyInfo from initial connection
|
||||
if decodedInfo.myInfo.isInitialized && decodedInfo.myInfo.myNodeNum > 0 {
|
||||
|
||||
let lastDotIndex = decodedInfo.myInfo.firmwareVersion.lastIndex(of: ".")
|
||||
|
|
@ -434,7 +439,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
let supportedVersion = connectedVersion == "0.0.0" || self.minimumVersion.compare(connectedVersion, options: .numeric) == .orderedAscending || minimumVersion.compare(connectedVersion, options: .numeric) == .orderedSame
|
||||
if !supportedVersion {
|
||||
invalidVersion = true
|
||||
lastConnectionError = "🚨 Update your firmware"
|
||||
lastConnectionError = "🚨" + NSLocalizedString("update.firmware", comment: "Update Your Firmware")
|
||||
return
|
||||
|
||||
} else {
|
||||
|
|
@ -445,9 +450,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
|
||||
if myInfo != nil {
|
||||
connectedPeripheral.num = myInfo!.myNodeNum
|
||||
connectedPeripheral.firmwareVersion = myInfo?.firmwareVersion ?? "Unknown"
|
||||
connectedPeripheral.name = myInfo?.bleName ?? "Unknown"
|
||||
connectedPeripheral.longName = myInfo?.bleName ?? "Unknown"
|
||||
connectedPeripheral.firmwareVersion = myInfo?.firmwareVersion ?? NSLocalizedString("unknown", comment: "Unknown")
|
||||
connectedPeripheral.name = myInfo?.bleName ?? NSLocalizedString("unknown", comment: "Unknown")
|
||||
connectedPeripheral.longName = myInfo?.bleName ?? NSLocalizedString("unknown", comment: "Unknown")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -461,7 +466,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
if self.connectedPeripheral != nil && self.connectedPeripheral.num == nodeInfo!.num {
|
||||
if nodeInfo!.user != nil {
|
||||
connectedPeripheral.shortName = nodeInfo?.user?.shortName ?? "????"
|
||||
connectedPeripheral.longName = nodeInfo?.user?.longName ?? "Unknown"
|
||||
connectedPeripheral.longName = nodeInfo?.user?.longName ?? NSLocalizedString("unknown", comment: "Unknown")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -492,16 +497,16 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
}
|
||||
}
|
||||
// Log any other unknownApp calls
|
||||
if !nowKnown { MeshLogger.log("ℹ️ MESH PACKET received for Unknown App UNHANDLED \(try! decodedInfo.packet.jsonString())") }
|
||||
if !nowKnown { MeshLogger.log("🕸️ MESH PACKET received for Unknown App UNHANDLED \(try! decodedInfo.packet.jsonString())") }
|
||||
|
||||
case .textMessageApp:
|
||||
textMessageAppPacket(packet: decodedInfo.packet, connectedNode: (self.connectedPeripheral != nil ? connectedPeripheral.num : 0), context: context!)
|
||||
case .remoteHardwareApp:
|
||||
MeshLogger.log("ℹ️ MESH PACKET received for Remote Hardware App UNHANDLED \(try! decodedInfo.packet.jsonString())")
|
||||
MeshLogger.log("🕸️ MESH PACKET received for Remote Hardware App UNHANDLED \(try! decodedInfo.packet.jsonString())")
|
||||
case .positionApp:
|
||||
positionPacket(packet: decodedInfo.packet, context: context!)
|
||||
case .waypointApp:
|
||||
MeshLogger.log("ℹ️ MESH PACKET received for Waypoint App UNHANDLED \(try! decodedInfo.packet.jsonString())")
|
||||
MeshLogger.log("🕸️ MESH PACKET received for Waypoint App UNHANDLED \(try! decodedInfo.packet.jsonString())")
|
||||
case .nodeinfoApp:
|
||||
if !invalidVersion { nodeInfoAppPacket(packet: decodedInfo.packet, context: context!) }
|
||||
case .routingApp:
|
||||
|
|
@ -509,46 +514,50 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
case .adminApp:
|
||||
adminAppPacket(packet: decodedInfo.packet, context: context!)
|
||||
case .replyApp:
|
||||
MeshLogger.log("ℹ️ MESH PACKET received for Reply App UNHANDLED \(try! decodedInfo.packet.jsonString())")
|
||||
MeshLogger.log("🕸️ MESH PACKET received for Reply App UNHANDLED \(try! decodedInfo.packet.jsonString())")
|
||||
case .ipTunnelApp:
|
||||
MeshLogger.log("ℹ️ MESH PACKET received for IP Tunnel App UNHANDLED \(try! decodedInfo.packet.jsonString())")
|
||||
MeshLogger.log("🕸️ MESH PACKET received for IP Tunnel App UNHANDLED \(try! decodedInfo.packet.jsonString())")
|
||||
case .serialApp:
|
||||
MeshLogger.log("ℹ️ MESH PACKET received for Serial App UNHANDLED \(try! decodedInfo.packet.jsonString())")
|
||||
MeshLogger.log("🕸️ MESH PACKET received for Serial App UNHANDLED \(try! decodedInfo.packet.jsonString())")
|
||||
case .storeForwardApp:
|
||||
MeshLogger.log("ℹ️ MESH PACKET received for Store Forward App UNHANDLED \(try! decodedInfo.packet.jsonString())")
|
||||
MeshLogger.log("🕸️ MESH PACKET received for Store Forward App UNHANDLED \(try! decodedInfo.packet.jsonString())")
|
||||
case .rangeTestApp:
|
||||
MeshLogger.log("ℹ️ MESH PACKET received for Range Test App UNHANDLED \(try! decodedInfo.packet.jsonString())")
|
||||
MeshLogger.log("🕸️ MESH PACKET received for Range Test App UNHANDLED \(try! decodedInfo.packet.jsonString())")
|
||||
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())")
|
||||
MeshLogger.log("🕸️ MESH PACKET received for Text Message Compressed App UNHANDLED \(try! decodedInfo.packet.jsonString())")
|
||||
case .zpsApp:
|
||||
MeshLogger.log("ℹ️ MESH PACKET received for ZPS App UNHANDLED \(try! decodedInfo.packet.jsonString())")
|
||||
MeshLogger.log("🕸️ MESH PACKET received for ZPS App UNHANDLED \(try! decodedInfo.packet.jsonString())")
|
||||
case .privateApp:
|
||||
MeshLogger.log("ℹ️ MESH PACKET received for Private App UNHANDLED \(try! decodedInfo.packet.jsonString())")
|
||||
MeshLogger.log("🕸️ MESH PACKET received for Private App UNHANDLED \(try! decodedInfo.packet.jsonString())")
|
||||
case .atakForwarder:
|
||||
MeshLogger.log("ℹ️ MESH PACKET received for ATAK Forwarder App UNHANDLED \(try! decodedInfo.packet.jsonString())")
|
||||
MeshLogger.log("🕸️ MESH PACKET received for ATAK Forwarder App UNHANDLED \(try! decodedInfo.packet.jsonString())")
|
||||
case .simulatorApp:
|
||||
MeshLogger.log("ℹ️ MESH PACKET received for Simulator App UNHANDLED \(try! decodedInfo.packet.jsonString())")
|
||||
MeshLogger.log("🕸️ MESH PACKET received for Simulator App UNHANDLED \(try! decodedInfo.packet.jsonString())")
|
||||
case .audioApp:
|
||||
MeshLogger.log("ℹ️ MESH PACKET received for Audio App UNHANDLED \(try! decodedInfo.packet.jsonString())")
|
||||
MeshLogger.log("🕸️ MESH PACKET received for Audio App UNHANDLED \(try! decodedInfo.packet.jsonString())")
|
||||
case .tracerouteApp:
|
||||
if let routingMessage = try? RouteDiscovery(serializedData: decodedInfo.packet.decoded.payload) {
|
||||
|
||||
if routingMessage.route.count == 0 {
|
||||
MeshLogger.log("🪧 Trace Route request sent to \(decodedInfo.packet.from) was recieived directly.")
|
||||
let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.traceroute.received.direct %@",
|
||||
comment: "Trace Route request sent to node: %@ was recieived directly."), String(decodedInfo.packet.from))
|
||||
MeshLogger.log("🪧 \(logString)")
|
||||
} else {
|
||||
|
||||
var routeString = "🪧 Trace Route request returned: \(decodedInfo.packet.to) --> "
|
||||
var routeString = "\(decodedInfo.packet.to) --> "
|
||||
for node in routingMessage.route {
|
||||
routeString += "\(node) --> "
|
||||
}
|
||||
routeString += "\(decodedInfo.packet.from)"
|
||||
MeshLogger.log(routeString)
|
||||
let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.traceroute.received.route %@",
|
||||
comment: "Trace Route request returned: %@"), routeString)
|
||||
MeshLogger.log("🪧 \(logString)")
|
||||
}
|
||||
}
|
||||
case .UNRECOGNIZED(_):
|
||||
MeshLogger.log("ℹ️ MESH PACKET received for Other App UNHANDLED \(try! decodedInfo.packet.jsonString())")
|
||||
MeshLogger.log("🕸️ MESH PACKET received for Other App UNHANDLED \(try! decodedInfo.packet.jsonString())")
|
||||
case .max:
|
||||
print("MAX PORT NUM OF 511")
|
||||
}
|
||||
|
|
@ -560,13 +569,11 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
do {
|
||||
let fetchedUser = try context?.fetch(fetchBCUserRequest) as! [UserEntity]
|
||||
if fetchedUser.count > 0 {
|
||||
|
||||
context?.delete(fetchedUser[0])
|
||||
print("🗑️ Deleted the All - Broadcast User")
|
||||
}
|
||||
|
||||
} catch {
|
||||
MeshLogger.log("💥 Error Deleting the All - Broadcast User")
|
||||
print("💥 Error Deleting the All - Broadcast User")
|
||||
}
|
||||
|
||||
if decodedInfo.configCompleteID != 0 && decodedInfo.configCompleteID == configNonce {
|
||||
|
|
@ -574,7 +581,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
lastConnectionError = ""
|
||||
timeoutTimerRuns = 0
|
||||
isSubscribed = true
|
||||
MeshLogger.log("🤜 BLE Config Complete Packet Id: \(decodedInfo.configCompleteID)")
|
||||
print("🤜 Want Config Complete. ID:\(decodedInfo.configCompleteID)")
|
||||
peripherals.removeAll(where: { $0.peripheral.state == CBPeripheralState.disconnected })
|
||||
// Config conplete returns so we don't read the characteristic again
|
||||
// MARK: Share Location Position Update Timer
|
||||
|
|
@ -620,7 +627,11 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
if preferredPeripheral != nil && preferredPeripheral?.peripheral != nil {
|
||||
connectTo(peripheral: preferredPeripheral!.peripheral)
|
||||
}
|
||||
MeshLogger.log("🚫 Message Send Failed, not properly connected to \(preferredPeripheral?.name ?? "Unknown")")
|
||||
let nodeName = connectedPeripheral!.peripheral.name ?? NSLocalizedString("unknown", comment: NSLocalizedString("unknown", comment: "Unknown"))
|
||||
let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.textmessage.send.failed %@",
|
||||
comment: "Message Send Failed, not properly connected to %@"), nodeName)
|
||||
MeshLogger.log("🚫 \(logString)")
|
||||
|
||||
success = false
|
||||
|
||||
} else if message.count < 1 {
|
||||
|
|
@ -690,24 +701,21 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
var toRadio: ToRadio!
|
||||
toRadio = ToRadio()
|
||||
toRadio.packet = meshPacket
|
||||
|
||||
let binaryData: Data = try! toRadio.serializedData()
|
||||
|
||||
MeshLogger.log("📲 New messageId \(newMessage.messageId) sent to \(newMessage.toUser?.longName! ?? "Unknown")")
|
||||
if connectedPeripheral!.peripheral.state == CBPeripheralState.connected {
|
||||
connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse)
|
||||
let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.textmessage.sent %@ %@ %@", comment: "Sent message %@ from %@ to %@"), String(newMessage.messageId), String(fromUserNum), String(toUserNum))
|
||||
MeshLogger.log("💬 \(logString)")
|
||||
do {
|
||||
|
||||
try context!.save()
|
||||
MeshLogger.log("💾 Saved a new sent message from \(connectedPeripheral.num) to \(toUserNum)")
|
||||
print("💾 Saved a new sent message from \(connectedPeripheral.num) to \(toUserNum)")
|
||||
success = true
|
||||
|
||||
} catch {
|
||||
|
||||
context!.rollback()
|
||||
|
||||
let nsError = error as NSError
|
||||
MeshLogger.log("💥 Unresolved Core Data error in Send Message Function your database is corrupted running a node db reset should clean up the data. Error: \(nsError)")
|
||||
print("💥 Unresolved Core Data error in Send Message Function your database is corrupted running a node db reset should clean up the data. Error: \(nsError)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -744,8 +752,8 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
toRadio = ToRadio()
|
||||
toRadio.packet = meshPacket
|
||||
let binaryData: Data = try! toRadio.serializedData()
|
||||
|
||||
MeshLogger.log("📍 Sent a Waypoint Packet from the Apple device GPS to node: \(fromNodeNum)")
|
||||
let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.waypoint.sent %@", comment: "Sent a Waypoint Packet from: %@"), String(fromNodeNum))
|
||||
MeshLogger.log("📍 \(logString)")
|
||||
|
||||
if connectedPeripheral!.peripheral.state == CBPeripheralState.connected {
|
||||
connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse)
|
||||
|
|
@ -794,7 +802,8 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
if connectedPeripheral!.peripheral.state == CBPeripheralState.connected {
|
||||
connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse)
|
||||
success = true
|
||||
MeshLogger.log("📍 Sent a Position Packet from the Apple device GPS to node: \(fromNodeNum)")
|
||||
let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.sharelocation %@", comment: "Sent a Position Packet from the Apple device GPS to node: %@"), String(fromNodeNum))
|
||||
MeshLogger.log("📍 \(logString)")
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
|
@ -817,54 +826,37 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
public func sendShutdown(destNum: Int64) -> Bool {
|
||||
public func sendShutdown(fromUser: UserEntity, toUser: UserEntity) -> Bool {
|
||||
|
||||
var adminPacket = AdminMessage()
|
||||
adminPacket.shutdownSeconds = 10
|
||||
|
||||
var meshPacket: MeshPacket = MeshPacket()
|
||||
meshPacket.to = UInt32(connectedPeripheral.num)
|
||||
meshPacket.from = 0 //UInt32(connectedPeripheral.num)
|
||||
meshPacket.to = UInt32(toUser.num)
|
||||
meshPacket.from = UInt32(fromUser.num)
|
||||
meshPacket.id = UInt32.random(in: UInt32(UInt8.max)..<UInt32.max)
|
||||
meshPacket.priority = MeshPacket.Priority.reliable
|
||||
meshPacket.wantAck = true
|
||||
|
||||
var dataMessage = DataMessage()
|
||||
dataMessage.payload = try! adminPacket.serializedData()
|
||||
dataMessage.portnum = PortNum.adminApp
|
||||
meshPacket.decoded = dataMessage
|
||||
|
||||
var toRadio: ToRadio!
|
||||
toRadio = ToRadio()
|
||||
toRadio.packet = meshPacket
|
||||
|
||||
let binaryData: Data = try! toRadio.serializedData()
|
||||
|
||||
if connectedPeripheral!.peripheral.state == CBPeripheralState.connected {
|
||||
|
||||
do {
|
||||
try context!.save()
|
||||
MeshLogger.log("💾 Saved a Shutdown Admin Message for node: \(String(destNum))")
|
||||
connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse)
|
||||
return true
|
||||
|
||||
} catch {
|
||||
context!.rollback()
|
||||
let nsError = error as NSError
|
||||
MeshLogger.log("💥 Error Inserting New Core Data MessageEntity: \(nsError)")
|
||||
}
|
||||
let messageDescription = "Sent Shutdown Admin Message to: \(toUser.longName ?? NSLocalizedString("unknown", comment: ""))"
|
||||
if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
public func sendReboot(destNum: Int64) -> Bool {
|
||||
public func sendReboot(fromUser: UserEntity, toUser: UserEntity) -> Bool {
|
||||
|
||||
var adminPacket = AdminMessage()
|
||||
adminPacket.rebootSeconds = 10
|
||||
|
||||
var meshPacket: MeshPacket = MeshPacket()
|
||||
meshPacket.to = UInt32(connectedPeripheral.num)
|
||||
meshPacket.from = 0 //UInt32(connectedPeripheral.num)
|
||||
meshPacket.from = UInt32(fromUser.num)
|
||||
meshPacket.id = UInt32.random(in: UInt32(UInt8.max)..<UInt32.max)
|
||||
meshPacket.priority = MeshPacket.Priority.reliable
|
||||
meshPacket.wantAck = true
|
||||
|
|
@ -874,69 +866,46 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
dataMessage.payload = try! adminPacket.serializedData()
|
||||
dataMessage.portnum = PortNum.adminApp
|
||||
meshPacket.decoded = dataMessage
|
||||
|
||||
var toRadio: ToRadio!
|
||||
toRadio = ToRadio()
|
||||
toRadio.packet = meshPacket
|
||||
|
||||
let binaryData: Data = try! toRadio.serializedData()
|
||||
|
||||
if connectedPeripheral!.peripheral.state == CBPeripheralState.connected {
|
||||
|
||||
do {
|
||||
try context!.save()
|
||||
connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse)
|
||||
MeshLogger.log("💾 Saved a Reboot Admin Message for node: \(String(destNum))")
|
||||
return true
|
||||
} catch {
|
||||
context!.rollback()
|
||||
let nsError = error as NSError
|
||||
MeshLogger.log("💥 Error Inserting New Core Data MessageEntity: \(nsError)")
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
public func sendFactoryReset(destNum: Int64) -> Bool {
|
||||
|
||||
var adminPacket = AdminMessage()
|
||||
adminPacket.factoryReset = 1
|
||||
|
||||
var meshPacket: MeshPacket = MeshPacket()
|
||||
meshPacket.to = UInt32(destNum)
|
||||
meshPacket.from = 0 //UInt32(connectedPeripheral.num)
|
||||
meshPacket.id = UInt32.random(in: UInt32(UInt8.max)..<UInt32.max)
|
||||
meshPacket.priority = MeshPacket.Priority.reliable
|
||||
meshPacket.wantAck = true
|
||||
|
||||
var dataMessage = DataMessage()
|
||||
dataMessage.payload = try! adminPacket.serializedData()
|
||||
dataMessage.portnum = PortNum.adminApp
|
||||
|
||||
meshPacket.decoded = dataMessage
|
||||
|
||||
var toRadio: ToRadio!
|
||||
toRadio = ToRadio()
|
||||
toRadio.packet = meshPacket
|
||||
|
||||
let binaryData: Data = try! toRadio.serializedData()
|
||||
|
||||
if connectedPeripheral!.peripheral.state == CBPeripheralState.connected {
|
||||
connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse)
|
||||
MeshLogger.log("💾 Sent a Factory Reset for node: \(String(destNum))")
|
||||
let messageDescription = "Sent Reboot Admin Message to: \(toUser.longName ?? NSLocalizedString("unknown", comment: ""))"
|
||||
if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
public func sendNodeDBReset(destNum: Int64) -> Bool {
|
||||
public func sendFactoryReset(fromUser: UserEntity, toUser: UserEntity) -> Bool {
|
||||
|
||||
var adminPacket = AdminMessage()
|
||||
adminPacket.factoryReset = 1
|
||||
|
||||
var meshPacket: MeshPacket = MeshPacket()
|
||||
meshPacket.to = UInt32(toUser.num)
|
||||
meshPacket.from = UInt32(fromUser.num)
|
||||
meshPacket.id = UInt32.random(in: UInt32(UInt8.max)..<UInt32.max)
|
||||
meshPacket.priority = MeshPacket.Priority.reliable
|
||||
meshPacket.wantAck = true
|
||||
|
||||
var dataMessage = DataMessage()
|
||||
dataMessage.payload = try! adminPacket.serializedData()
|
||||
dataMessage.portnum = PortNum.adminApp
|
||||
meshPacket.decoded = dataMessage
|
||||
|
||||
let messageDescription = "Sent Factory Reset Admin Message to: \(toUser.longName ?? NSLocalizedString("unknown", comment: ""))"
|
||||
if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
public func sendNodeDBReset(fromUser: UserEntity, toUser: UserEntity) -> Bool {
|
||||
|
||||
var adminPacket = AdminMessage()
|
||||
adminPacket.nodedbReset = 1
|
||||
|
||||
var meshPacket: MeshPacket = MeshPacket()
|
||||
meshPacket.to = UInt32(destNum)
|
||||
meshPacket.from = 0 //UInt32(connectedPeripheral.num)
|
||||
meshPacket.to = UInt32(toUser.num)
|
||||
meshPacket.from = UInt32(fromUser.num)
|
||||
meshPacket.id = UInt32.random(in: UInt32(UInt8.max)..<UInt32.max)
|
||||
meshPacket.priority = MeshPacket.Priority.reliable
|
||||
meshPacket.wantAck = true
|
||||
|
|
@ -946,16 +915,8 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
dataMessage.portnum = PortNum.adminApp
|
||||
|
||||
meshPacket.decoded = dataMessage
|
||||
|
||||
var toRadio: ToRadio!
|
||||
toRadio = ToRadio()
|
||||
toRadio.packet = meshPacket
|
||||
|
||||
let binaryData: Data = try! toRadio.serializedData()
|
||||
|
||||
if connectedPeripheral!.peripheral.state == CBPeripheralState.connected {
|
||||
connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse)
|
||||
MeshLogger.log("💾 Sent a NodeDB Reset for node: \(String(destNum))")
|
||||
let messageDescription = "Sent NodeDB Reset Admin Message to: \(toUser.longName ?? NSLocalizedString("unknown", comment: ""))"
|
||||
if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
|
@ -982,6 +943,32 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
return success
|
||||
}
|
||||
|
||||
public func getChannel(channel: Channel, fromUser: UserEntity, toUser: UserEntity) -> Int64 {
|
||||
|
||||
var adminPacket = AdminMessage()
|
||||
adminPacket.getChannelRequest = UInt32(channel.index + 1)
|
||||
var meshPacket: MeshPacket = MeshPacket()
|
||||
meshPacket.to = UInt32(toUser.num)
|
||||
meshPacket.from = 0 //UInt32(fromUser.num)
|
||||
meshPacket.id = UInt32.random(in: UInt32(UInt8.max)..<UInt32.max)
|
||||
meshPacket.priority = MeshPacket.Priority.reliable
|
||||
|
||||
var dataMessage = DataMessage()
|
||||
dataMessage.payload = try! adminPacket.serializedData()
|
||||
dataMessage.portnum = PortNum.adminApp
|
||||
dataMessage.wantResponse = true
|
||||
|
||||
meshPacket.decoded = dataMessage
|
||||
|
||||
let messageDescription = "Requested Channel \(channel.index) for \(toUser.longName ?? NSLocalizedString("unknown", comment: "Unknown"))"
|
||||
|
||||
if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) {
|
||||
|
||||
return Int64(meshPacket.id)
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
public func saveChannel(channel: Channel, fromUser: UserEntity, toUser: UserEntity) -> Int64 {
|
||||
|
||||
var adminPacket = AdminMessage()
|
||||
|
|
@ -991,18 +978,16 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
meshPacket.from = 0 //UInt32(fromUser.num)
|
||||
meshPacket.id = UInt32.random(in: UInt32(UInt8.max)..<UInt32.max)
|
||||
meshPacket.priority = MeshPacket.Priority.reliable
|
||||
meshPacket.wantAck = true
|
||||
|
||||
var dataMessage = DataMessage()
|
||||
dataMessage.payload = try! adminPacket.serializedData()
|
||||
dataMessage.portnum = PortNum.adminApp
|
||||
|
||||
dataMessage.wantResponse = true
|
||||
meshPacket.decoded = dataMessage
|
||||
|
||||
let messageDescription = "Saved Channel \(channel.index) for \(toUser.longName ?? "Unknown")"
|
||||
let messageDescription = "Saved Channel \(channel.index) for \(toUser.longName ?? NSLocalizedString("unknown", comment: "Unknown"))"
|
||||
|
||||
if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) {
|
||||
|
||||
return Int64(meshPacket.id)
|
||||
}
|
||||
|
||||
|
|
@ -1053,7 +1038,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
adminPacket.setChannel = chan
|
||||
var meshPacket: MeshPacket = MeshPacket()
|
||||
meshPacket.to = UInt32(connectedPeripheral.num)
|
||||
meshPacket.from = 0 //UInt32(connectedPeripheral.num)
|
||||
meshPacket.from = UInt32(connectedPeripheral.num)
|
||||
meshPacket.id = UInt32.random(in: UInt32(UInt8.max)..<UInt32.max)
|
||||
meshPacket.priority = MeshPacket.Priority.reliable
|
||||
meshPacket.wantAck = true
|
||||
|
|
@ -1068,7 +1053,8 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
let binaryData: Data = try! toRadio.serializedData()
|
||||
if connectedPeripheral!.peripheral.state == CBPeripheralState.connected {
|
||||
self.connectedPeripheral.peripheral.writeValue(binaryData, for: self.TORADIO_characteristic, type: .withResponse)
|
||||
MeshLogger.log("✈️ Sent a Channel for: \(String(self.connectedPeripheral.num)) Channel Index \(chan.index)")
|
||||
let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.channel.sent %@ %d", comment: "Sent a Channel for: %@ Channel Index %d"), String(connectedPeripheral.num), chan.index)
|
||||
MeshLogger.log("🎛️ \(logString)")
|
||||
}
|
||||
}
|
||||
// Save the LoRa Config and the device will reboot
|
||||
|
|
@ -1091,7 +1077,8 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
let binaryData: Data = try! toRadio.serializedData()
|
||||
if connectedPeripheral!.peripheral.state == CBPeripheralState.connected {
|
||||
self.connectedPeripheral.peripheral.writeValue(binaryData, for: self.TORADIO_characteristic, type: .withResponse)
|
||||
MeshLogger.log("✈️ Sent a LoRaConfig for: \(String(self.connectedPeripheral.num))")
|
||||
let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.lora.config.sent %@", comment: "Sent a LoRaConfig for: %@"), String(connectedPeripheral.num))
|
||||
MeshLogger.log("📻 \(logString)")
|
||||
}
|
||||
return true
|
||||
|
||||
|
|
@ -1118,7 +1105,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
dataMessage.payload = try! adminPacket.serializedData()
|
||||
dataMessage.portnum = PortNum.adminApp
|
||||
meshPacket.decoded = dataMessage
|
||||
let messageDescription = "Saved User Config for \(toUser.longName ?? "Unknown")"
|
||||
let messageDescription = "Saved User Config for \(toUser.longName ?? NSLocalizedString("unknown", comment: "Unknown"))"
|
||||
|
||||
if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) {
|
||||
return Int64(meshPacket.id)
|
||||
|
|
@ -1141,7 +1128,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
dataMessage.payload = try! adminPacket.serializedData()
|
||||
dataMessage.portnum = PortNum.adminApp
|
||||
meshPacket.decoded = dataMessage
|
||||
let messageDescription = "Saved Bluetooth Config for \(toUser.longName ?? "Unknown")"
|
||||
let messageDescription = "Saved Bluetooth Config for \(toUser.longName ?? NSLocalizedString("unknown", comment: "Unknown"))"
|
||||
|
||||
if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) {
|
||||
return Int64(meshPacket.id)
|
||||
|
|
@ -1166,7 +1153,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
dataMessage.portnum = PortNum.adminApp
|
||||
meshPacket.decoded = dataMessage
|
||||
|
||||
let messageDescription = "Saved Device Config for \(toUser.longName ?? "Unknown")"
|
||||
let messageDescription = "Saved Device Config for \(toUser.longName ?? NSLocalizedString("unknown", comment: "Unknown"))"
|
||||
|
||||
if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) {
|
||||
|
||||
|
|
@ -1191,7 +1178,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
dataMessage.payload = try! adminPacket.serializedData()
|
||||
dataMessage.portnum = PortNum.adminApp
|
||||
meshPacket.decoded = dataMessage
|
||||
let messageDescription = "Saved Display Config for \(toUser.longName ?? "Unknown")"
|
||||
let messageDescription = "Saved Display Config for \(toUser.longName ?? NSLocalizedString("unknown", comment: "Unknown"))"
|
||||
|
||||
if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) {
|
||||
return Int64(meshPacket.id)
|
||||
|
|
@ -1215,7 +1202,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
dataMessage.payload = try! adminPacket.serializedData()
|
||||
dataMessage.portnum = PortNum.adminApp
|
||||
meshPacket.decoded = dataMessage
|
||||
let messageDescription = "Saved LoRa Config for \(toUser.longName ?? "Unknown")"
|
||||
let messageDescription = "Saved LoRa Config for \(toUser.longName ?? NSLocalizedString("unknown", comment: "Unknown"))"
|
||||
|
||||
if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) {
|
||||
return Int64(meshPacket.id)
|
||||
|
|
@ -1243,7 +1230,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
|
||||
meshPacket.decoded = dataMessage
|
||||
|
||||
let messageDescription = "Saved Position Config for \(toUser.longName ?? "Unknown")"
|
||||
let messageDescription = "Saved Position Config for \(toUser.longName ?? NSLocalizedString("unknown", comment: "Unknown"))"
|
||||
|
||||
if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) {
|
||||
|
||||
|
|
@ -1272,7 +1259,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
|
||||
meshPacket.decoded = dataMessage
|
||||
|
||||
let messageDescription = "Saved WiFi Config for \(toUser.longName ?? "Unknown")"
|
||||
let messageDescription = "Saved WiFi Config for \(toUser.longName ?? NSLocalizedString("unknown", comment: "Unknown"))"
|
||||
|
||||
if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) {
|
||||
|
||||
|
|
@ -1300,7 +1287,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
|
||||
meshPacket.decoded = dataMessage
|
||||
|
||||
let messageDescription = "Saved Canned Message Module Config for \(toUser.longName ?? "Unknown")"
|
||||
let messageDescription = "Saved Canned Message Module Config for \(toUser.longName ?? NSLocalizedString("unknown", comment: "Unknown"))"
|
||||
|
||||
if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) {
|
||||
|
||||
|
|
@ -1329,7 +1316,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
|
||||
meshPacket.decoded = dataMessage
|
||||
|
||||
let messageDescription = "💾 Saved Canned Message Module Messages for \(toUser.longName ?? "Unknown")"
|
||||
let messageDescription = "💾 Saved Canned Message Module Messages for \(toUser.longName ?? NSLocalizedString("unknown", comment: "Unknown"))"
|
||||
|
||||
if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) {
|
||||
|
||||
|
|
@ -1395,8 +1382,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
let binaryData: Data = try! toRadio.serializedData()
|
||||
|
||||
if connectedPeripheral!.peripheral.state == CBPeripheralState.connected {
|
||||
MeshLogger.log("✈️ Sent a Canned Messages Module Get Messages Request Admin Message for node: \(String(destNum))")
|
||||
connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse)
|
||||
let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.cannedmessages.messages.get %@", comment: "Requested Canned Messages Module Messages for node: %@"), String(connectedPeripheral.num))
|
||||
MeshLogger.log("🥫 \(logString)")
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
@ -1418,16 +1406,12 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
var dataMessage = DataMessage()
|
||||
dataMessage.payload = try! adminPacket.serializedData()
|
||||
dataMessage.portnum = PortNum.adminApp
|
||||
|
||||
meshPacket.decoded = dataMessage
|
||||
|
||||
let messageDescription = "Saved External Notification Module Config for \(toUser.longName ?? "Unknown")"
|
||||
|
||||
let messageDescription = "Saved External Notification Module Config for \(toUser.longName ?? NSLocalizedString("unknown", comment: "Unknown"))"
|
||||
if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) {
|
||||
|
||||
return Int64(meshPacket.id)
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
|
|
@ -1450,7 +1434,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
|
||||
meshPacket.decoded = dataMessage
|
||||
|
||||
let messageDescription = "Saved WiFi Config for \(toUser.longName ?? "Unknown")"
|
||||
let messageDescription = "Saved WiFi Config for \(toUser.longName ?? NSLocalizedString("unknown", comment: "Unknown"))"
|
||||
|
||||
if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) {
|
||||
|
||||
|
|
@ -1478,7 +1462,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
|
||||
meshPacket.decoded = dataMessage
|
||||
|
||||
let messageDescription = "Saved Range Test Module Config for \(toUser.longName ?? "Unknown")"
|
||||
let messageDescription = "Saved Range Test Module Config for \(toUser.longName ?? NSLocalizedString("unknown", comment: "Unknown"))"
|
||||
|
||||
if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) {
|
||||
|
||||
|
|
@ -1504,16 +1488,12 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
var dataMessage = DataMessage()
|
||||
dataMessage.payload = try! adminPacket.serializedData()
|
||||
dataMessage.portnum = PortNum.adminApp
|
||||
|
||||
meshPacket.decoded = dataMessage
|
||||
|
||||
let messageDescription = "Saved Serial Module Config for \(toUser.longName ?? "Unknown")"
|
||||
|
||||
let messageDescription = "Saved Serial Module Config for \(toUser.longName ?? NSLocalizedString("unknown", comment: "Unknown"))"
|
||||
if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) {
|
||||
|
||||
return Int64(meshPacket.id)
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
|
|
@ -1532,16 +1512,12 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
var dataMessage = DataMessage()
|
||||
dataMessage.payload = try! adminPacket.serializedData()
|
||||
dataMessage.portnum = PortNum.adminApp
|
||||
|
||||
meshPacket.decoded = dataMessage
|
||||
|
||||
let messageDescription = "Saved Telemetry Module Config for \(toUser.longName ?? "Unknown")"
|
||||
|
||||
let messageDescription = "Saved Telemetry Module Config for \(toUser.longName ?? NSLocalizedString("unknown", comment: "Unknown"))"
|
||||
if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) {
|
||||
|
||||
return Int64(meshPacket.id)
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
|
|
@ -1551,11 +1527,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
var toRadio: ToRadio!
|
||||
toRadio = ToRadio()
|
||||
toRadio.packet = meshPacket
|
||||
|
||||
let binaryData: Data = try! toRadio.serializedData()
|
||||
|
||||
if connectedPeripheral!.peripheral.state == CBPeripheralState.connected {
|
||||
|
||||
let newMessage = MessageEntity(context: context!)
|
||||
newMessage.messageId = Int64(meshPacket.id)
|
||||
newMessage.messageTimestamp = Int32(Date().timeIntervalSince1970)
|
||||
|
|
@ -1566,19 +1540,14 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
newMessage.toUser = toUser
|
||||
|
||||
do {
|
||||
|
||||
connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse)
|
||||
try context!.save()
|
||||
MeshLogger.log("💾 \(adminDescription)")
|
||||
|
||||
print("⚙️ \(adminDescription)")
|
||||
return true
|
||||
|
||||
} catch {
|
||||
|
||||
context!.rollback()
|
||||
|
||||
let nsError = error as NSError
|
||||
MeshLogger.log("💥 Error inserting new core data MessageEntity: \(nsError)")
|
||||
print("💥 Error inserting new core data MessageEntity: \(nsError)")
|
||||
}
|
||||
}
|
||||
return false
|
||||
|
|
@ -1623,9 +1592,9 @@ extension BLEManager: CBCentralManagerDelegate {
|
|||
// Called each time a peripheral is discovered
|
||||
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String: Any], rssi RSSI: NSNumber) {
|
||||
|
||||
if timeoutTimerRuns < 2 && peripheral.identifier.uuidString == UserDefaults.standard.object(forKey: "preferredPeripheralId") as? String ?? "" {
|
||||
if self.automaticallyReconnect && timeoutTimerRuns < 2 && peripheral.identifier.uuidString == UserDefaults.standard.object(forKey: "preferredPeripheralId") as? String ?? "" {
|
||||
self.connectTo(peripheral: peripheral)
|
||||
MeshLogger.log("ℹ️ BLE Reconnecting to prefered peripheral: \(peripheral.name ?? "Unknown")")
|
||||
print("ℹ️ BLE Reconnecting to prefered peripheral: \(peripheral.name ?? "Unknown")")
|
||||
}
|
||||
let name = advertisementData[CBAdvertisementDataLocalNameKey] as? String
|
||||
let device = Peripheral(id: peripheral.identifier.uuidString, num: 0, name: name ?? "Unknown", shortName: "????", longName: name ?? "Unknown", firmwareVersion: "Unknown", rssi: RSSI.intValue, lastUpdate: Date(), peripheral: peripheral)
|
||||
|
|
|
|||
|
|
@ -12,9 +12,11 @@ class MeshLogger {
|
|||
guard let logFile = logFile else {
|
||||
return
|
||||
}
|
||||
|
||||
let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMddjmmssSSa", options: 0, locale: Locale.current)
|
||||
let dateFormatString = (localeDateFormat ?? "MM/dd/YY j:mm:ss.SS a")
|
||||
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateFormat = "M/d/yy h:mm:ss.SSSS"
|
||||
formatter.dateFormat = dateFormatString
|
||||
let timestamp = formatter.string(from: Date())
|
||||
guard let data = (message + " - " + timestamp + "\n").data(using: String.Encoding.utf8) else { return }
|
||||
print(message)
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -3,6 +3,6 @@
|
|||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>_XCCurrentVersionName</key>
|
||||
<string>MeshtasticDataModelV4.xcdatamodel</string>
|
||||
<string>MeshtasticDataModelV5.xcdatamodel</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,279 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="21513" systemVersion="22A400" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
||||
<entity name="BluetoothConfigEntity" representedClassName="BluetoothConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="fixedPin" optional="YES" attributeType="Integer 32" defaultValueString="123456" usesScalarValueType="YES"/>
|
||||
<attribute name="mode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="bluetoothConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="bluetoothConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="CannedMessageConfigEntity" representedClassName="CannedMessageConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerEventCcw" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerEventCw" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerEventPress" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerPinA" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerPinB" optional="YES" attributeType="Integer 32" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerPinPress" optional="YES" attributeType="Integer 32" usesScalarValueType="YES"/>
|
||||
<attribute name="messages" optional="YES" attributeType="String" minValueString="0" maxValueString="198"/>
|
||||
<attribute name="rotary1Enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="sendBell" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="updown1Enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<relationship name="cannedMessagesConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="cannedMessageConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="ChannelEntity" representedClassName="ChannelEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="downlinkEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="id" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="index" attributeType="Integer 32" minValueString="0" maxValueString="13" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="mute" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="name" optional="YES" attributeType="String"/>
|
||||
<attribute name="psk" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="role" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="uplinkEnabled" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<relationship name="myInfoChannel" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MyInfoEntity" inverseName="channels" inverseEntity="MyInfoEntity"/>
|
||||
<fetchedProperty name="allPrivateMessages" optional="YES">
|
||||
<fetchRequest name="fetchedPropertyFetchRequest" entity="MessageEntity" predicateString="channel == $FETCH_SOURCE.index && toUser == nil AND isEmoji == false"/>
|
||||
</fetchedProperty>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="index"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</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="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="DisplayConfigEntity" representedClassName="DisplayConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="compassNorthTop" optional="YES" attributeType="Boolean" defaultValueString="NO" 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="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"/>
|
||||
<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="usePWM" optional="YES" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<relationship name="externalNotificationConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="externalNotificationConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="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="modemPreset" optional="YES" attributeType="Integer 32" defaultValueString="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="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="realACK" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="receivedACK" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="replyID" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="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="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="bitrate" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="bleName" optional="YES" attributeType="String"/>
|
||||
<attribute name="errorCount" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="firmwareVersion" attributeType="String"/>
|
||||
<attribute name="hasGps" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="hasWifi" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="maxChannels" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="messageTimeoutMsec" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<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"/>
|
||||
<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="id" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="lastHeard" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="num" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<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="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="mqttConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MQTTConfigEntity" inverseName="mqttConfigNode" inverseEntity="MQTTConfigEntity"/>
|
||||
<relationship name="myInfo" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MyInfoEntity" inverseName="myInfoNode" inverseEntity="MyInfoEntity"/>
|
||||
<relationship name="networkConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NetworkConfigEntity" inverseName="networkConfigNode" inverseEntity="NetworkConfigEntity"/>
|
||||
<relationship name="positionConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="PositionConfigEntity" inverseName="positionConfigNode" inverseEntity="PositionConfigEntity"/>
|
||||
<relationship name="positions" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="PositionEntity" inverseName="nodePosition" inverseEntity="PositionEntity"/>
|
||||
<relationship name="rangeTestConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="RangeTestConfigEntity" inverseName="rangeTestConfigNode" inverseEntity="RangeTestConfigEntity"/>
|
||||
<relationship name="serialConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="SerialConfigEntity" inverseName="serialConfigNode" inverseEntity="SerialConfigEntity"/>
|
||||
<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="user" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="UserEntity" inverseName="userNode" inverseEntity="UserEntity"/>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="num"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="PositionConfigEntity" representedClassName="PositionConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="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="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="latitudeI" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="longitudeI" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="satsInView" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="seqNo" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="speed" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="time" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<relationship name="nodePosition" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="positions" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="RangeTestConfigEntity" representedClassName="RangeTestConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="save" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="sender" optional="YES" attributeType="Integer 32" usesScalarValueType="YES"/>
|
||||
<relationship name="rangeTestConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="rangeTestConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="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="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="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="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="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="longName" attributeType="String"/>
|
||||
<attribute name="macaddr" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="mute" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="num" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="shortName" attributeType="String"/>
|
||||
<attribute name="userId" attributeType="String"/>
|
||||
<relationship name="receivedMessages" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="MessageEntity" inverseName="toUser" inverseEntity="MessageEntity"/>
|
||||
<relationship name="sentMessages" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="MessageEntity" inverseName="fromUser" inverseEntity="MessageEntity"/>
|
||||
<relationship name="userNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="user" inverseEntity="NodeInfoEntity"/>
|
||||
<fetchedProperty name="adminMessages" optional="YES">
|
||||
<fetchRequest name="fetchedPropertyFetchRequest" entity="MessageEntity" predicateString="(toUser.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"/>
|
||||
</fetchedProperty>
|
||||
</entity>
|
||||
<entity name="WaypointEntity" representedClassName="WaypointEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="expire" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="id" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="latitudeI" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="locked" attributeType="Boolean" 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" optional="YES" attributeType="String" maxValueString="30"/>
|
||||
</entity>
|
||||
</model>
|
||||
|
|
@ -31,7 +31,7 @@ struct MeshtasticAppleApp: App {
|
|||
print("URL received \(userActivity)")
|
||||
self.incomingUrl = userActivity.webpageURL
|
||||
|
||||
if self.incomingUrl!.absoluteString.lowercased().contains("meshtastic.org/e/#") {
|
||||
if ((self.incomingUrl?.absoluteString.lowercased().contains("meshtastic.org/e/#")) != nil) {
|
||||
|
||||
if let components = self.incomingUrl?.absoluteString.components(separatedBy: "#") {
|
||||
self.channelSettings = components.last!
|
||||
|
|
|
|||
|
|
@ -61,11 +61,8 @@ public func clearTelemetry(destNum: Int64, metricsType: Int32, context: NSManage
|
|||
}
|
||||
|
||||
public func deleteChannelMessages(channel: ChannelEntity, context: NSManagedObjectContext) {
|
||||
let fetchChannelMessagesRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "MessageEntity")
|
||||
fetchChannelMessagesRequest.predicate = NSPredicate(format: "channel == %i AND toUser == nil AND admin == false", Int32(channel.id))
|
||||
fetchChannelMessagesRequest.includesPropertyValues = false
|
||||
do {
|
||||
let objects = try context.fetch(fetchChannelMessagesRequest) as! [NSManagedObject]
|
||||
let objects = channel.allPrivateMessages// try context.fetch(fetchChannelMessagesRequest) as! [NSManagedObject]
|
||||
for object in objects {
|
||||
context.delete(object)
|
||||
}
|
||||
|
|
@ -77,11 +74,8 @@ public func deleteChannelMessages(channel: ChannelEntity, context: NSManagedObje
|
|||
|
||||
public func deleteUserMessages(user: UserEntity, context: NSManagedObjectContext) {
|
||||
|
||||
let fetchUserMessagesRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "MessageEntity")
|
||||
fetchUserMessagesRequest.predicate = NSPredicate(format: "((toUser.num == %lld) OR (fromUser.num == %lld)) AND toUser != nil AND fromUser != nil AND admin == false", Int64(user.num), Int64(user.num))
|
||||
fetchUserMessagesRequest.includesPropertyValues = false
|
||||
do {
|
||||
let objects = try context.fetch(fetchUserMessagesRequest) as! [NSManagedObject]
|
||||
let objects = user.messageList//try context.fetch(fetchUserMessagesRequest) as! [NSManagedObject]
|
||||
for object in objects {
|
||||
context.delete(object)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -154,6 +154,26 @@ struct AdminMessage {
|
|||
set {payloadVariant = .getDeviceMetadataResponse(newValue)}
|
||||
}
|
||||
|
||||
///
|
||||
/// Get the Ringtone in the response to this message.
|
||||
var getRingtoneRequest: Bool {
|
||||
get {
|
||||
if case .getRingtoneRequest(let v)? = payloadVariant {return v}
|
||||
return false
|
||||
}
|
||||
set {payloadVariant = .getRingtoneRequest(newValue)}
|
||||
}
|
||||
|
||||
///
|
||||
/// Get the Ringtone in the response to this message.
|
||||
var getRingtoneResponse: String {
|
||||
get {
|
||||
if case .getRingtoneResponse(let v)? = payloadVariant {return v}
|
||||
return String()
|
||||
}
|
||||
set {payloadVariant = .getRingtoneResponse(newValue)}
|
||||
}
|
||||
|
||||
///
|
||||
/// Set the owner for this node
|
||||
var setOwner: User {
|
||||
|
|
@ -208,6 +228,16 @@ struct AdminMessage {
|
|||
set {payloadVariant = .setCannedMessageModuleMessages(newValue)}
|
||||
}
|
||||
|
||||
///
|
||||
/// Set the ringtone for ExternalNotification.
|
||||
var setRingtoneMessage: String {
|
||||
get {
|
||||
if case .setRingtoneMessage(let v)? = payloadVariant {return v}
|
||||
return String()
|
||||
}
|
||||
set {payloadVariant = .setRingtoneMessage(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)
|
||||
|
|
@ -357,6 +387,12 @@ struct AdminMessage {
|
|||
/// Device metadata response
|
||||
case getDeviceMetadataResponse(DeviceMetadata)
|
||||
///
|
||||
/// Get the Ringtone in the response to this message.
|
||||
case getRingtoneRequest(Bool)
|
||||
///
|
||||
/// Get the Ringtone in the response to this message.
|
||||
case getRingtoneResponse(String)
|
||||
///
|
||||
/// Set the owner for this node
|
||||
case setOwner(User)
|
||||
///
|
||||
|
|
@ -376,6 +412,9 @@ struct AdminMessage {
|
|||
/// Set the Canned Message Module messages text.
|
||||
case setCannedMessageModuleMessages(String)
|
||||
///
|
||||
/// Set the ringtone for ExternalNotification.
|
||||
case setRingtoneMessage(String)
|
||||
///
|
||||
/// 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)
|
||||
|
|
@ -466,6 +505,14 @@ struct AdminMessage {
|
|||
guard case .getDeviceMetadataResponse(let l) = lhs, case .getDeviceMetadataResponse(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.getRingtoneRequest, .getRingtoneRequest): return {
|
||||
guard case .getRingtoneRequest(let l) = lhs, case .getRingtoneRequest(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.getRingtoneResponse, .getRingtoneResponse): return {
|
||||
guard case .getRingtoneResponse(let l) = lhs, case .getRingtoneResponse(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.setOwner, .setOwner): return {
|
||||
guard case .setOwner(let l) = lhs, case .setOwner(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
|
|
@ -486,6 +533,10 @@ struct AdminMessage {
|
|||
guard case .setCannedMessageModuleMessages(let l) = lhs, case .setCannedMessageModuleMessages(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.setRingtoneMessage, .setRingtoneMessage): return {
|
||||
guard case .setRingtoneMessage(let l) = lhs, case .setRingtoneMessage(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
|
||||
|
|
@ -634,6 +685,10 @@ struct AdminMessage {
|
|||
///
|
||||
/// TODO: REPLACE
|
||||
case audioConfig // = 7
|
||||
|
||||
///
|
||||
/// TODO: REPLACE
|
||||
case remotehardwareConfig // = 8
|
||||
case UNRECOGNIZED(Int)
|
||||
|
||||
init() {
|
||||
|
|
@ -650,6 +705,7 @@ struct AdminMessage {
|
|||
case 5: self = .telemetryConfig
|
||||
case 6: self = .cannedmsgConfig
|
||||
case 7: self = .audioConfig
|
||||
case 8: self = .remotehardwareConfig
|
||||
default: self = .UNRECOGNIZED(rawValue)
|
||||
}
|
||||
}
|
||||
|
|
@ -664,6 +720,7 @@ struct AdminMessage {
|
|||
case .telemetryConfig: return 5
|
||||
case .cannedmsgConfig: return 6
|
||||
case .audioConfig: return 7
|
||||
case .remotehardwareConfig: return 8
|
||||
case .UNRECOGNIZED(let i): return i
|
||||
}
|
||||
}
|
||||
|
|
@ -699,6 +756,7 @@ extension AdminMessage.ModuleConfigType: CaseIterable {
|
|||
.telemetryConfig,
|
||||
.cannedmsgConfig,
|
||||
.audioConfig,
|
||||
.remotehardwareConfig,
|
||||
]
|
||||
}
|
||||
|
||||
|
|
@ -728,11 +786,14 @@ extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat
|
|||
11: .standard(proto: "get_canned_message_module_messages_response"),
|
||||
12: .standard(proto: "get_device_metadata_request"),
|
||||
13: .standard(proto: "get_device_metadata_response"),
|
||||
14: .standard(proto: "get_ringtone_request"),
|
||||
15: .standard(proto: "get_ringtone_response"),
|
||||
32: .standard(proto: "set_owner"),
|
||||
33: .standard(proto: "set_channel"),
|
||||
34: .standard(proto: "set_config"),
|
||||
35: .standard(proto: "set_module_config"),
|
||||
36: .standard(proto: "set_canned_message_module_messages"),
|
||||
37: .standard(proto: "set_ringtone_message"),
|
||||
64: .standard(proto: "begin_edit_settings"),
|
||||
65: .standard(proto: "commit_edit_settings"),
|
||||
66: .standard(proto: "confirm_set_channel"),
|
||||
|
|
@ -872,6 +933,22 @@ extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat
|
|||
self.payloadVariant = .getDeviceMetadataResponse(v)
|
||||
}
|
||||
}()
|
||||
case 14: try {
|
||||
var v: Bool?
|
||||
try decoder.decodeSingularBoolField(value: &v)
|
||||
if let v = v {
|
||||
if self.payloadVariant != nil {try decoder.handleConflictingOneOf()}
|
||||
self.payloadVariant = .getRingtoneRequest(v)
|
||||
}
|
||||
}()
|
||||
case 15: try {
|
||||
var v: String?
|
||||
try decoder.decodeSingularStringField(value: &v)
|
||||
if let v = v {
|
||||
if self.payloadVariant != nil {try decoder.handleConflictingOneOf()}
|
||||
self.payloadVariant = .getRingtoneResponse(v)
|
||||
}
|
||||
}()
|
||||
case 32: try {
|
||||
var v: User?
|
||||
var hadOneofValue = false
|
||||
|
|
@ -932,6 +1009,14 @@ extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat
|
|||
self.payloadVariant = .setCannedMessageModuleMessages(v)
|
||||
}
|
||||
}()
|
||||
case 37: try {
|
||||
var v: String?
|
||||
try decoder.decodeSingularStringField(value: &v)
|
||||
if let v = v {
|
||||
if self.payloadVariant != nil {try decoder.handleConflictingOneOf()}
|
||||
self.payloadVariant = .setRingtoneMessage(v)
|
||||
}
|
||||
}()
|
||||
case 64: try {
|
||||
var v: Bool?
|
||||
try decoder.decodeSingularBoolField(value: &v)
|
||||
|
|
@ -1071,6 +1156,14 @@ extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat
|
|||
guard case .getDeviceMetadataResponse(let v)? = self.payloadVariant else { preconditionFailure() }
|
||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 13)
|
||||
}()
|
||||
case .getRingtoneRequest?: try {
|
||||
guard case .getRingtoneRequest(let v)? = self.payloadVariant else { preconditionFailure() }
|
||||
try visitor.visitSingularBoolField(value: v, fieldNumber: 14)
|
||||
}()
|
||||
case .getRingtoneResponse?: try {
|
||||
guard case .getRingtoneResponse(let v)? = self.payloadVariant else { preconditionFailure() }
|
||||
try visitor.visitSingularStringField(value: v, fieldNumber: 15)
|
||||
}()
|
||||
case .setOwner?: try {
|
||||
guard case .setOwner(let v)? = self.payloadVariant else { preconditionFailure() }
|
||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 32)
|
||||
|
|
@ -1091,6 +1184,10 @@ extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat
|
|||
guard case .setCannedMessageModuleMessages(let v)? = self.payloadVariant else { preconditionFailure() }
|
||||
try visitor.visitSingularStringField(value: v, fieldNumber: 36)
|
||||
}()
|
||||
case .setRingtoneMessage?: try {
|
||||
guard case .setRingtoneMessage(let v)? = self.payloadVariant else { preconditionFailure() }
|
||||
try visitor.visitSingularStringField(value: v, fieldNumber: 37)
|
||||
}()
|
||||
case .beginEditSettings?: try {
|
||||
guard case .beginEditSettings(let v)? = self.payloadVariant else { preconditionFailure() }
|
||||
try visitor.visitSingularBoolField(value: v, fieldNumber: 64)
|
||||
|
|
@ -1165,5 +1262,6 @@ extension AdminMessage.ModuleConfigType: SwiftProtobuf._ProtoNameProviding {
|
|||
5: .same(proto: "TELEMETRY_CONFIG"),
|
||||
6: .same(proto: "CANNEDMSG_CONFIG"),
|
||||
7: .same(proto: "AUDIO_CONFIG"),
|
||||
8: .same(proto: "REMOTEHARDWARE_CONFIG"),
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -474,7 +474,7 @@ struct Config {
|
|||
|
||||
///
|
||||
/// acquire an address via DHCP or assign static
|
||||
var ethMode: Config.NetworkConfig.EthMode = .dhcp
|
||||
var addressMode: Config.NetworkConfig.AddressMode = .dhcp
|
||||
|
||||
///
|
||||
/// struct to keep static address
|
||||
|
|
@ -489,7 +489,7 @@ struct Config {
|
|||
|
||||
var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
enum EthMode: SwiftProtobuf.Enum {
|
||||
enum AddressMode: SwiftProtobuf.Enum {
|
||||
typealias RawValue = Int
|
||||
|
||||
///
|
||||
|
|
@ -592,6 +592,14 @@ struct Config {
|
|||
/// Override auto-detect in screen
|
||||
var oled: Config.DisplayConfig.OledType = .oledAuto
|
||||
|
||||
///
|
||||
/// Display Mode
|
||||
var displaymode: Config.DisplayConfig.DisplayMode = .default
|
||||
|
||||
///
|
||||
/// Print first line in pseudo-bold? FALSE is original style, TRUE is bold
|
||||
var headingBold: Bool = false
|
||||
|
||||
var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
///
|
||||
|
|
@ -739,6 +747,52 @@ struct Config {
|
|||
|
||||
}
|
||||
|
||||
enum DisplayMode: SwiftProtobuf.Enum {
|
||||
typealias RawValue = Int
|
||||
|
||||
///
|
||||
/// Default. The old style for the 128x64 OLED screen
|
||||
case `default` // = 0
|
||||
|
||||
///
|
||||
/// Rearrange display elements to cater for bicolor OLED displays
|
||||
case twocolor // = 1
|
||||
|
||||
///
|
||||
/// Same as TwoColor, but with inverted top bar. Not so good for Epaper displays
|
||||
case inverted // = 2
|
||||
|
||||
///
|
||||
/// TFT Full Color Displays (not implemented yet)
|
||||
case color // = 3
|
||||
case UNRECOGNIZED(Int)
|
||||
|
||||
init() {
|
||||
self = .default
|
||||
}
|
||||
|
||||
init?(rawValue: Int) {
|
||||
switch rawValue {
|
||||
case 0: self = .default
|
||||
case 1: self = .twocolor
|
||||
case 2: self = .inverted
|
||||
case 3: self = .color
|
||||
default: self = .UNRECOGNIZED(rawValue)
|
||||
}
|
||||
}
|
||||
|
||||
var rawValue: Int {
|
||||
switch self {
|
||||
case .default: return 0
|
||||
case .twocolor: return 1
|
||||
case .inverted: return 2
|
||||
case .color: return 3
|
||||
case .UNRECOGNIZED(let i): return i
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
init() {}
|
||||
}
|
||||
|
||||
|
|
@ -1099,9 +1153,9 @@ extension Config.PositionConfig.PositionFlags: CaseIterable {
|
|||
]
|
||||
}
|
||||
|
||||
extension Config.NetworkConfig.EthMode: CaseIterable {
|
||||
extension Config.NetworkConfig.AddressMode: CaseIterable {
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
static var allCases: [Config.NetworkConfig.EthMode] = [
|
||||
static var allCases: [Config.NetworkConfig.AddressMode] = [
|
||||
.dhcp,
|
||||
.static,
|
||||
]
|
||||
|
|
@ -1136,6 +1190,16 @@ extension Config.DisplayConfig.OledType: CaseIterable {
|
|||
]
|
||||
}
|
||||
|
||||
extension Config.DisplayConfig.DisplayMode: CaseIterable {
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
static var allCases: [Config.DisplayConfig.DisplayMode] = [
|
||||
.default,
|
||||
.twocolor,
|
||||
.inverted,
|
||||
.color,
|
||||
]
|
||||
}
|
||||
|
||||
extension Config.LoRaConfig.RegionCode: CaseIterable {
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
static var allCases: [Config.LoRaConfig.RegionCode] = [
|
||||
|
|
@ -1189,12 +1253,13 @@ extension Config.PositionConfig: @unchecked Sendable {}
|
|||
extension Config.PositionConfig.PositionFlags: @unchecked Sendable {}
|
||||
extension Config.PowerConfig: @unchecked Sendable {}
|
||||
extension Config.NetworkConfig: @unchecked Sendable {}
|
||||
extension Config.NetworkConfig.EthMode: @unchecked Sendable {}
|
||||
extension Config.NetworkConfig.AddressMode: @unchecked Sendable {}
|
||||
extension Config.NetworkConfig.IpV4Config: @unchecked Sendable {}
|
||||
extension Config.DisplayConfig: @unchecked Sendable {}
|
||||
extension Config.DisplayConfig.GpsCoordinateFormat: @unchecked Sendable {}
|
||||
extension Config.DisplayConfig.DisplayUnits: @unchecked Sendable {}
|
||||
extension Config.DisplayConfig.OledType: @unchecked Sendable {}
|
||||
extension Config.DisplayConfig.DisplayMode: @unchecked Sendable {}
|
||||
extension Config.LoRaConfig: @unchecked Sendable {}
|
||||
extension Config.LoRaConfig.RegionCode: @unchecked Sendable {}
|
||||
extension Config.LoRaConfig.ModemPreset: @unchecked Sendable {}
|
||||
|
|
@ -1607,7 +1672,7 @@ extension Config.NetworkConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImp
|
|||
4: .standard(proto: "wifi_psk"),
|
||||
5: .standard(proto: "ntp_server"),
|
||||
6: .standard(proto: "eth_enabled"),
|
||||
7: .standard(proto: "eth_mode"),
|
||||
7: .standard(proto: "address_mode"),
|
||||
8: .standard(proto: "ipv4_config"),
|
||||
]
|
||||
|
||||
|
|
@ -1622,7 +1687,7 @@ extension Config.NetworkConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImp
|
|||
case 4: try { try decoder.decodeSingularStringField(value: &self.wifiPsk) }()
|
||||
case 5: try { try decoder.decodeSingularStringField(value: &self.ntpServer) }()
|
||||
case 6: try { try decoder.decodeSingularBoolField(value: &self.ethEnabled) }()
|
||||
case 7: try { try decoder.decodeSingularEnumField(value: &self.ethMode) }()
|
||||
case 7: try { try decoder.decodeSingularEnumField(value: &self.addressMode) }()
|
||||
case 8: try { try decoder.decodeSingularMessageField(value: &self._ipv4Config) }()
|
||||
default: break
|
||||
}
|
||||
|
|
@ -1649,8 +1714,8 @@ extension Config.NetworkConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImp
|
|||
if self.ethEnabled != false {
|
||||
try visitor.visitSingularBoolField(value: self.ethEnabled, fieldNumber: 6)
|
||||
}
|
||||
if self.ethMode != .dhcp {
|
||||
try visitor.visitSingularEnumField(value: self.ethMode, fieldNumber: 7)
|
||||
if self.addressMode != .dhcp {
|
||||
try visitor.visitSingularEnumField(value: self.addressMode, fieldNumber: 7)
|
||||
}
|
||||
try { if let v = self._ipv4Config {
|
||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 8)
|
||||
|
|
@ -1664,14 +1729,14 @@ extension Config.NetworkConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImp
|
|||
if lhs.wifiPsk != rhs.wifiPsk {return false}
|
||||
if lhs.ntpServer != rhs.ntpServer {return false}
|
||||
if lhs.ethEnabled != rhs.ethEnabled {return false}
|
||||
if lhs.ethMode != rhs.ethMode {return false}
|
||||
if lhs.addressMode != rhs.addressMode {return false}
|
||||
if lhs._ipv4Config != rhs._ipv4Config {return false}
|
||||
if lhs.unknownFields != rhs.unknownFields {return false}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
extension Config.NetworkConfig.EthMode: SwiftProtobuf._ProtoNameProviding {
|
||||
extension Config.NetworkConfig.AddressMode: SwiftProtobuf._ProtoNameProviding {
|
||||
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
||||
0: .same(proto: "DHCP"),
|
||||
1: .same(proto: "STATIC"),
|
||||
|
|
@ -1738,6 +1803,8 @@ extension Config.DisplayConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImp
|
|||
5: .standard(proto: "flip_screen"),
|
||||
6: .same(proto: "units"),
|
||||
7: .same(proto: "oled"),
|
||||
8: .same(proto: "displaymode"),
|
||||
9: .standard(proto: "heading_bold"),
|
||||
]
|
||||
|
||||
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||
|
|
@ -1753,6 +1820,8 @@ extension Config.DisplayConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImp
|
|||
case 5: try { try decoder.decodeSingularBoolField(value: &self.flipScreen) }()
|
||||
case 6: try { try decoder.decodeSingularEnumField(value: &self.units) }()
|
||||
case 7: try { try decoder.decodeSingularEnumField(value: &self.oled) }()
|
||||
case 8: try { try decoder.decodeSingularEnumField(value: &self.displaymode) }()
|
||||
case 9: try { try decoder.decodeSingularBoolField(value: &self.headingBold) }()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
|
@ -1780,6 +1849,12 @@ extension Config.DisplayConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImp
|
|||
if self.oled != .oledAuto {
|
||||
try visitor.visitSingularEnumField(value: self.oled, fieldNumber: 7)
|
||||
}
|
||||
if self.displaymode != .default {
|
||||
try visitor.visitSingularEnumField(value: self.displaymode, fieldNumber: 8)
|
||||
}
|
||||
if self.headingBold != false {
|
||||
try visitor.visitSingularBoolField(value: self.headingBold, fieldNumber: 9)
|
||||
}
|
||||
try unknownFields.traverse(visitor: &visitor)
|
||||
}
|
||||
|
||||
|
|
@ -1791,6 +1866,8 @@ extension Config.DisplayConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImp
|
|||
if lhs.flipScreen != rhs.flipScreen {return false}
|
||||
if lhs.units != rhs.units {return false}
|
||||
if lhs.oled != rhs.oled {return false}
|
||||
if lhs.displaymode != rhs.displaymode {return false}
|
||||
if lhs.headingBold != rhs.headingBold {return false}
|
||||
if lhs.unknownFields != rhs.unknownFields {return false}
|
||||
return true
|
||||
}
|
||||
|
|
@ -1822,6 +1899,15 @@ extension Config.DisplayConfig.OledType: SwiftProtobuf._ProtoNameProviding {
|
|||
]
|
||||
}
|
||||
|
||||
extension Config.DisplayConfig.DisplayMode: SwiftProtobuf._ProtoNameProviding {
|
||||
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
||||
0: .same(proto: "DEFAULT"),
|
||||
1: .same(proto: "TWOCOLOR"),
|
||||
2: .same(proto: "INVERTED"),
|
||||
3: .same(proto: "COLOR"),
|
||||
]
|
||||
}
|
||||
|
||||
extension Config.LoRaConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
||||
static let protoMessageName: String = Config.protoMessageName + ".LoRaConfig"
|
||||
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
||||
|
|
|
|||
|
|
@ -211,6 +211,17 @@ struct LocalModuleConfig {
|
|||
/// Clears the value of `audio`. Subsequent reads from it will return its default value.
|
||||
mutating func clearAudio() {_uniqueStorage()._audio = nil}
|
||||
|
||||
///
|
||||
/// The part of the config that is specific to the Remote Hardware module
|
||||
var remoteHardware: ModuleConfig.RemoteHardwareConfig {
|
||||
get {return _storage._remoteHardware ?? ModuleConfig.RemoteHardwareConfig()}
|
||||
set {_uniqueStorage()._remoteHardware = newValue}
|
||||
}
|
||||
/// Returns true if `remoteHardware` has been explicitly set.
|
||||
var hasRemoteHardware: Bool {return _storage._remoteHardware != nil}
|
||||
/// Clears the value of `remoteHardware`. Subsequent reads from it will return its default value.
|
||||
mutating func clearRemoteHardware() {_uniqueStorage()._remoteHardware = nil}
|
||||
|
||||
///
|
||||
/// A version integer used to invalidate old save files when we make
|
||||
/// incompatible changes This integer is set at build time and is private to
|
||||
|
|
@ -369,6 +380,7 @@ extension LocalModuleConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem
|
|||
6: .same(proto: "telemetry"),
|
||||
7: .standard(proto: "canned_message"),
|
||||
9: .same(proto: "audio"),
|
||||
10: .standard(proto: "remote_hardware"),
|
||||
8: .same(proto: "version"),
|
||||
]
|
||||
|
||||
|
|
@ -381,6 +393,7 @@ extension LocalModuleConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem
|
|||
var _telemetry: ModuleConfig.TelemetryConfig? = nil
|
||||
var _cannedMessage: ModuleConfig.CannedMessageConfig? = nil
|
||||
var _audio: ModuleConfig.AudioConfig? = nil
|
||||
var _remoteHardware: ModuleConfig.RemoteHardwareConfig? = nil
|
||||
var _version: UInt32 = 0
|
||||
|
||||
static let defaultInstance = _StorageClass()
|
||||
|
|
@ -396,6 +409,7 @@ extension LocalModuleConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem
|
|||
_telemetry = source._telemetry
|
||||
_cannedMessage = source._cannedMessage
|
||||
_audio = source._audio
|
||||
_remoteHardware = source._remoteHardware
|
||||
_version = source._version
|
||||
}
|
||||
}
|
||||
|
|
@ -424,6 +438,7 @@ extension LocalModuleConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem
|
|||
case 7: try { try decoder.decodeSingularMessageField(value: &_storage._cannedMessage) }()
|
||||
case 8: try { try decoder.decodeSingularUInt32Field(value: &_storage._version) }()
|
||||
case 9: try { try decoder.decodeSingularMessageField(value: &_storage._audio) }()
|
||||
case 10: try { try decoder.decodeSingularMessageField(value: &_storage._remoteHardware) }()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
|
@ -463,6 +478,9 @@ extension LocalModuleConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem
|
|||
try { if let v = _storage._audio {
|
||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 9)
|
||||
} }()
|
||||
try { if let v = _storage._remoteHardware {
|
||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 10)
|
||||
} }()
|
||||
}
|
||||
try unknownFields.traverse(visitor: &visitor)
|
||||
}
|
||||
|
|
@ -480,6 +498,7 @@ extension LocalModuleConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem
|
|||
if _storage._telemetry != rhs_storage._telemetry {return false}
|
||||
if _storage._cannedMessage != rhs_storage._cannedMessage {return false}
|
||||
if _storage._audio != rhs_storage._audio {return false}
|
||||
if _storage._remoteHardware != rhs_storage._remoteHardware {return false}
|
||||
if _storage._version != rhs_storage._version {return false}
|
||||
return true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -94,6 +94,10 @@ enum HardwareModel: SwiftProtobuf.Enum {
|
|||
/// TODO: REPLACE
|
||||
case tloraV211P8 // = 15
|
||||
|
||||
///
|
||||
/// TODO: REPLACE
|
||||
case tloraT3S3 // = 16
|
||||
|
||||
///
|
||||
/// B&Q Consulting Station Edition G1: https://uniteng.com/wiki/doku.php?id=meshtastic:station
|
||||
case stationG1 // = 25
|
||||
|
|
@ -150,6 +154,10 @@ enum HardwareModel: SwiftProtobuf.Enum {
|
|||
/// New Heltec Wireless Stick Lite with ESP32-S3 CPU
|
||||
case heltecWslV3 // = 44
|
||||
|
||||
///
|
||||
/// New BETAFPV ELRS Micro TX Module 2.4G with ESP32 CPU
|
||||
case betafpv2400Tx // = 45
|
||||
|
||||
///
|
||||
/// Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits.
|
||||
case privateHw // = 255
|
||||
|
|
@ -177,6 +185,7 @@ enum HardwareModel: SwiftProtobuf.Enum {
|
|||
case 13: self = .rak11200
|
||||
case 14: self = .nanoG1
|
||||
case 15: self = .tloraV211P8
|
||||
case 16: self = .tloraT3S3
|
||||
case 25: self = .stationG1
|
||||
case 32: self = .loraRelayV1
|
||||
case 33: self = .nrf52840Dk
|
||||
|
|
@ -191,6 +200,7 @@ enum HardwareModel: SwiftProtobuf.Enum {
|
|||
case 42: self = .m5Stack
|
||||
case 43: self = .heltecV3
|
||||
case 44: self = .heltecWslV3
|
||||
case 45: self = .betafpv2400Tx
|
||||
case 255: self = .privateHw
|
||||
default: self = .UNRECOGNIZED(rawValue)
|
||||
}
|
||||
|
|
@ -214,6 +224,7 @@ enum HardwareModel: SwiftProtobuf.Enum {
|
|||
case .rak11200: return 13
|
||||
case .nanoG1: return 14
|
||||
case .tloraV211P8: return 15
|
||||
case .tloraT3S3: return 16
|
||||
case .stationG1: return 25
|
||||
case .loraRelayV1: return 32
|
||||
case .nrf52840Dk: return 33
|
||||
|
|
@ -228,6 +239,7 @@ enum HardwareModel: SwiftProtobuf.Enum {
|
|||
case .m5Stack: return 42
|
||||
case .heltecV3: return 43
|
||||
case .heltecWslV3: return 44
|
||||
case .betafpv2400Tx: return 45
|
||||
case .privateHw: return 255
|
||||
case .UNRECOGNIZED(let i): return i
|
||||
}
|
||||
|
|
@ -256,6 +268,7 @@ extension HardwareModel: CaseIterable {
|
|||
.rak11200,
|
||||
.nanoG1,
|
||||
.tloraV211P8,
|
||||
.tloraT3S3,
|
||||
.stationG1,
|
||||
.loraRelayV1,
|
||||
.nrf52840Dk,
|
||||
|
|
@ -270,6 +283,7 @@ extension HardwareModel: CaseIterable {
|
|||
.m5Stack,
|
||||
.heltecV3,
|
||||
.heltecWslV3,
|
||||
.betafpv2400Tx,
|
||||
.privateHw,
|
||||
]
|
||||
}
|
||||
|
|
@ -1765,6 +1779,28 @@ extension LogRecord.Level: CaseIterable {
|
|||
|
||||
#endif // swift(>=4.2)
|
||||
|
||||
struct QueueStatus {
|
||||
// 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.
|
||||
|
||||
/// Last attempt to queue status, ErrorCode
|
||||
var res: Int32 = 0
|
||||
|
||||
/// Free entries in the outgoing queue
|
||||
var free: UInt32 = 0
|
||||
|
||||
/// Maximum entries in the outgoing queue
|
||||
var maxlen: UInt32 = 0
|
||||
|
||||
/// What was mesh packet id that generated this response?
|
||||
var meshPacketID: UInt32 = 0
|
||||
|
||||
var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
init() {}
|
||||
}
|
||||
|
||||
///
|
||||
/// Packets from the radio to the phone will appear on the fromRadio characteristic.
|
||||
/// It will support READ and NOTIFY. When a new packet arrives the device will BLE notify?
|
||||
|
|
@ -1888,6 +1924,15 @@ struct FromRadio {
|
|||
set {_uniqueStorage()._payloadVariant = .channel(newValue)}
|
||||
}
|
||||
|
||||
/// Queue status info
|
||||
var queueStatus: QueueStatus {
|
||||
get {
|
||||
if case .queueStatus(let v)? = _storage._payloadVariant {return v}
|
||||
return QueueStatus()
|
||||
}
|
||||
set {_uniqueStorage()._payloadVariant = .queueStatus(newValue)}
|
||||
}
|
||||
|
||||
var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
///
|
||||
|
|
@ -1928,6 +1973,8 @@ struct FromRadio {
|
|||
///
|
||||
/// One packet is sent for each channel
|
||||
case channel(Channel)
|
||||
/// Queue status info
|
||||
case queueStatus(QueueStatus)
|
||||
|
||||
#if !swift(>=4.1)
|
||||
static func ==(lhs: FromRadio.OneOf_PayloadVariant, rhs: FromRadio.OneOf_PayloadVariant) -> Bool {
|
||||
|
|
@ -1971,6 +2018,10 @@ struct FromRadio {
|
|||
guard case .channel(let l) = lhs, case .channel(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.queueStatus, .queueStatus): return {
|
||||
guard case .queueStatus(let l) = lhs, case .queueStatus(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
|
|
@ -2126,6 +2177,7 @@ extension NodeInfo: @unchecked Sendable {}
|
|||
extension MyNodeInfo: @unchecked Sendable {}
|
||||
extension LogRecord: @unchecked Sendable {}
|
||||
extension LogRecord.Level: @unchecked Sendable {}
|
||||
extension QueueStatus: @unchecked Sendable {}
|
||||
extension FromRadio: @unchecked Sendable {}
|
||||
extension FromRadio.OneOf_PayloadVariant: @unchecked Sendable {}
|
||||
extension ToRadio: @unchecked Sendable {}
|
||||
|
|
@ -2153,6 +2205,7 @@ extension HardwareModel: SwiftProtobuf._ProtoNameProviding {
|
|||
13: .same(proto: "RAK11200"),
|
||||
14: .same(proto: "NANO_G1"),
|
||||
15: .same(proto: "TLORA_V2_1_1P8"),
|
||||
16: .same(proto: "TLORA_T3_S3"),
|
||||
25: .same(proto: "STATION_G1"),
|
||||
32: .same(proto: "LORA_RELAY_V1"),
|
||||
33: .same(proto: "NRF52840DK"),
|
||||
|
|
@ -2167,6 +2220,7 @@ extension HardwareModel: SwiftProtobuf._ProtoNameProviding {
|
|||
42: .same(proto: "M5STACK"),
|
||||
43: .same(proto: "HELTEC_V3"),
|
||||
44: .same(proto: "HELTEC_WSL_V3"),
|
||||
45: .same(proto: "BETAFPV_2400_TX"),
|
||||
255: .same(proto: "PRIVATE_HW"),
|
||||
]
|
||||
}
|
||||
|
|
@ -3237,6 +3291,56 @@ extension LogRecord.Level: SwiftProtobuf._ProtoNameProviding {
|
|||
]
|
||||
}
|
||||
|
||||
extension QueueStatus: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
||||
static let protoMessageName: String = "QueueStatus"
|
||||
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
||||
1: .same(proto: "res"),
|
||||
2: .same(proto: "free"),
|
||||
3: .same(proto: "maxlen"),
|
||||
4: .standard(proto: "mesh_packet_id"),
|
||||
]
|
||||
|
||||
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.decodeSingularInt32Field(value: &self.res) }()
|
||||
case 2: try { try decoder.decodeSingularUInt32Field(value: &self.free) }()
|
||||
case 3: try { try decoder.decodeSingularUInt32Field(value: &self.maxlen) }()
|
||||
case 4: try { try decoder.decodeSingularUInt32Field(value: &self.meshPacketID) }()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
|
||||
if self.res != 0 {
|
||||
try visitor.visitSingularInt32Field(value: self.res, fieldNumber: 1)
|
||||
}
|
||||
if self.free != 0 {
|
||||
try visitor.visitSingularUInt32Field(value: self.free, fieldNumber: 2)
|
||||
}
|
||||
if self.maxlen != 0 {
|
||||
try visitor.visitSingularUInt32Field(value: self.maxlen, fieldNumber: 3)
|
||||
}
|
||||
if self.meshPacketID != 0 {
|
||||
try visitor.visitSingularUInt32Field(value: self.meshPacketID, fieldNumber: 4)
|
||||
}
|
||||
try unknownFields.traverse(visitor: &visitor)
|
||||
}
|
||||
|
||||
static func ==(lhs: QueueStatus, rhs: QueueStatus) -> Bool {
|
||||
if lhs.res != rhs.res {return false}
|
||||
if lhs.free != rhs.free {return false}
|
||||
if lhs.maxlen != rhs.maxlen {return false}
|
||||
if lhs.meshPacketID != rhs.meshPacketID {return false}
|
||||
if lhs.unknownFields != rhs.unknownFields {return false}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
extension FromRadio: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
||||
static let protoMessageName: String = "FromRadio"
|
||||
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
||||
|
|
@ -3250,6 +3354,7 @@ extension FromRadio: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementation
|
|||
8: .same(proto: "rebooted"),
|
||||
9: .same(proto: "moduleConfig"),
|
||||
10: .same(proto: "channel"),
|
||||
11: .same(proto: "queueStatus"),
|
||||
]
|
||||
|
||||
fileprivate class _StorageClass {
|
||||
|
|
@ -3389,6 +3494,19 @@ extension FromRadio: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementation
|
|||
_storage._payloadVariant = .channel(v)
|
||||
}
|
||||
}()
|
||||
case 11: try {
|
||||
var v: QueueStatus?
|
||||
var hadOneofValue = false
|
||||
if let current = _storage._payloadVariant {
|
||||
hadOneofValue = true
|
||||
if case .queueStatus(let m) = current {v = m}
|
||||
}
|
||||
try decoder.decodeSingularMessageField(value: &v)
|
||||
if let v = v {
|
||||
if hadOneofValue {try decoder.handleConflictingOneOf()}
|
||||
_storage._payloadVariant = .queueStatus(v)
|
||||
}
|
||||
}()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
|
@ -3441,6 +3559,10 @@ extension FromRadio: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementation
|
|||
guard case .channel(let v)? = _storage._payloadVariant else { preconditionFailure() }
|
||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 10)
|
||||
}()
|
||||
case .queueStatus?: try {
|
||||
guard case .queueStatus(let v)? = _storage._payloadVariant else { preconditionFailure() }
|
||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 11)
|
||||
}()
|
||||
case nil: break
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -111,6 +111,16 @@ struct ModuleConfig {
|
|||
set {payloadVariant = .audio(newValue)}
|
||||
}
|
||||
|
||||
///
|
||||
/// TODO: REPLACE
|
||||
var remoteHardware: ModuleConfig.RemoteHardwareConfig {
|
||||
get {
|
||||
if case .remoteHardware(let v)? = payloadVariant {return v}
|
||||
return ModuleConfig.RemoteHardwareConfig()
|
||||
}
|
||||
set {payloadVariant = .remoteHardware(newValue)}
|
||||
}
|
||||
|
||||
var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
///
|
||||
|
|
@ -140,6 +150,9 @@ struct ModuleConfig {
|
|||
///
|
||||
/// TODO: REPLACE
|
||||
case audio(ModuleConfig.AudioConfig)
|
||||
///
|
||||
/// TODO: REPLACE
|
||||
case remoteHardware(ModuleConfig.RemoteHardwareConfig)
|
||||
|
||||
#if !swift(>=4.1)
|
||||
static func ==(lhs: ModuleConfig.OneOf_PayloadVariant, rhs: ModuleConfig.OneOf_PayloadVariant) -> Bool {
|
||||
|
|
@ -179,6 +192,10 @@ struct ModuleConfig {
|
|||
guard case .audio(let l) = lhs, case .audio(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.remoteHardware, .remoteHardware): return {
|
||||
guard case .remoteHardware(let l) = lhs, case .remoteHardware(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
|
|
@ -230,6 +247,22 @@ struct ModuleConfig {
|
|||
init() {}
|
||||
}
|
||||
|
||||
///
|
||||
/// RemoteHardwareModule Config
|
||||
struct RemoteHardwareConfig {
|
||||
// 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.
|
||||
|
||||
///
|
||||
/// Whether the Module is enabled
|
||||
var enabled: Bool = false
|
||||
|
||||
var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
init() {}
|
||||
}
|
||||
|
||||
///
|
||||
/// Audio Config for codec2 voice
|
||||
struct AudioConfig {
|
||||
|
|
@ -844,6 +877,7 @@ extension ModuleConfig.CannedMessageConfig.InputEventChar: CaseIterable {
|
|||
extension ModuleConfig: @unchecked Sendable {}
|
||||
extension ModuleConfig.OneOf_PayloadVariant: @unchecked Sendable {}
|
||||
extension ModuleConfig.MQTTConfig: @unchecked Sendable {}
|
||||
extension ModuleConfig.RemoteHardwareConfig: @unchecked Sendable {}
|
||||
extension ModuleConfig.AudioConfig: @unchecked Sendable {}
|
||||
extension ModuleConfig.AudioConfig.Audio_Baud: @unchecked Sendable {}
|
||||
extension ModuleConfig.SerialConfig: @unchecked Sendable {}
|
||||
|
|
@ -870,6 +904,7 @@ extension ModuleConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat
|
|||
6: .same(proto: "telemetry"),
|
||||
7: .standard(proto: "canned_message"),
|
||||
8: .same(proto: "audio"),
|
||||
9: .standard(proto: "remote_hardware"),
|
||||
]
|
||||
|
||||
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||
|
|
@ -982,6 +1017,19 @@ extension ModuleConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat
|
|||
self.payloadVariant = .audio(v)
|
||||
}
|
||||
}()
|
||||
case 9: try {
|
||||
var v: ModuleConfig.RemoteHardwareConfig?
|
||||
var hadOneofValue = false
|
||||
if let current = self.payloadVariant {
|
||||
hadOneofValue = true
|
||||
if case .remoteHardware(let m) = current {v = m}
|
||||
}
|
||||
try decoder.decodeSingularMessageField(value: &v)
|
||||
if let v = v {
|
||||
if hadOneofValue {try decoder.handleConflictingOneOf()}
|
||||
self.payloadVariant = .remoteHardware(v)
|
||||
}
|
||||
}()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
|
@ -1025,6 +1073,10 @@ extension ModuleConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat
|
|||
guard case .audio(let v)? = self.payloadVariant else { preconditionFailure() }
|
||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 8)
|
||||
}()
|
||||
case .remoteHardware?: try {
|
||||
guard case .remoteHardware(let v)? = self.payloadVariant else { preconditionFailure() }
|
||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 9)
|
||||
}()
|
||||
case nil: break
|
||||
}
|
||||
try unknownFields.traverse(visitor: &visitor)
|
||||
|
|
@ -1099,6 +1151,38 @@ extension ModuleConfig.MQTTConfig: SwiftProtobuf.Message, SwiftProtobuf._Message
|
|||
}
|
||||
}
|
||||
|
||||
extension ModuleConfig.RemoteHardwareConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
||||
static let protoMessageName: String = ModuleConfig.protoMessageName + ".RemoteHardwareConfig"
|
||||
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
||||
1: .same(proto: "enabled"),
|
||||
]
|
||||
|
||||
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.decodeSingularBoolField(value: &self.enabled) }()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
|
||||
if self.enabled != false {
|
||||
try visitor.visitSingularBoolField(value: self.enabled, fieldNumber: 1)
|
||||
}
|
||||
try unknownFields.traverse(visitor: &visitor)
|
||||
}
|
||||
|
||||
static func ==(lhs: ModuleConfig.RemoteHardwareConfig, rhs: ModuleConfig.RemoteHardwareConfig) -> Bool {
|
||||
if lhs.enabled != rhs.enabled {return false}
|
||||
if lhs.unknownFields != rhs.unknownFields {return false}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
extension ModuleConfig.AudioConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
||||
static let protoMessageName: String = ModuleConfig.protoMessageName + ".AudioConfig"
|
||||
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
||||
|
|
|
|||
|
|
@ -31,44 +31,99 @@ struct StoreAndForward {
|
|||
/// TODO: REPLACE
|
||||
var rr: StoreAndForward.RequestResponse = .unset
|
||||
|
||||
///
|
||||
/// TODO: REPLACE
|
||||
var variant: StoreAndForward.OneOf_Variant? = nil
|
||||
|
||||
///
|
||||
/// TODO: REPLACE
|
||||
var stats: StoreAndForward.Statistics {
|
||||
get {return _stats ?? StoreAndForward.Statistics()}
|
||||
set {_stats = newValue}
|
||||
get {
|
||||
if case .stats(let v)? = variant {return v}
|
||||
return StoreAndForward.Statistics()
|
||||
}
|
||||
set {variant = .stats(newValue)}
|
||||
}
|
||||
/// Returns true if `stats` has been explicitly set.
|
||||
var hasStats: Bool {return self._stats != nil}
|
||||
/// Clears the value of `stats`. Subsequent reads from it will return its default value.
|
||||
mutating func clearStats() {self._stats = nil}
|
||||
|
||||
///
|
||||
/// TODO: REPLACE
|
||||
var history: StoreAndForward.History {
|
||||
get {return _history ?? StoreAndForward.History()}
|
||||
set {_history = newValue}
|
||||
get {
|
||||
if case .history(let v)? = variant {return v}
|
||||
return StoreAndForward.History()
|
||||
}
|
||||
set {variant = .history(newValue)}
|
||||
}
|
||||
/// Returns true if `history` has been explicitly set.
|
||||
var hasHistory: Bool {return self._history != nil}
|
||||
/// Clears the value of `history`. Subsequent reads from it will return its default value.
|
||||
mutating func clearHistory() {self._history = nil}
|
||||
|
||||
///
|
||||
/// TODO: REPLACE
|
||||
var heartbeat: StoreAndForward.Heartbeat {
|
||||
get {return _heartbeat ?? StoreAndForward.Heartbeat()}
|
||||
set {_heartbeat = newValue}
|
||||
get {
|
||||
if case .heartbeat(let v)? = variant {return v}
|
||||
return StoreAndForward.Heartbeat()
|
||||
}
|
||||
set {variant = .heartbeat(newValue)}
|
||||
}
|
||||
|
||||
///
|
||||
/// Empty Payload
|
||||
var empty: Bool {
|
||||
get {
|
||||
if case .empty(let v)? = variant {return v}
|
||||
return false
|
||||
}
|
||||
set {variant = .empty(newValue)}
|
||||
}
|
||||
/// Returns true if `heartbeat` has been explicitly set.
|
||||
var hasHeartbeat: Bool {return self._heartbeat != nil}
|
||||
/// Clears the value of `heartbeat`. Subsequent reads from it will return its default value.
|
||||
mutating func clearHeartbeat() {self._heartbeat = nil}
|
||||
|
||||
var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
///
|
||||
/// 1 - 99 = From Router
|
||||
/// 101 - 199 = From Client
|
||||
/// TODO: REPLACE
|
||||
enum OneOf_Variant: Equatable {
|
||||
///
|
||||
/// TODO: REPLACE
|
||||
case stats(StoreAndForward.Statistics)
|
||||
///
|
||||
/// TODO: REPLACE
|
||||
case history(StoreAndForward.History)
|
||||
///
|
||||
/// TODO: REPLACE
|
||||
case heartbeat(StoreAndForward.Heartbeat)
|
||||
///
|
||||
/// Empty Payload
|
||||
case empty(Bool)
|
||||
|
||||
#if !swift(>=4.1)
|
||||
static func ==(lhs: StoreAndForward.OneOf_Variant, rhs: StoreAndForward.OneOf_Variant) -> Bool {
|
||||
// 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 (lhs, rhs) {
|
||||
case (.stats, .stats): return {
|
||||
guard case .stats(let l) = lhs, case .stats(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.history, .history): return {
|
||||
guard case .history(let l) = lhs, case .history(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
|
||||
}()
|
||||
case (.empty, .empty): return {
|
||||
guard case .empty(let l) = lhs, case .empty(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
///
|
||||
/// 001 - 063 = From Router
|
||||
/// 064 - 127 = From Client
|
||||
enum RequestResponse: SwiftProtobuf.Enum {
|
||||
typealias RawValue = Int
|
||||
|
||||
|
|
@ -101,26 +156,30 @@ struct StoreAndForward {
|
|||
/// Router is responding to a request for history.
|
||||
case routerHistory // = 6
|
||||
|
||||
///
|
||||
/// Router is responding to a request for stats.
|
||||
case routerStats // = 7
|
||||
|
||||
///
|
||||
/// Client is an in error state.
|
||||
case clientError // = 101
|
||||
case clientError // = 64
|
||||
|
||||
///
|
||||
/// Client has requested a replay from the router.
|
||||
case clientHistory // = 102
|
||||
case clientHistory // = 65
|
||||
|
||||
///
|
||||
/// Client has requested stats from the router.
|
||||
case clientStats // = 103
|
||||
case clientStats // = 66
|
||||
|
||||
///
|
||||
/// Client has requested the router respond. This can work as a
|
||||
/// "are you there" message.
|
||||
case clientPing // = 104
|
||||
case clientPing // = 67
|
||||
|
||||
///
|
||||
/// The response to a "Ping"
|
||||
case clientPong // = 105
|
||||
case clientPong // = 68
|
||||
|
||||
///
|
||||
/// Client has requested that the router abort processing the client's request
|
||||
|
|
@ -140,11 +199,12 @@ struct StoreAndForward {
|
|||
case 4: self = .routerPong
|
||||
case 5: self = .routerBusy
|
||||
case 6: self = .routerHistory
|
||||
case 101: self = .clientError
|
||||
case 102: self = .clientHistory
|
||||
case 103: self = .clientStats
|
||||
case 104: self = .clientPing
|
||||
case 105: self = .clientPong
|
||||
case 7: self = .routerStats
|
||||
case 64: self = .clientError
|
||||
case 65: self = .clientHistory
|
||||
case 66: self = .clientStats
|
||||
case 67: self = .clientPing
|
||||
case 68: self = .clientPong
|
||||
case 106: self = .clientAbort
|
||||
default: self = .UNRECOGNIZED(rawValue)
|
||||
}
|
||||
|
|
@ -159,11 +219,12 @@ struct StoreAndForward {
|
|||
case .routerPong: return 4
|
||||
case .routerBusy: return 5
|
||||
case .routerHistory: return 6
|
||||
case .clientError: return 101
|
||||
case .clientHistory: return 102
|
||||
case .clientStats: return 103
|
||||
case .clientPing: return 104
|
||||
case .clientPong: return 105
|
||||
case .routerStats: return 7
|
||||
case .clientError: return 64
|
||||
case .clientHistory: return 65
|
||||
case .clientStats: return 66
|
||||
case .clientPing: return 67
|
||||
case .clientPong: return 68
|
||||
case .clientAbort: return 106
|
||||
case .UNRECOGNIZED(let i): return i
|
||||
}
|
||||
|
|
@ -264,10 +325,6 @@ struct StoreAndForward {
|
|||
}
|
||||
|
||||
init() {}
|
||||
|
||||
fileprivate var _stats: StoreAndForward.Statistics? = nil
|
||||
fileprivate var _history: StoreAndForward.History? = nil
|
||||
fileprivate var _heartbeat: StoreAndForward.Heartbeat? = nil
|
||||
}
|
||||
|
||||
#if swift(>=4.2)
|
||||
|
|
@ -282,6 +339,7 @@ extension StoreAndForward.RequestResponse: CaseIterable {
|
|||
.routerPong,
|
||||
.routerBusy,
|
||||
.routerHistory,
|
||||
.routerStats,
|
||||
.clientError,
|
||||
.clientHistory,
|
||||
.clientStats,
|
||||
|
|
@ -295,6 +353,7 @@ extension StoreAndForward.RequestResponse: CaseIterable {
|
|||
|
||||
#if swift(>=5.5) && canImport(_Concurrency)
|
||||
extension StoreAndForward: @unchecked Sendable {}
|
||||
extension StoreAndForward.OneOf_Variant: @unchecked Sendable {}
|
||||
extension StoreAndForward.RequestResponse: @unchecked Sendable {}
|
||||
extension StoreAndForward.Statistics: @unchecked Sendable {}
|
||||
extension StoreAndForward.History: @unchecked Sendable {}
|
||||
|
|
@ -310,6 +369,7 @@ extension StoreAndForward: SwiftProtobuf.Message, SwiftProtobuf._MessageImplemen
|
|||
2: .same(proto: "stats"),
|
||||
3: .same(proto: "history"),
|
||||
4: .same(proto: "heartbeat"),
|
||||
5: .same(proto: "empty"),
|
||||
]
|
||||
|
||||
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||
|
|
@ -319,9 +379,53 @@ extension StoreAndForward: SwiftProtobuf.Message, SwiftProtobuf._MessageImplemen
|
|||
// enabled. https://github.com/apple/swift-protobuf/issues/1034
|
||||
switch fieldNumber {
|
||||
case 1: try { try decoder.decodeSingularEnumField(value: &self.rr) }()
|
||||
case 2: try { try decoder.decodeSingularMessageField(value: &self._stats) }()
|
||||
case 3: try { try decoder.decodeSingularMessageField(value: &self._history) }()
|
||||
case 4: try { try decoder.decodeSingularMessageField(value: &self._heartbeat) }()
|
||||
case 2: try {
|
||||
var v: StoreAndForward.Statistics?
|
||||
var hadOneofValue = false
|
||||
if let current = self.variant {
|
||||
hadOneofValue = true
|
||||
if case .stats(let m) = current {v = m}
|
||||
}
|
||||
try decoder.decodeSingularMessageField(value: &v)
|
||||
if let v = v {
|
||||
if hadOneofValue {try decoder.handleConflictingOneOf()}
|
||||
self.variant = .stats(v)
|
||||
}
|
||||
}()
|
||||
case 3: try {
|
||||
var v: StoreAndForward.History?
|
||||
var hadOneofValue = false
|
||||
if let current = self.variant {
|
||||
hadOneofValue = true
|
||||
if case .history(let m) = current {v = m}
|
||||
}
|
||||
try decoder.decodeSingularMessageField(value: &v)
|
||||
if let v = v {
|
||||
if hadOneofValue {try decoder.handleConflictingOneOf()}
|
||||
self.variant = .history(v)
|
||||
}
|
||||
}()
|
||||
case 4: try {
|
||||
var v: StoreAndForward.Heartbeat?
|
||||
var hadOneofValue = false
|
||||
if let current = self.variant {
|
||||
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.variant = .heartbeat(v)
|
||||
}
|
||||
}()
|
||||
case 5: try {
|
||||
var v: Bool?
|
||||
try decoder.decodeSingularBoolField(value: &v)
|
||||
if let v = v {
|
||||
if self.variant != nil {try decoder.handleConflictingOneOf()}
|
||||
self.variant = .empty(v)
|
||||
}
|
||||
}()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
|
@ -335,23 +439,31 @@ extension StoreAndForward: SwiftProtobuf.Message, SwiftProtobuf._MessageImplemen
|
|||
if self.rr != .unset {
|
||||
try visitor.visitSingularEnumField(value: self.rr, fieldNumber: 1)
|
||||
}
|
||||
try { if let v = self._stats {
|
||||
switch self.variant {
|
||||
case .stats?: try {
|
||||
guard case .stats(let v)? = self.variant else { preconditionFailure() }
|
||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 2)
|
||||
} }()
|
||||
try { if let v = self._history {
|
||||
}()
|
||||
case .history?: try {
|
||||
guard case .history(let v)? = self.variant else { preconditionFailure() }
|
||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 3)
|
||||
} }()
|
||||
try { if let v = self._heartbeat {
|
||||
}()
|
||||
case .heartbeat?: try {
|
||||
guard case .heartbeat(let v)? = self.variant else { preconditionFailure() }
|
||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 4)
|
||||
} }()
|
||||
}()
|
||||
case .empty?: try {
|
||||
guard case .empty(let v)? = self.variant else { preconditionFailure() }
|
||||
try visitor.visitSingularBoolField(value: v, fieldNumber: 5)
|
||||
}()
|
||||
case nil: break
|
||||
}
|
||||
try unknownFields.traverse(visitor: &visitor)
|
||||
}
|
||||
|
||||
static func ==(lhs: StoreAndForward, rhs: StoreAndForward) -> Bool {
|
||||
if lhs.rr != rhs.rr {return false}
|
||||
if lhs._stats != rhs._stats {return false}
|
||||
if lhs._history != rhs._history {return false}
|
||||
if lhs._heartbeat != rhs._heartbeat {return false}
|
||||
if lhs.variant != rhs.variant {return false}
|
||||
if lhs.unknownFields != rhs.unknownFields {return false}
|
||||
return true
|
||||
}
|
||||
|
|
@ -366,11 +478,12 @@ extension StoreAndForward.RequestResponse: SwiftProtobuf._ProtoNameProviding {
|
|||
4: .same(proto: "ROUTER_PONG"),
|
||||
5: .same(proto: "ROUTER_BUSY"),
|
||||
6: .same(proto: "ROUTER_HISTORY"),
|
||||
101: .same(proto: "CLIENT_ERROR"),
|
||||
102: .same(proto: "CLIENT_HISTORY"),
|
||||
103: .same(proto: "CLIENT_STATS"),
|
||||
104: .same(proto: "CLIENT_PING"),
|
||||
105: .same(proto: "CLIENT_PONG"),
|
||||
7: .same(proto: "ROUTER_STATS"),
|
||||
64: .same(proto: "CLIENT_ERROR"),
|
||||
65: .same(proto: "CLIENT_HISTORY"),
|
||||
66: .same(proto: "CLIENT_STATS"),
|
||||
67: .same(proto: "CLIENT_PING"),
|
||||
68: .same(proto: "CLIENT_PONG"),
|
||||
106: .same(proto: "CLIENT_ABORT"),
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,6 +72,10 @@ enum TelemetrySensorType: SwiftProtobuf.Enum {
|
|||
///
|
||||
/// 3-Axis magnetic sensor
|
||||
case qmc5883L // = 11
|
||||
|
||||
///
|
||||
/// High accuracy temperature and humidity
|
||||
case sht31 // = 12
|
||||
case UNRECOGNIZED(Int)
|
||||
|
||||
init() {
|
||||
|
|
@ -92,6 +96,7 @@ enum TelemetrySensorType: SwiftProtobuf.Enum {
|
|||
case 9: self = .qmc6310
|
||||
case 10: self = .qmi8658
|
||||
case 11: self = .qmc5883L
|
||||
case 12: self = .sht31
|
||||
default: self = .UNRECOGNIZED(rawValue)
|
||||
}
|
||||
}
|
||||
|
|
@ -110,6 +115,7 @@ enum TelemetrySensorType: SwiftProtobuf.Enum {
|
|||
case .qmc6310: return 9
|
||||
case .qmi8658: return 10
|
||||
case .qmc5883L: return 11
|
||||
case .sht31: return 12
|
||||
case .UNRECOGNIZED(let i): return i
|
||||
}
|
||||
}
|
||||
|
|
@ -133,6 +139,7 @@ extension TelemetrySensorType: CaseIterable {
|
|||
.qmc6310,
|
||||
.qmi8658,
|
||||
.qmc5883L,
|
||||
.sht31,
|
||||
]
|
||||
}
|
||||
|
||||
|
|
@ -296,6 +303,7 @@ extension TelemetrySensorType: SwiftProtobuf._ProtoNameProviding {
|
|||
9: .same(proto: "QMC6310"),
|
||||
10: .same(proto: "QMI8658"),
|
||||
11: .same(proto: "QMC5883L"),
|
||||
12: .same(proto: "SHT31"),
|
||||
]
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -39,10 +39,10 @@ struct Connect: View {
|
|||
if node != nil {
|
||||
Text(bleManager.connectedPeripheral.longName).font(.title2)
|
||||
}
|
||||
Text("ble.name").font(.caption)+Text(": \(bleManager.connectedPeripheral.peripheral.name ?? "Unknown")")
|
||||
Text("ble.name").font(.caption)+Text(": \(bleManager.connectedPeripheral.peripheral.name ?? NSLocalizedString("unknown", comment: "Unknown"))")
|
||||
.font(.caption).foregroundColor(Color.gray)
|
||||
if node != nil {
|
||||
Text("firmware.version").font(.caption)+Text(": \(node?.myInfo?.firmwareVersion ?? "Unknown")")
|
||||
Text("firmware.version").font(.caption)+Text(": \(node?.myInfo?.firmwareVersion ?? NSLocalizedString("unknown", comment: "Unknown"))")
|
||||
.font(.caption).foregroundColor(Color.gray)
|
||||
}
|
||||
if bleManager.isSubscribed {
|
||||
|
|
@ -88,7 +88,7 @@ struct Connect: View {
|
|||
|
||||
Button(role: .destructive) {
|
||||
if bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral.peripheral.state == CBPeripheralState.connected {
|
||||
bleManager.disconnectPeripheral()
|
||||
bleManager.disconnectPeripheral(reconnect: false)
|
||||
isPreferredRadio = false
|
||||
}
|
||||
} label: {
|
||||
|
|
@ -101,7 +101,7 @@ struct Connect: View {
|
|||
|
||||
Text("Num: \(String(node!.num))")
|
||||
Text("Short Name: \(node?.user?.shortName ?? "????")")
|
||||
Text("Long Name: \(node?.user?.longName ?? "Unknown")")
|
||||
Text("Long Name: \(node?.user?.longName ?? NSLocalizedString("unknown", comment: "Unknown"))")
|
||||
Text("Max Channels: \(String(node?.myInfo?.maxChannels ?? 0))")
|
||||
Text("Bitrate: \(String(format: "%.2f", node?.myInfo?.bitrate ?? 0.00))")
|
||||
Text("BLE RSSI: \(bleManager.connectedPeripheral.rssi)")
|
||||
|
|
@ -194,7 +194,7 @@ struct Connect: View {
|
|||
}
|
||||
|
||||
} else {
|
||||
Text("Bluetooth: OFF")
|
||||
Text("bluetooth.off")
|
||||
.foregroundColor(.red)
|
||||
.font(.title)
|
||||
}
|
||||
|
|
@ -210,7 +210,7 @@ struct Connect: View {
|
|||
Button(role: .destructive, action: {
|
||||
|
||||
if bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral.peripheral.state == CBPeripheralState.connected {
|
||||
bleManager.disconnectPeripheral()
|
||||
bleManager.disconnectPeripheral(reconnect: false)
|
||||
isPreferredRadio = false
|
||||
}
|
||||
|
||||
|
|
@ -287,6 +287,6 @@ struct Connect: View {
|
|||
})
|
||||
}
|
||||
func didDismissSheet() {
|
||||
bleManager.disconnectPeripheral()
|
||||
bleManager.disconnectPeripheral(reconnect: false)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ struct InvalidVersion: View {
|
|||
|
||||
VStack {
|
||||
|
||||
Text("Update Firmware")
|
||||
Text("update.firmware")
|
||||
.font(.largeTitle)
|
||||
.foregroundColor(.orange)
|
||||
|
||||
|
|
@ -51,7 +51,7 @@ struct InvalidVersion: View {
|
|||
Button {
|
||||
dismiss()
|
||||
} label: {
|
||||
Label("Close", systemImage: "xmark")
|
||||
Label("close", systemImage: "xmark")
|
||||
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ struct ConnectedDevice: View {
|
|||
|
||||
}
|
||||
} else {
|
||||
Text("Bluetooth Off").font(.subheadline).foregroundColor(.red)
|
||||
Text("bluetooth.off").font(.subheadline).foregroundColor(.red)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,64 +0,0 @@
|
|||
import SwiftUI
|
||||
|
||||
struct MessageBubble: View {
|
||||
|
||||
@State var showAlert = false
|
||||
var contentMessage: String
|
||||
var isCurrentUser: Bool
|
||||
var time: Int32
|
||||
var shortName: String
|
||||
var id: UInt32
|
||||
|
||||
var body: some View {
|
||||
|
||||
HStack(alignment: .top) {
|
||||
|
||||
CircleText(text: shortName, color: isCurrentUser ? .accentColor : Color(.gray)).padding(.all, 5)
|
||||
.gesture(LongPressGesture(minimumDuration: 2)
|
||||
.onEnded {_ in
|
||||
print("I want to delete message: \(id)")
|
||||
self.showAlert = true
|
||||
})
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
Text(contentMessage)
|
||||
.textSelection(.enabled)
|
||||
.padding(10)
|
||||
.foregroundColor(.white)
|
||||
.background(isCurrentUser ? .accentColor : Color(.gray))
|
||||
.cornerRadius(10)
|
||||
HStack(spacing: 4) {
|
||||
|
||||
let messageDate = Date(timeIntervalSince1970: TimeInterval(time))
|
||||
|
||||
if time != 0 {
|
||||
Text(messageDate, style: .date).font(.caption2).foregroundColor(.gray)
|
||||
Text(messageDate, style: .time).font(.caption2).foregroundColor(.gray)
|
||||
} else {
|
||||
Text("Unknown").font(.caption2).foregroundColor(.gray)
|
||||
}
|
||||
}
|
||||
.padding(.bottom, 10)
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
.alert(isPresented: $showAlert) {
|
||||
|
||||
Alert(title: Text("Are you sure you want to delete this message?"), message: Text("This action is permanent."),
|
||||
primaryButton: .destructive(Text("OK")) {
|
||||
print("OK button tapped")
|
||||
},
|
||||
secondaryButton: .cancel()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct MessageBubble_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
Group {
|
||||
MessageBubble(contentMessage: "this is the best text ever", isCurrentUser: true, time: 0, shortName: "EB", id: 12)
|
||||
}
|
||||
.previewLayout(.fixed(width: 300, height: 100))
|
||||
}
|
||||
}
|
||||
|
|
@ -21,7 +21,7 @@ struct NodeAnnotation: View {
|
|||
} else {
|
||||
|
||||
VStack(spacing: 0) {
|
||||
Text("Unknown Time")
|
||||
Text("unknown.age")
|
||||
.font(.caption2).foregroundColor(.accentColor)
|
||||
.padding(5)
|
||||
.background(Color(.white))
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import UIKit
|
||||
import MapKit
|
||||
import SwiftUI
|
||||
|
||||
// a simple circle annotation, with a string in it
|
||||
class PositionAnnotation: NSObject, MKAnnotation {
|
||||
|
|
@ -53,11 +54,7 @@ class PositionAnnotationView: MKAnnotationView {
|
|||
guard let context = UIGraphicsGetCurrentContext() else { return }
|
||||
|
||||
let circleRect = CGRect(x: 1, y: 1, width: 38, height: 38)
|
||||
|
||||
context.setFillColor(CGColor(red: 0, green: 0.5, blue: 1.0, alpha: 1.0))
|
||||
|
||||
context.setFillColor(Color.accentColor.cgColor ?? CGColor(red: 0, green: 0.5, blue: 1.0, alpha: 1.0))
|
||||
context.fillEllipse(in: circleRect)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ private extension MapView {
|
|||
if (node.positions?.count ?? 0) > 0 && (node.positions!.lastObject as! PositionEntity).coordinate != nil {
|
||||
let annotation = PositionAnnotation()
|
||||
annotation.coordinate = (node.positions!.lastObject as! PositionEntity).coordinate!
|
||||
annotation.title = node.user?.longName ?? "Unknown"
|
||||
annotation.title = node.user?.longName ?? NSLocalizedString("unknown", comment: "Unknown")
|
||||
annotation.shortName = node.user?.shortName?.uppercased() ?? "???"
|
||||
|
||||
view.addAnnotation(annotation)
|
||||
|
|
|
|||
|
|
@ -118,20 +118,6 @@ public struct MapView: UIViewRepresentable {
|
|||
mapView.delegate = context.coordinator
|
||||
mapView.register(PositionAnnotationView.self, forAnnotationViewWithReuseIdentifier: NSStringFromClass(PositionAnnotationView.self))
|
||||
|
||||
/*Timer.scheduledTimer(withTimeInterval: 10, repeats: true) { timer in
|
||||
for node in self.locationNodes {
|
||||
// try and get the last position
|
||||
if (node.positions?.count ?? 0) > 0 && (node.positions!.lastObject as! PositionEntity).coordinate != nil {
|
||||
let annotation = PositionAnnotation()
|
||||
annotation.coordinate = (node.positions!.lastObject as! PositionEntity).coordinate!
|
||||
annotation.title = node.user?.longName ?? "Unknown"
|
||||
annotation.shortName = node.user?.shortName?.uppercased() ?? "???"
|
||||
|
||||
mapView.addAnnotation(annotation)
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
return mapView
|
||||
}
|
||||
|
||||
|
|
@ -229,18 +215,6 @@ public struct MapView: UIViewRepresentable {
|
|||
} else {
|
||||
shouldMoveRegion = true
|
||||
}
|
||||
|
||||
/*for node in self.locationNodes {
|
||||
// try and get the last position
|
||||
if (node.positions?.count ?? 0) > 0 && (node.positions!.lastObject as! PositionEntity).coordinate != nil {
|
||||
let annotation = PositionAnnotation()
|
||||
annotation.coordinate = (node.positions!.lastObject as! PositionEntity).coordinate!
|
||||
annotation.title = node.user?.longName ?? "Unknown"
|
||||
annotation.shortName = node.user?.shortName?.uppercased() ?? "???"
|
||||
|
||||
mapView.addAnnotation(annotation)
|
||||
}
|
||||
}*/
|
||||
|
||||
var displayedNodes: [Int64] = []
|
||||
for position in self.positions {
|
||||
|
|
@ -250,7 +224,7 @@ public struct MapView: UIViewRepresentable {
|
|||
|
||||
let annotation = PositionAnnotation()
|
||||
annotation.coordinate = position.coordinate!
|
||||
annotation.title = position.nodePosition!.user?.longName ?? "Unknown"
|
||||
annotation.title = position.nodePosition!.user?.longName ?? NSLocalizedString("unknown", comment: "Unknown")
|
||||
annotation.shortName = position.nodePosition!.user?.shortName?.uppercased() ?? "???"
|
||||
|
||||
mapView.addAnnotation(annotation)
|
||||
|
|
|
|||
|
|
@ -32,6 +32,8 @@ struct ChannelMessageList: View {
|
|||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMddjmmssa", options: 0, locale: Locale.current)
|
||||
let dateFormatString = (localeDateFormat ?? "MM/dd/YY j:mm:ss a")
|
||||
ScrollViewReader { scrollView in
|
||||
ScrollView {
|
||||
LazyVStack {
|
||||
|
|
@ -104,7 +106,7 @@ struct ChannelMessageList: View {
|
|||
Menu("message.details") {
|
||||
VStack {
|
||||
let messageDate = Date(timeIntervalSince1970: TimeInterval(message.messageTimestamp))
|
||||
Text("Date \(messageDate, style: .date) \(messageDate.formattedDate(format: "h:mm:ss a"))").font(.caption2).foregroundColor(.gray)
|
||||
Text(" \(messageDate.formattedDate(format: dateFormatString))").foregroundColor(.gray)
|
||||
}
|
||||
if !currentUser {
|
||||
VStack {
|
||||
|
|
@ -120,23 +122,22 @@ struct ChannelMessageList: View {
|
|||
Text("waiting")
|
||||
} else if currentUser && message.ackError > 0 {
|
||||
let ackErrorVal = RoutingError(rawValue: Int(message.ackError))
|
||||
Text("\(ackErrorVal?.display ?? "No Error" )").fixedSize(horizontal: false, vertical: true)
|
||||
Text("\(ackErrorVal?.display ?? "Empty Ack Error")").fixedSize(horizontal: false, vertical: true)
|
||||
}
|
||||
if currentUser {
|
||||
VStack {
|
||||
let ackDate = Date(timeIntervalSince1970: TimeInterval(message.ackTimestamp))
|
||||
let sixMonthsAgo = Calendar.current.date(byAdding: .month, value: -6, to: Date())
|
||||
if ackDate >= sixMonthsAgo! {
|
||||
Text((ackDate.formattedDate(format: "h:mm:ss a"))).font(.caption2).foregroundColor(.gray)
|
||||
Text("Ack Time: \(ackDate.formattedDate(format: "h:mm:ss a"))").foregroundColor(.gray)
|
||||
} else {
|
||||
Text("unknown.age").font(.caption2).foregroundColor(.gray)
|
||||
Text("unknown.age").foregroundColor(.gray)
|
||||
}
|
||||
}
|
||||
}
|
||||
if message.ackSNR != 0 {
|
||||
VStack {
|
||||
Text("Ack SNR\(String(format: "%.2f", message.ackSNR)) dB")
|
||||
.font(.caption2)
|
||||
Text("Ack SNR: \(String(format: "%.2f", message.ackSNR)) dB")
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
}
|
||||
|
|
@ -184,7 +185,7 @@ struct ChannelMessageList: View {
|
|||
Text("Waiting to be acknowledged. . .").font(.caption2).foregroundColor(.orange)
|
||||
} else if currentUser && message.ackError > 0 {
|
||||
let ackErrorVal = RoutingError(rawValue: Int(message.ackError))
|
||||
Text("\(ackErrorVal?.display ?? "No Error" )").fixedSize(horizontal: false, vertical: true)
|
||||
Text("\(ackErrorVal?.display ?? "Empty Ack Error")").fixedSize(horizontal: false, vertical: true)
|
||||
.font(.caption2).foregroundColor(.red)
|
||||
}
|
||||
}
|
||||
|
|
@ -355,7 +356,7 @@ struct ChannelMessageList: View {
|
|||
ToolbarItem(placement: .principal) {
|
||||
HStack {
|
||||
CircleText(text: String(channel.index), color: .accentColor, circleSize: 44, fontSize: 30).fixedSize()
|
||||
Text(String(channel.name ?? "Unknown").camelCaseToWords()).font(.headline)
|
||||
Text(String(channel.name ?? NSLocalizedString("unknown", comment: "Unknown")).camelCaseToWords()).font(.headline)
|
||||
}
|
||||
}
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
|
|
|
|||
|
|
@ -20,7 +20,8 @@ struct Contacts: View {
|
|||
|
||||
private var users: FetchedResults<UserEntity>
|
||||
@State var node: NodeInfoEntity? = nil
|
||||
@State private var selection: UserEntity? = nil // Nothing selected by default.
|
||||
@State private var userSelection: UserEntity? = nil // Nothing selected by default.
|
||||
@State private var channelSelection: ChannelEntity? = nil // Nothing selected by default.
|
||||
@State private var isPresentingDeleteChannelMessagesConfirm: Bool = false
|
||||
@State private var isPresentingDeleteUserMessagesConfirm: Bool = false
|
||||
@State private var isPresentingTraceRouteSentAlert = false
|
||||
|
|
@ -28,6 +29,8 @@ struct Contacts: View {
|
|||
var body: some View {
|
||||
|
||||
NavigationSplitView {
|
||||
let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMdd", options: 0, locale: Locale.current)
|
||||
let dateFormatString = (localeDateFormat ?? "MM/dd/YY")
|
||||
List {
|
||||
Section(header: Text("channels")) {
|
||||
// Display Contacts for the rest of the non admin channels
|
||||
|
|
@ -65,10 +68,10 @@ struct Contacts: View {
|
|||
Text("Yesterday")
|
||||
.font(.subheadline)
|
||||
} else if lastMessageDay < (currentDay - 1) && lastMessageDay > (currentDay - 5) {
|
||||
Text(lastMessageTime.formattedDate(format: "MM/dd/yy"))
|
||||
Text(lastMessageTime.formattedDate(format: dateFormatString))
|
||||
.font(.subheadline)
|
||||
} else if lastMessageDay < (currentDay - 1800) {
|
||||
Text(lastMessageTime.formattedDate(format: "MM/dd/yy"))
|
||||
Text(lastMessageTime.formattedDate(format: dateFormatString))
|
||||
.font(.subheadline)
|
||||
}
|
||||
}
|
||||
|
|
@ -111,6 +114,7 @@ struct Contacts: View {
|
|||
if channel.allPrivateMessages.count > 0 {
|
||||
Button(role: .destructive) {
|
||||
isPresentingDeleteChannelMessagesConfirm = true
|
||||
channelSelection = channel
|
||||
} label: {
|
||||
Label("Delete Messages", systemImage: "trash")
|
||||
}
|
||||
|
|
@ -119,13 +123,12 @@ struct Contacts: View {
|
|||
.confirmationDialog(
|
||||
"This conversation will be deleted.",
|
||||
isPresented: $isPresentingDeleteChannelMessagesConfirm,
|
||||
|
||||
titleVisibility: .visible
|
||||
) {
|
||||
|
||||
Button(role: .destructive) {
|
||||
deleteChannelMessages(channel: channel, context: context)
|
||||
deleteChannelMessages(channel: channelSelection!, context: context)
|
||||
context.refresh(node!.myInfo!, mergeChanges: true)
|
||||
channelSelection = nil
|
||||
} label: {
|
||||
Text("delete")
|
||||
}
|
||||
|
|
@ -151,7 +154,7 @@ struct Contacts: View {
|
|||
.padding(.trailing, 5)
|
||||
VStack {
|
||||
HStack {
|
||||
Text(user.longName ?? "Unknown").font(.headline)
|
||||
Text(user.longName ?? NSLocalizedString("unknown", comment: "Unknown")).font(.headline)
|
||||
Spacer()
|
||||
if user.messageList.count > 0 {
|
||||
VStack (alignment: .trailing) {
|
||||
|
|
@ -162,10 +165,10 @@ struct Contacts: View {
|
|||
Text("Yesterday")
|
||||
.font(.subheadline)
|
||||
} else if lastMessageDay < (currentDay - 1) && lastMessageDay > (currentDay - 5) {
|
||||
Text(lastMessageTime.formattedDate(format: "MM/dd/yy"))
|
||||
Text(lastMessageTime.formattedDate(format: dateFormatString))
|
||||
.font(.subheadline)
|
||||
} else if lastMessageDay < (currentDay - 1800) {
|
||||
Text(lastMessageTime.formattedDate(format: "MM/dd/yy"))
|
||||
Text(lastMessageTime.formattedDate(format: dateFormatString))
|
||||
.font(.subheadline)
|
||||
}
|
||||
}
|
||||
|
|
@ -206,6 +209,7 @@ struct Contacts: View {
|
|||
if user.messageList.count > 0 {
|
||||
Button(role: .destructive) {
|
||||
isPresentingDeleteUserMessagesConfirm = true
|
||||
userSelection = user
|
||||
} label: {
|
||||
Label("Delete Messages", systemImage: "trash")
|
||||
}
|
||||
|
|
@ -227,7 +231,7 @@ struct Contacts: View {
|
|||
titleVisibility: .visible
|
||||
) {
|
||||
Button(role: .destructive) {
|
||||
deleteUserMessages(user: user, context: context)
|
||||
deleteUserMessages(user: userSelection!, context: context)
|
||||
context.refresh(node!.user!, mergeChanges: true)
|
||||
} label: {
|
||||
Text("delete")
|
||||
|
|
@ -272,7 +276,7 @@ struct Contacts: View {
|
|||
}
|
||||
}
|
||||
detail: {
|
||||
if let user = selection {
|
||||
if let user = userSelection {
|
||||
UserMessageList(user:user)
|
||||
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -31,6 +31,8 @@ struct UserMessageList: View {
|
|||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMddjmmss", options: 0, locale: Locale.current)
|
||||
let dateFormatString = (localeDateFormat ?? "MM/dd/YY j:mm:ss:a")
|
||||
ScrollViewReader { scrollView in
|
||||
ScrollView {
|
||||
LazyVStack {
|
||||
|
|
@ -105,8 +107,9 @@ struct UserMessageList: View {
|
|||
}
|
||||
Menu("message.details") {
|
||||
VStack {
|
||||
|
||||
let messageDate = Date(timeIntervalSince1970: TimeInterval(message.messageTimestamp))
|
||||
Text("Date \(messageDate, style: .date) \(messageDate.formattedDate(format: "h:mm:ss a"))").font(.caption2).foregroundColor(.gray)
|
||||
Text("\(messageDate.formattedDate(format: dateFormatString))").foregroundColor(.gray)
|
||||
}
|
||||
if !currentUser {
|
||||
VStack {
|
||||
|
|
@ -116,20 +119,21 @@ struct UserMessageList: View {
|
|||
if currentUser && message.receivedACK {
|
||||
VStack {
|
||||
Text("received.ack")+Text(" \(message.receivedACK ? "✔️" : "")")
|
||||
Text("received.ack.real")+Text(" \(message.realACK ? "✔️" : "")")
|
||||
}
|
||||
} else if currentUser && message.ackError == 0 {
|
||||
// Empty Error
|
||||
Text("waiting")
|
||||
} else if currentUser && message.ackError > 0 {
|
||||
let ackErrorVal = RoutingError(rawValue: Int(message.ackError))
|
||||
Text("\(ackErrorVal?.display ?? "No Error" )").fixedSize(horizontal: false, vertical: true)
|
||||
Text("\(ackErrorVal?.display ?? "Empty Ack Error")").fixedSize(horizontal: false, vertical: true)
|
||||
}
|
||||
if currentUser {
|
||||
VStack {
|
||||
let ackDate = Date(timeIntervalSince1970: TimeInterval(message.ackTimestamp))
|
||||
let sixMonthsAgo = Calendar.current.date(byAdding: .month, value: -6, to: Date())
|
||||
if ackDate >= sixMonthsAgo! {
|
||||
Text((ackDate.formattedDate(format: "h:mm:ss a"))).font(.caption2).foregroundColor(.gray)
|
||||
Text("Ack Time: \(ackDate.formattedDate(format: "h:mm:ss a"))").foregroundColor(.gray)
|
||||
} else {
|
||||
Text("unknown.age").font(.caption2).foregroundColor(.gray)
|
||||
}
|
||||
|
|
@ -137,7 +141,7 @@ struct UserMessageList: View {
|
|||
}
|
||||
if message.ackSNR != 0 {
|
||||
VStack {
|
||||
Text("Ack SNR\(String(format: "%.2f", message.ackSNR)) dB")
|
||||
Text("Ack SNR: \(String(format: "%.2f", message.ackSNR)) dB")
|
||||
.font(.caption2)
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
|
|
@ -181,12 +185,12 @@ struct UserMessageList: View {
|
|||
let ackErrorVal = RoutingError(rawValue: Int(message.ackError))
|
||||
if currentUser && message.receivedACK {
|
||||
// Ack Received
|
||||
Text("\(ackErrorVal?.display ?? "No Error" )").font(.caption2).foregroundColor(.gray)
|
||||
Text("\(ackErrorVal?.display ?? "Empty Ack Error")").font(.caption2).foregroundColor(message.realACK ? .gray : .orange)
|
||||
} else if currentUser && message.ackError == 0 {
|
||||
// Empty Error
|
||||
Text("Waiting to be acknowledged. . .").font(.caption2).foregroundColor(.orange)
|
||||
} else if currentUser && message.ackError > 0 {
|
||||
Text("\(ackErrorVal?.display ?? "No Error" )").fixedSize(horizontal: false, vertical: true)
|
||||
Text("\(ackErrorVal?.display ?? "Empty Ack Error")").fixedSize(horizontal: false, vertical: true)
|
||||
.font(.caption2).foregroundColor(.red)
|
||||
}
|
||||
}
|
||||
|
|
@ -353,7 +357,7 @@ struct UserMessageList: View {
|
|||
ToolbarItem(placement: .principal) {
|
||||
HStack {
|
||||
CircleText(text: user.shortName ?? "???", color: .accentColor, circleSize: 44, fontSize: 14).fixedSize()
|
||||
Text(user.longName ?? "Unknown").font(.headline)
|
||||
Text(user.longName ?? NSLocalizedString("unknown", comment: "Unknown")).font(.headline)
|
||||
}
|
||||
}
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
|
|
|
|||
|
|
@ -5,9 +5,7 @@
|
|||
// Copyright(c) Garth Vander Houwen 7/7/22.
|
||||
//
|
||||
import SwiftUI
|
||||
#if canImport(Charts)
|
||||
import Charts
|
||||
#endif
|
||||
|
||||
struct DeviceMetricsLog: View {
|
||||
|
||||
|
|
@ -24,7 +22,7 @@ struct DeviceMetricsLog: View {
|
|||
let oneDayAgo = Calendar.current.date(byAdding: .day, value: -3, to: Date())
|
||||
let data = node.telemetries!.filtered(using: NSPredicate(format: "metricsType == 0 && time !=nil && time >= %@", oneDayAgo! as CVarArg))
|
||||
if data.count > 0 {
|
||||
GroupBox(label: Label("Battery Level Trend", systemImage: "battery.100")) {
|
||||
GroupBox(label: Label("battery.level.trend", systemImage: "battery.100")) {
|
||||
Chart(data.array as! [TelemetryEntity], id: \.self) {
|
||||
LineMark(
|
||||
x: .value("Hour", $0.time!.formattedDate(format: "ha")),
|
||||
|
|
@ -38,10 +36,13 @@ struct DeviceMetricsLog: View {
|
|||
.frame(height: 150)
|
||||
}
|
||||
}
|
||||
let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMddjmma", options: 0, locale: Locale.current)
|
||||
let dateFormatString = (localeDateFormat ?? "MM/dd/YY j:mma")
|
||||
if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac {
|
||||
//Add a table for mac and ipad
|
||||
Table(node.telemetries!.reversed() as! [TelemetryEntity]) {
|
||||
TableColumn("Battery Level") { dm in
|
||||
|
||||
TableColumn("battery.level") { dm in
|
||||
if dm.metricsType == 0 {
|
||||
if dm.batteryLevel == 0 {
|
||||
Text("Powered")
|
||||
|
|
@ -51,24 +52,24 @@ struct DeviceMetricsLog: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
TableColumn("Voltage") { dm in
|
||||
TableColumn("voltage") { dm in
|
||||
if dm.metricsType == 0 {
|
||||
Text("\(String(format: "%.2f", dm.voltage))")
|
||||
}
|
||||
}
|
||||
TableColumn("Channel Utilization") { dm in
|
||||
TableColumn("channel.utilization") { dm in
|
||||
if dm.metricsType == 0 {
|
||||
Text(String(format: "%.2f", dm.channelUtilization))
|
||||
}
|
||||
}
|
||||
TableColumn("Airtime") { dm in
|
||||
TableColumn("airtime") { dm in
|
||||
if dm.metricsType == 0 {
|
||||
Text("\(String(format: "%.2f", dm.airUtilTx))%")
|
||||
}
|
||||
}
|
||||
TableColumn("Time Stamp") { dm in
|
||||
TableColumn("timestamp") { dm in
|
||||
if dm.metricsType == 0 {
|
||||
Text(dm.time?.formattedDate(format: "MM/dd/yy hh:mm") ?? "Unknown time")
|
||||
Text(dm.time?.formattedDate(format: dateFormatString) ?? NSLocalizedString("unknown.age", comment: ""))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -81,14 +82,14 @@ struct DeviceMetricsLog: View {
|
|||
GridItem(),
|
||||
GridItem(),
|
||||
GridItem(),
|
||||
GridItem(.fixed(120))
|
||||
GridItem(.fixed(140))
|
||||
]
|
||||
LazyVGrid(columns: columns, alignment: .leading, spacing: 1) {
|
||||
GridRow {
|
||||
Text("Batt")
|
||||
.font(.caption)
|
||||
.fontWeight(.bold)
|
||||
Text("Voltage")
|
||||
Text("Volt")
|
||||
.font(.caption)
|
||||
.fontWeight(.bold)
|
||||
Text("ChUtil")
|
||||
|
|
@ -97,7 +98,7 @@ struct DeviceMetricsLog: View {
|
|||
Text("AirTm")
|
||||
.font(.caption)
|
||||
.fontWeight(.bold)
|
||||
Text("Timestamp")
|
||||
Text("timestamp")
|
||||
.font(.caption)
|
||||
.fontWeight(.bold)
|
||||
}
|
||||
|
|
@ -117,8 +118,9 @@ struct DeviceMetricsLog: View {
|
|||
.font(.caption)
|
||||
Text("\(String(format: "%.2f", dm.airUtilTx))%")
|
||||
.font(.caption)
|
||||
Text(dm.time?.formattedDate(format: "MM/dd/yy hh:mm") ?? "Unknown time")
|
||||
.font(.caption)
|
||||
|
||||
Text(dm.time?.formattedDate(format: dateFormatString) ?? NSLocalizedString("unknown.age", comment: ""))
|
||||
.font(.caption2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -132,7 +134,7 @@ struct DeviceMetricsLog: View {
|
|||
Button(role: .destructive) {
|
||||
isPresentingClearLogConfirm = true
|
||||
} label: {
|
||||
Label("Clear Log", systemImage: "trash.fill")
|
||||
Label("clear.log", systemImage: "trash.fill")
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
.buttonBorderShape(.capsule)
|
||||
|
|
@ -143,7 +145,7 @@ struct DeviceMetricsLog: View {
|
|||
isPresented: $isPresentingClearLogConfirm,
|
||||
titleVisibility: .visible
|
||||
) {
|
||||
Button("Delete all device metrics?", role: .destructive) {
|
||||
Button("device.metrics.delete", role: .destructive) {
|
||||
if clearTelemetry(destNum: node.num, metricsType: 0, context: context) {
|
||||
print("Cleared Device Metrics for \(node.num)")
|
||||
} else {
|
||||
|
|
@ -162,7 +164,7 @@ struct DeviceMetricsLog: View {
|
|||
.controlSize(.large)
|
||||
.padding()
|
||||
}
|
||||
.navigationTitle("Device Metrics Log")
|
||||
.navigationTitle("device.metrics.log")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.navigationBarItems(trailing:
|
||||
ZStack {
|
||||
|
|
@ -175,14 +177,14 @@ struct DeviceMetricsLog: View {
|
|||
isPresented: $isExporting,
|
||||
document: CsvDocument(emptyCsv: exportString),
|
||||
contentType: .commaSeparatedText,
|
||||
defaultFilename: String("\(node.user!.longName ?? "Node") Device Telemetry Log"),
|
||||
defaultFilename: String("\(node.user!.longName ?? "Node") \(NSLocalizedString("device.metrics.log", comment: "Device Metrics Log"))"),
|
||||
onCompletion: { result in
|
||||
if case .success = result {
|
||||
print("Device Telemetry log download succeeded.")
|
||||
print("Device metrics log download succeeded.")
|
||||
self.isExporting = false
|
||||
|
||||
} else {
|
||||
print("Device Telemetry log download failed: \(result).")
|
||||
print("Device metrics log download failed: \(result).")
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -21,7 +21,8 @@ struct EnvironmentMetricsLog: View {
|
|||
var body: some View {
|
||||
|
||||
NavigationStack {
|
||||
|
||||
let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMddjmma", options: 0, locale: Locale.current)
|
||||
let dateFormatString = (localeDateFormat ?? "MM/dd/YY j:mma")
|
||||
if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac {
|
||||
//Add a table for mac and ipad
|
||||
Table(node.telemetries!.reversed() as! [TelemetryEntity]) {
|
||||
|
|
@ -40,24 +41,24 @@ struct EnvironmentMetricsLog: View {
|
|||
Text("\(String(format: "%.2f", em.barometricPressure))")
|
||||
}
|
||||
}
|
||||
TableColumn("Gas Resistance") { em in
|
||||
TableColumn("gas.resistance") { em in
|
||||
if em.metricsType == 1 {
|
||||
Text("\(String(format: "%.2f", em.gasResistance))")
|
||||
}
|
||||
}
|
||||
TableColumn("Current") { em in
|
||||
TableColumn("current") { em in
|
||||
if em.metricsType == 1 {
|
||||
Text("\(String(format: "%.2f", em.current))")
|
||||
}
|
||||
}
|
||||
TableColumn("Voltage") { em in
|
||||
TableColumn("voltage") { em in
|
||||
if em.metricsType == 1 {
|
||||
Text("\(String(format: "%.2f", em.voltage))")
|
||||
}
|
||||
}
|
||||
TableColumn("Time Stamp") { em in
|
||||
TableColumn("timestamp") { em in
|
||||
if em.metricsType == 1 {
|
||||
Text(em.time?.formattedDate(format: "MM/dd/yy hh:mm") ?? "Unknown time")
|
||||
Text(em.time?.formattedDate(format: dateFormatString) ?? NSLocalizedString("unknown.age", comment: ""))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -68,12 +69,11 @@ struct EnvironmentMetricsLog: View {
|
|||
GridItem(),
|
||||
GridItem(),
|
||||
GridItem(),
|
||||
GridItem(.fixed(115))
|
||||
GridItem(.fixed(140))
|
||||
]
|
||||
LazyVGrid(columns: columns, alignment: .leading, spacing: 1) {
|
||||
|
||||
GridRow {
|
||||
|
||||
Text("Temp")
|
||||
.font(.caption)
|
||||
.fontWeight(.bold)
|
||||
|
|
@ -83,10 +83,10 @@ struct EnvironmentMetricsLog: View {
|
|||
Text("Bar")
|
||||
.font(.caption)
|
||||
.fontWeight(.bold)
|
||||
Text("Gas")
|
||||
Text("gas")
|
||||
.font(.caption)
|
||||
.fontWeight(.bold)
|
||||
Text("Timestamp")
|
||||
Text("timestamp")
|
||||
.font(.caption)
|
||||
.fontWeight(.bold)
|
||||
}
|
||||
|
|
@ -104,8 +104,8 @@ struct EnvironmentMetricsLog: View {
|
|||
.font(.caption)
|
||||
Text("\(String(format: "%.2f", em.gasResistance))")
|
||||
.font(.caption)
|
||||
Text(em.time?.formattedDate(format: "MM/dd/yy hh:mm") ?? "Unknown time")
|
||||
.font(.caption)
|
||||
Text(em.time?.formattedDate(format: dateFormatString) ?? NSLocalizedString("unknown.age", comment: ""))
|
||||
.font(.caption2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -118,11 +118,8 @@ struct EnvironmentMetricsLog: View {
|
|||
HStack {
|
||||
|
||||
Button(role: .destructive) {
|
||||
|
||||
isPresentingClearLogConfirm = true
|
||||
|
||||
} label: {
|
||||
|
||||
Label("Clear Log", systemImage: "trash.fill")
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
|
|
@ -135,22 +132,15 @@ struct EnvironmentMetricsLog: View {
|
|||
titleVisibility: .visible
|
||||
) {
|
||||
Button("Delete all environment metrics?", role: .destructive) {
|
||||
|
||||
if clearTelemetry(destNum: node.num, metricsType: 1, context: context) {
|
||||
|
||||
print("Clear Environment Metrics Log Failed")
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
|
||||
exportString = TelemetryToCsvFile(telemetry: node.telemetries!.array as! [TelemetryEntity], metricsType: 1)
|
||||
isExporting = true
|
||||
|
||||
} label: {
|
||||
|
||||
Label("save", systemImage: "square.and.arrow.down")
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
|
|
@ -161,13 +151,10 @@ struct EnvironmentMetricsLog: View {
|
|||
.navigationTitle("Environment Metrics Log")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.navigationBarItems(trailing:
|
||||
|
||||
ZStack {
|
||||
|
||||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????")
|
||||
})
|
||||
.onAppear {
|
||||
|
||||
self.bleManager.context = context
|
||||
}
|
||||
.fileExporter(
|
||||
|
|
@ -176,15 +163,10 @@ struct EnvironmentMetricsLog: View {
|
|||
contentType: .commaSeparatedText,
|
||||
defaultFilename: String("\(node.user!.longName ?? "Node") Environment Metrics Log"),
|
||||
onCompletion: { result in
|
||||
|
||||
if case .success = result {
|
||||
|
||||
print("Environment metrics log download succeeded.")
|
||||
|
||||
self.isExporting = false
|
||||
|
||||
} else {
|
||||
|
||||
print("Environment metrics log download failed: \(result).")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -192,28 +192,21 @@ struct NodeDetail: View {
|
|||
HStack {
|
||||
|
||||
VStack(alignment: .center) {
|
||||
|
||||
CircleText(text: node.user?.shortName ?? "???", color: .accentColor)
|
||||
}
|
||||
|
||||
Divider()
|
||||
|
||||
VStack {
|
||||
|
||||
if node.user != nil {
|
||||
|
||||
Image(node.user!.hwModel ?? "UNSET")
|
||||
Image(node.user!.hwModel ?? NSLocalizedString("unset", comment: "Unset"))
|
||||
.resizable()
|
||||
.frame(width: 75, height: 75)
|
||||
.cornerRadius(5)
|
||||
|
||||
Text(String(node.user!.hwModel ?? "UNSET"))
|
||||
Text(String(node.user!.hwModel ?? NSLocalizedString("unset", comment: "Unset")))
|
||||
.font(.callout).fixedSize()
|
||||
}
|
||||
}
|
||||
.padding(5)
|
||||
|
||||
|
||||
if node.snr > 0 {
|
||||
Divider()
|
||||
VStack(alignment: .center) {
|
||||
|
|
@ -362,9 +355,7 @@ struct NodeDetail: View {
|
|||
isPresented: $showingShutdownConfirm
|
||||
) {
|
||||
Button("Shutdown Node?", role: .destructive) {
|
||||
|
||||
if !bleManager.sendShutdown(destNum: node.num) {
|
||||
|
||||
if !bleManager.sendShutdown(fromUser: node.user!, toUser: node.user!) {
|
||||
print("Shutdown Failed")
|
||||
}
|
||||
}
|
||||
|
|
@ -377,26 +368,23 @@ struct NodeDetail: View {
|
|||
|
||||
}) {
|
||||
|
||||
Label("Reboot", systemImage: "arrow.triangle.2.circlepath")
|
||||
Label("reboot", systemImage: "arrow.triangle.2.circlepath")
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
.buttonBorderShape(.capsule)
|
||||
.controlSize(.large)
|
||||
.padding()
|
||||
.confirmationDialog(
|
||||
|
||||
"are.you.sure",
|
||||
isPresented: $showingRebootConfirm
|
||||
) {
|
||||
|
||||
Button("Reboot Node?", role: .destructive) {
|
||||
|
||||
if !bleManager.sendReboot(destNum: node.num) {
|
||||
|
||||
print("Reboot Failed")
|
||||
.confirmationDialog("are.you.sure",
|
||||
|
||||
isPresented: $showingRebootConfirm
|
||||
) {
|
||||
Button("reboot.node", role: .destructive) {
|
||||
|
||||
if !bleManager.sendReboot(fromUser: node.user!, toUser: node.user!) {
|
||||
print("Reboot Failed")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(5)
|
||||
}
|
||||
|
|
@ -404,12 +392,10 @@ struct NodeDetail: View {
|
|||
.offset( y:-40)
|
||||
}
|
||||
.edgesIgnoringSafeArea([.leading, .trailing])
|
||||
.navigationBarTitle((node.user != nil) ? String(node.user!.longName ?? "Unknown") : "Unknown", displayMode: .inline)
|
||||
.navigationBarTitle(String(node.user?.longName ?? NSLocalizedString("unknown", comment: "")), displayMode: .inline)
|
||||
.padding(.bottom, 10)
|
||||
.navigationBarItems(trailing:
|
||||
|
||||
ZStack {
|
||||
|
||||
ConnectedDevice(
|
||||
bluetoothOn: bleManager.isSwitchedOn,
|
||||
deviceConnected: bleManager.connectedPeripheral != nil,
|
||||
|
|
@ -423,15 +409,3 @@ struct NodeDetail: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct NodeInfoEntityDetail_Previews: PreviewProvider {
|
||||
|
||||
static let bleManager = BLEManager()
|
||||
|
||||
static var previews: some View {
|
||||
Group {
|
||||
|
||||
// NodeDetail(node: node)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ struct NodeList: View {
|
|||
CircleText(text: node.user?.shortName ?? "???", color: .accentColor, circleSize: 52, fontSize: 16, brightness: 0.1)
|
||||
.padding(.trailing, 5)
|
||||
VStack(alignment: .leading) {
|
||||
Text(node.user?.longName ?? "Unknown").font(.headline)
|
||||
Text(node.user?.longName ?? NSLocalizedString("unknown", comment: "Unknown")).font(.headline)
|
||||
if connected {
|
||||
HStack(alignment: .bottom) {
|
||||
Image(systemName: "repeat.circle.fill")
|
||||
|
|
@ -53,7 +53,7 @@ struct NodeList: View {
|
|||
HStack(alignment: .bottom) {
|
||||
let lastPostion = node.positions!.reversed()[0] as! PositionEntity
|
||||
let myCoord = CLLocation(latitude: LocationHelper.currentLocation.latitude, longitude: LocationHelper.currentLocation.longitude)
|
||||
if lastPostion.coordinate != nil {
|
||||
if lastPostion.coordinate != nil && myCoord.coordinate.longitude != LocationHelper.DefaultLocation.longitude && myCoord.coordinate.latitude != LocationHelper.DefaultLocation.latitude {
|
||||
let nodeCoord = CLLocation(latitude: lastPostion.coordinate!.latitude, longitude: lastPostion.coordinate!.longitude)
|
||||
let metersAway = nodeCoord.distance(from: myCoord)
|
||||
Image(systemName: "lines.measurement.horizontal")
|
||||
|
|
|
|||
|
|
@ -84,8 +84,6 @@ struct NodeMap: View {
|
|||
|
||||
var body: some View {
|
||||
|
||||
//self.$userLocation = LocationHelper.currentLocation
|
||||
|
||||
NavigationStack {
|
||||
ZStack {
|
||||
|
||||
|
|
@ -112,11 +110,9 @@ struct NodeMap: View {
|
|||
)
|
||||
|
||||
.frame(maxHeight: .infinity)
|
||||
.ignoresSafeArea(.all, edges: [.leading, .trailing])
|
||||
}
|
||||
.ignoresSafeArea(.all, edges: [.top, .leading, .trailing])
|
||||
}
|
||||
}
|
||||
.navigationTitle("Mesh Map")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
|
||||
.navigationBarItems(leading:
|
||||
MeshtasticLogo(), trailing:
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ struct PositionLog: View {
|
|||
var body: some View {
|
||||
|
||||
NavigationStack {
|
||||
let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMddjmma", options: 0, locale: Locale.current)
|
||||
let dateFormatString = (localeDateFormat ?? "MM/dd/YY j:mma")
|
||||
|
||||
if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac {
|
||||
//Add a table for mac and ipad
|
||||
|
|
@ -50,7 +52,7 @@ struct PositionLog: View {
|
|||
Text("\(String(format: "%.2f", position.snr)) dB")
|
||||
}
|
||||
TableColumn("Time Stamp") { position in
|
||||
Text(position.time?.formattedDate(format: "MM/dd/yy hh:mm") ?? "Unknown time")
|
||||
Text(position.time?.formattedDate(format: dateFormatString) ?? NSLocalizedString("unknown.age", comment: ""))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -59,11 +61,11 @@ struct PositionLog: View {
|
|||
ScrollView {
|
||||
// Use a grid on iOS as a table only shows a single column
|
||||
let columns = [
|
||||
GridItem(.fixed(90)),
|
||||
GridItem(.fixed(95)),
|
||||
GridItem(.fixed(95)),
|
||||
GridItem(),
|
||||
GridItem(),
|
||||
GridItem(.fixed(115))
|
||||
GridItem(.fixed(45)),
|
||||
GridItem(.fixed(40)),
|
||||
GridItem(.fixed(140))
|
||||
]
|
||||
LazyVGrid(columns: columns, alignment: .leading, spacing: 1) {
|
||||
|
||||
|
|
@ -81,7 +83,7 @@ struct PositionLog: View {
|
|||
Text("Alt")
|
||||
.font(.caption2)
|
||||
.fontWeight(.bold)
|
||||
Text("Timestamp")
|
||||
Text("timestamp")
|
||||
.font(.caption2)
|
||||
.fontWeight(.bold)
|
||||
}
|
||||
|
|
@ -95,7 +97,7 @@ struct PositionLog: View {
|
|||
.font(.caption2)
|
||||
Text(String(mappin.altitude))
|
||||
.font(.caption2)
|
||||
Text(mappin.time?.formattedDate(format: "MM/dd/yy hh:mm") ?? "Unknown time")
|
||||
Text(mappin.time?.formattedDate(format: dateFormatString) ?? "Unknown time")
|
||||
.font(.caption2)
|
||||
}
|
||||
}
|
||||
|
|
@ -125,9 +127,7 @@ struct PositionLog: View {
|
|||
titleVisibility: .visible
|
||||
) {
|
||||
Button("Delete all positions?", role: .destructive) {
|
||||
|
||||
if clearPositions(destNum: node.num, context: context) {
|
||||
|
||||
print("Successfully Cleared Position Log")
|
||||
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ struct AboutMeshtastic: View {
|
|||
Link("Documentation", destination: URL(string: "https://meshtastic.org/docs/getting-started")!)
|
||||
.font(.title2)
|
||||
}
|
||||
Text("Meshtastic Copyright(c) Meshtastic LLC")
|
||||
Text("Meshtastic® Copyright Meshtastic LLC")
|
||||
.font(.caption)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,8 @@ struct AdminMessageList: View {
|
|||
var user: UserEntity?
|
||||
|
||||
var body: some View {
|
||||
|
||||
let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMddjmmssa", options: 0, locale: Locale.current)
|
||||
let dateFormatString = (localeDateFormat ?? "MM/dd/YY j:mm:ss a")
|
||||
List {
|
||||
if user != nil {
|
||||
|
||||
|
|
@ -29,7 +30,7 @@ struct AdminMessageList: View {
|
|||
|
||||
HStack {
|
||||
|
||||
Text("\(am.adminDescription ?? "Unknown") - \(Date(timeIntervalSince1970: TimeInterval(am.messageTimestamp)), style: .date) \(Date(timeIntervalSince1970: TimeInterval(am.messageTimestamp)).formattedDate(format: "h:mm:ss a"))")
|
||||
Text("\(am.adminDescription ?? NSLocalizedString("unknown", comment: "Unknown")) - \(Date(timeIntervalSince1970: TimeInterval(am.messageTimestamp)).formattedDate(format: dateFormatString))")
|
||||
.font(.caption)
|
||||
|
||||
if am.receivedACK {
|
||||
|
|
@ -37,15 +38,16 @@ struct AdminMessageList: View {
|
|||
Image(systemName: "checkmark.square")
|
||||
.foregroundColor(.gray)
|
||||
.font(.caption)
|
||||
Text("Acknowledged: \(Date(timeIntervalSince1970: TimeInterval(am.ackTimestamp)).formattedDate(format: "h:mm:ss a"))")
|
||||
Text("routing.acknowledged").foregroundColor(.gray).font(.caption) + Text(": \(Date(timeIntervalSince1970: TimeInterval(am.ackTimestamp)).formattedDate(format: "h:mm:ss a"))")
|
||||
.foregroundColor(.gray)
|
||||
.font(.caption)
|
||||
|
||||
} else {
|
||||
let ackErrorVal = RoutingError(rawValue: Int(am.ackError))
|
||||
Image(systemName: "square")
|
||||
.foregroundColor(.gray)
|
||||
.font(.caption)
|
||||
Text("Not Acknowledged")
|
||||
Text(ackErrorVal?.display ?? "Empty Ack Error")
|
||||
.foregroundColor(.gray)
|
||||
.font(.caption)
|
||||
}
|
||||
|
|
@ -53,15 +55,12 @@ struct AdminMessageList: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("Admin Message Log")
|
||||
.navigationTitle("admin.log")
|
||||
.navigationBarItems(trailing:
|
||||
|
||||
ZStack {
|
||||
|
||||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????")
|
||||
})
|
||||
.onAppear {
|
||||
|
||||
self.bleManager.context = context
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,91 +4,6 @@ import SwiftUI
|
|||
import SwiftProtobuf
|
||||
import MapKit
|
||||
|
||||
enum KeyboardType: Int, CaseIterable, Identifiable {
|
||||
|
||||
case defaultKeyboard = 0
|
||||
case asciiCapable = 1
|
||||
case twitter = 9
|
||||
case emailAddress = 7
|
||||
case numbersAndPunctuation = 2
|
||||
|
||||
var id: Int { self.rawValue }
|
||||
var description: String {
|
||||
get {
|
||||
switch self {
|
||||
case .defaultKeyboard:
|
||||
return NSLocalizedString("default", comment: "Default Keyboard")
|
||||
case .asciiCapable:
|
||||
return NSLocalizedString("ascii.capable", comment: "ASCII Capable Keyboard")
|
||||
case .twitter:
|
||||
return NSLocalizedString("twitter", comment: "Twitter Keyboard")
|
||||
case .emailAddress:
|
||||
return NSLocalizedString("email.address", comment: "Email Address Keyboard")
|
||||
case .numbersAndPunctuation:
|
||||
return NSLocalizedString("numbers.punctuation", comment: "Numbers and Punctuation Keyboard")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum MeshMapType: String, CaseIterable, Identifiable {
|
||||
|
||||
case satellite = "satellite"
|
||||
case hybrid = "hybrid"
|
||||
case standard = "standard"
|
||||
|
||||
var id: String { self.rawValue }
|
||||
|
||||
var description: String {
|
||||
get {
|
||||
switch self {
|
||||
case .satellite:
|
||||
return NSLocalizedString("satellite", comment: "Satellite Map Type")
|
||||
case .standard:
|
||||
return NSLocalizedString("standard", comment: "Standard Map Type")
|
||||
case .hybrid:
|
||||
return NSLocalizedString("hybrid", comment: "Hybrid Map Type")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum LocationUpdateInterval: Int, CaseIterable, Identifiable {
|
||||
|
||||
case fiveSeconds = 5
|
||||
case tenSeconds = 10
|
||||
case fifteenSeconds = 15
|
||||
case thirtySeconds = 30
|
||||
case oneMinute = 60
|
||||
case fiveMinutes = 300
|
||||
case tenMinutes = 600
|
||||
case fifteenMinutes = 900
|
||||
|
||||
var id: Int { self.rawValue }
|
||||
var description: String {
|
||||
get {
|
||||
switch self {
|
||||
case .fiveSeconds:
|
||||
return NSLocalizedString("interval.five.seconds", comment: "Five Seconds")
|
||||
case .tenSeconds:
|
||||
return NSLocalizedString("interval.ten.seconds", comment: "Ten Seconds")
|
||||
case .fifteenSeconds:
|
||||
return NSLocalizedString("interval.fifteen.seconds", comment: "Fifteen Seconds")
|
||||
case .thirtySeconds:
|
||||
return NSLocalizedString("interval.thirty.seconds", comment: "Thirty Seconds")
|
||||
case .oneMinute:
|
||||
return NSLocalizedString("interval.one.minute", comment: "One Minute")
|
||||
case .fiveMinutes:
|
||||
return NSLocalizedString("interval.five.minutes", comment: "Five Minutes")
|
||||
case .tenMinutes:
|
||||
return NSLocalizedString("interval.ten.minutes", comment: "Ten Minutes")
|
||||
case .fifteenMinutes:
|
||||
return NSLocalizedString("interval.fifteen.minutes", comment: "Fifteen Minutes")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct AppSettings: View {
|
||||
|
||||
@Environment(\.managedObjectContext) var context
|
||||
|
|
|
|||
|
|
@ -1,288 +1,297 @@
|
|||
////
|
||||
//// ShareChannel.swift
|
||||
//// MeshtasticApple
|
||||
////
|
||||
//// Copyright(c) Garth Vander Houwen 4/8/22.
|
||||
////
|
||||
//import SwiftUI
|
||||
//import CoreData
|
||||
//
|
||||
//func generateChannelKey(size: Int) -> String {
|
||||
// var keyData = Data(count: size)
|
||||
// _ = keyData.withUnsafeMutableBytes {
|
||||
// SecRandomCopyBytes(kSecRandomDefault, size, $0.baseAddress!)
|
||||
// }
|
||||
// return keyData.base64EncodedString()
|
||||
//}
|
||||
// ShareChannel.swift
|
||||
// MeshtasticApple
|
||||
//
|
||||
//struct Channels: View {
|
||||
//
|
||||
// @Environment(\.managedObjectContext) var context
|
||||
// @EnvironmentObject var bleManager: BLEManager
|
||||
// @Environment(\.dismiss) private var goBack
|
||||
// @Environment(\.sizeCategory) var sizeCategory
|
||||
// Copyright(c) Garth Vander Houwen 4/8/22.
|
||||
//
|
||||
//
|
||||
// var node: NodeInfoEntity?
|
||||
//
|
||||
// @State var hasChanges = false
|
||||
// @State private var isPresentingEditView = false
|
||||
// @State private var isPresentingSaveConfirm: Bool = false
|
||||
// @State private var channelIndex: Int32 = 0
|
||||
// @State private var channelName = ""
|
||||
// @State private var channelKeySize = 32
|
||||
// @State private var channelKey = "AQ=="
|
||||
// @State private var channelRole = 0
|
||||
// @State private var uplink = false
|
||||
// @State private var downlink = false
|
||||
//
|
||||
// var body: some View {
|
||||
//
|
||||
// NavigationStack {
|
||||
// List {
|
||||
// if node != nil && node?.myInfo != nil {
|
||||
// ForEach(node!.myInfo!.channels?.array as! [ChannelEntity], id: \.self) { (channel: ChannelEntity) in
|
||||
// Button(action: {
|
||||
// channelIndex = channel.index
|
||||
// channelRole = Int(channel.role)
|
||||
// channelKey = channel.psk?.base64EncodedString() ?? ""
|
||||
// if channelKey.count == 0 {
|
||||
// channelKeySize = 0
|
||||
// } else if channelKey == "AQ==" {
|
||||
// channelKeySize = -1
|
||||
// } else if channelKey.count == 24 {
|
||||
// channelKeySize = 16
|
||||
// } else if channelKey.count == 32 {
|
||||
// channelKeySize = 24
|
||||
// } else if channelKey.count == 44 {
|
||||
// channelKeySize = 32
|
||||
// }
|
||||
// channelName = channel.name ?? ""
|
||||
// uplink = channel.uplinkEnabled
|
||||
// downlink = channel.downlinkEnabled
|
||||
// isPresentingEditView = true
|
||||
// hasChanges = false
|
||||
// }) {
|
||||
// VStack(alignment: .leading) {
|
||||
// HStack {
|
||||
// CircleText(text: String(channel.index), color: .accentColor, circleSize: 45, fontSize: 36, brightness: 0.1)
|
||||
// .padding(.trailing, 5)
|
||||
// VStack {
|
||||
// HStack {
|
||||
// if channel.name?.isEmpty ?? false {
|
||||
// if channel.role == 1 {
|
||||
// Text(String("PrimaryChannel").camelCaseToWords()).font(.headline)
|
||||
// } else {
|
||||
// Text(String("Channel \(channel.index)").camelCaseToWords()).font(.headline)
|
||||
// }
|
||||
// } else {
|
||||
// Text(String(channel.name ?? "Channel \(channel.index)").camelCaseToWords()).font(.headline)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// if node?.myInfo?.channels?.array.count ?? 0 < 8 {
|
||||
//
|
||||
// Button {
|
||||
// let key = generateChannelKey(size: 32)
|
||||
// channelName = ""
|
||||
// channelIndex = Int32(node!.myInfo!.channels!.array.count)
|
||||
// channelRole = 2
|
||||
// channelKey = key
|
||||
// uplink = false
|
||||
// downlink = false
|
||||
// hasChanges = false
|
||||
// isPresentingEditView = true
|
||||
//
|
||||
// } label: {
|
||||
// Label("Add Channel", systemImage: "plus.square")
|
||||
// }
|
||||
// .buttonStyle(.bordered)
|
||||
// .buttonBorderShape(.capsule)
|
||||
// .controlSize(.large)
|
||||
// .padding()
|
||||
// .sheet(isPresented: $isPresentingEditView) {
|
||||
//
|
||||
// #if targetEnvironment(macCatalyst)
|
||||
// Text("channel")
|
||||
// .font(.largeTitle)
|
||||
// .padding()
|
||||
// #endif
|
||||
// Form {
|
||||
// HStack {
|
||||
// Text("name")
|
||||
// Spacer()
|
||||
// TextField(
|
||||
// "Channel Name",
|
||||
// text: $channelName
|
||||
// )
|
||||
// .disableAutocorrection(true)
|
||||
// .keyboardType(.alphabet)
|
||||
// .foregroundColor(Color.gray)
|
||||
// .disabled(channelRole == 1 && channelName.count > 0)
|
||||
// .onChange(of: channelName, perform: { value 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 bit").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: .top) {
|
||||
// Text("Key")
|
||||
// Spacer()
|
||||
// TextField (
|
||||
// "",
|
||||
// text: $channelKey,
|
||||
// axis: .vertical
|
||||
// )
|
||||
// .foregroundColor(Color.gray)
|
||||
// .disabled(true)
|
||||
//
|
||||
// }
|
||||
// .textSelection(.enabled)
|
||||
// Picker("Channel Role", selection: $channelRole) {
|
||||
// if channelRole == 1 {
|
||||
// Text("Primary").tag(1)
|
||||
// } else{
|
||||
// Text("Disabled").tag(0)
|
||||
// Text("Secondary").tag(2)
|
||||
// }
|
||||
// }
|
||||
// .pickerStyle(DefaultPickerStyle())
|
||||
// .disabled(channelRole == 1)
|
||||
// Toggle("Uplink Enabled", isOn: $uplink)
|
||||
// .toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
// Toggle("Downlink Enabled", isOn: $downlink)
|
||||
// .toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
// }
|
||||
// .onSubmit {
|
||||
// //validate(name: channelName)
|
||||
// }
|
||||
// .onChange(of: channelName) { newName in
|
||||
// hasChanges = true
|
||||
// }
|
||||
// .onChange(of: channelKeySize) { newKeySize in
|
||||
// if channelKeySize == -1 {
|
||||
// channelKey = "AQ=="
|
||||
// } else {
|
||||
// let key = generateChannelKey(size: channelKeySize)
|
||||
// channelKey = key
|
||||
// }
|
||||
// hasChanges = true
|
||||
// }
|
||||
// .onChange(of: channelKey) { newKey in
|
||||
// hasChanges = true
|
||||
// }
|
||||
// .onChange(of: channelRole) { newRole in
|
||||
// hasChanges = true
|
||||
// }
|
||||
// .onChange(of: uplink) { newUplink in
|
||||
// hasChanges = true
|
||||
// }
|
||||
// .onChange(of: downlink) { newDownlink in
|
||||
// hasChanges = true
|
||||
// }
|
||||
// HStack {
|
||||
// Button {
|
||||
// isPresentingSaveConfirm = true
|
||||
// } label: {
|
||||
// Label("save", systemImage: "square.and.arrow.down")
|
||||
// }
|
||||
// .disabled(bleManager.connectedPeripheral == nil || !hasChanges)
|
||||
// .buttonStyle(.bordered)
|
||||
// .buttonBorderShape(.capsule)
|
||||
// .controlSize(.large)
|
||||
// .padding(.bottom)
|
||||
// .confirmationDialog(
|
||||
// "are.you.sure",
|
||||
// isPresented: $isPresentingSaveConfirm,
|
||||
// titleVisibility: .visible
|
||||
// ) {
|
||||
// Button("Save Channel \(channelIndex) to \(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown")?") {
|
||||
//
|
||||
// var channel = Channel()
|
||||
// channel.index = channelIndex
|
||||
// channel.settings.id = UInt32(channelIndex)
|
||||
// channel.settings.name = channelName
|
||||
// channel.settings.psk = Data(base64Encoded: channelKey) ?? Data()
|
||||
// channel.role = ChannelRoles(rawValue: channelRole)?.protoEnumValue() ?? .secondary
|
||||
// channel.settings.uplinkEnabled = uplink
|
||||
// channel.settings.downlinkEnabled = downlink
|
||||
//
|
||||
// let adminMessageId = bleManager.saveChannel(channel: channel, fromUser: node!.user!, toUser: node!.user!)
|
||||
//
|
||||
// if adminMessageId > 0 {
|
||||
// // Should show a saved successfully alert once I know that to be true
|
||||
// // for now just disable the button after a successful save
|
||||
// channelName = ""
|
||||
// hasChanges = false
|
||||
// isPresentingEditView = false
|
||||
// bleManager.disconnectPeripheral()
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// #if targetEnvironment(macCatalyst)
|
||||
// Button {
|
||||
// isPresentingEditView = false
|
||||
// } label: {
|
||||
// Label("Close", systemImage: "xmark")
|
||||
// }
|
||||
// .buttonStyle(.bordered)
|
||||
// .buttonBorderShape(.capsule)
|
||||
// .controlSize(.large)
|
||||
// .padding(.bottom)
|
||||
// #endif
|
||||
// }
|
||||
// .presentationDetents([.medium, .large])
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// .navigationTitle("channels")
|
||||
// .navigationSplitViewStyle(.automatic)
|
||||
// .navigationBarItems(trailing:
|
||||
// ZStack {
|
||||
// ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????")
|
||||
// })
|
||||
// .onAppear {
|
||||
// bleManager.context = context
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
import SwiftUI
|
||||
import CoreData
|
||||
|
||||
func generateChannelKey(size: Int) -> String {
|
||||
var keyData = Data(count: size)
|
||||
_ = keyData.withUnsafeMutableBytes {
|
||||
SecRandomCopyBytes(kSecRandomDefault, size, $0.baseAddress!)
|
||||
}
|
||||
return keyData.base64EncodedString()
|
||||
}
|
||||
|
||||
struct Channels: View {
|
||||
|
||||
@Environment(\.managedObjectContext) var context
|
||||
@EnvironmentObject var bleManager: BLEManager
|
||||
@Environment(\.dismiss) private var goBack
|
||||
@Environment(\.sizeCategory) var sizeCategory
|
||||
|
||||
|
||||
var node: NodeInfoEntity?
|
||||
|
||||
@State var hasChanges = false
|
||||
@State private var isPresentingEditView = false
|
||||
@State private var isPresentingSaveConfirm: Bool = false
|
||||
@State private var channelIndex: Int32 = 0
|
||||
@State private var channelName = ""
|
||||
@State private var channelKeySize = 32
|
||||
@State private var channelKey = "AQ=="
|
||||
@State private var channelRole = 0
|
||||
@State private var uplink = false
|
||||
@State private var downlink = false
|
||||
|
||||
var body: some View {
|
||||
|
||||
NavigationStack {
|
||||
List {
|
||||
if node != nil && node?.myInfo != nil {
|
||||
ForEach(node!.myInfo!.channels?.array as! [ChannelEntity], id: \.self) { (channel: ChannelEntity) in
|
||||
Button(action: {
|
||||
channelIndex = channel.index
|
||||
channelRole = Int(channel.role)
|
||||
channelKey = channel.psk?.base64EncodedString() ?? ""
|
||||
if channelKey.count == 0 {
|
||||
channelKeySize = 0
|
||||
} else if channelKey == "AQ==" {
|
||||
channelKeySize = -1
|
||||
} else if channelKey.count == 24 {
|
||||
channelKeySize = 16
|
||||
} else if channelKey.count == 32 {
|
||||
channelKeySize = 24
|
||||
} else if channelKey.count == 44 {
|
||||
channelKeySize = 32
|
||||
}
|
||||
channelName = channel.name ?? ""
|
||||
uplink = channel.uplinkEnabled
|
||||
downlink = channel.downlinkEnabled
|
||||
isPresentingEditView = true
|
||||
hasChanges = false
|
||||
}) {
|
||||
VStack(alignment: .leading) {
|
||||
HStack {
|
||||
CircleText(text: String(channel.index), color: .accentColor, circleSize: 45, fontSize: 36, brightness: 0.1)
|
||||
.padding(.trailing, 5)
|
||||
VStack {
|
||||
HStack {
|
||||
if channel.name?.isEmpty ?? false {
|
||||
if channel.role == 1 {
|
||||
Text(String("PrimaryChannel").camelCaseToWords()).font(.headline)
|
||||
} else {
|
||||
Text(String("Channel \(channel.index)").camelCaseToWords()).font(.headline)
|
||||
}
|
||||
} else {
|
||||
Text(String(channel.name ?? "Channel \(channel.index)").camelCaseToWords()).font(.headline)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if node?.myInfo?.channels?.array.count ?? 0 < 8 && node != nil {
|
||||
|
||||
Button {
|
||||
let key = generateChannelKey(size: 32)
|
||||
channelName = ""
|
||||
channelIndex = Int32(node!.myInfo!.channels!.array.count)
|
||||
channelRole = 2
|
||||
channelKey = key
|
||||
uplink = false
|
||||
downlink = false
|
||||
hasChanges = false
|
||||
isPresentingEditView = true
|
||||
|
||||
} label: {
|
||||
Label("Add Channel", systemImage: "plus.square")
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
.buttonBorderShape(.capsule)
|
||||
.controlSize(.large)
|
||||
.padding()
|
||||
.sheet(isPresented: $isPresentingEditView) {
|
||||
|
||||
#if targetEnvironment(macCatalyst)
|
||||
Text("channel")
|
||||
.font(.largeTitle)
|
||||
.padding()
|
||||
#endif
|
||||
Form {
|
||||
HStack {
|
||||
Text("name")
|
||||
Spacer()
|
||||
TextField(
|
||||
"Channel Name",
|
||||
text: $channelName
|
||||
)
|
||||
.disableAutocorrection(true)
|
||||
.keyboardType(.alphabet)
|
||||
.foregroundColor(Color.gray)
|
||||
.disabled(channelRole == 1 && channelName.count > 0)
|
||||
.onChange(of: channelName, perform: { value 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 bit").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: .top) {
|
||||
Text("Key")
|
||||
Spacer()
|
||||
TextField (
|
||||
"",
|
||||
text: $channelKey,
|
||||
axis: .vertical
|
||||
)
|
||||
.foregroundColor(Color.gray)
|
||||
.disabled(true)
|
||||
|
||||
}
|
||||
.textSelection(.enabled)
|
||||
Picker("Channel Role", selection: $channelRole) {
|
||||
if channelRole == 1 {
|
||||
Text("Primary").tag(1)
|
||||
} else{
|
||||
Text("Disabled").tag(0)
|
||||
Text("Secondary").tag(2)
|
||||
}
|
||||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
.disabled(channelRole == 1)
|
||||
Toggle("Uplink Enabled", isOn: $uplink)
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
Toggle("Downlink Enabled", isOn: $downlink)
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
}
|
||||
//.onSubmit {
|
||||
//validate(name: channelName)
|
||||
//}
|
||||
.onChange(of: channelName) { newName in
|
||||
hasChanges = true
|
||||
}
|
||||
.onChange(of: channelKeySize) { newKeySize in
|
||||
if channelKeySize == -1 {
|
||||
channelKey = "AQ=="
|
||||
} else {
|
||||
let key = generateChannelKey(size: channelKeySize)
|
||||
channelKey = key
|
||||
}
|
||||
hasChanges = true
|
||||
}
|
||||
.onChange(of: channelKey) { newKey in
|
||||
hasChanges = true
|
||||
}
|
||||
.onChange(of: channelRole) { newRole in
|
||||
hasChanges = true
|
||||
}
|
||||
.onChange(of: uplink) { newUplink in
|
||||
hasChanges = true
|
||||
}
|
||||
.onChange(of: downlink) { newDownlink in
|
||||
hasChanges = true
|
||||
}
|
||||
HStack {
|
||||
Button {
|
||||
var channel = Channel()
|
||||
channel.index = channelIndex
|
||||
channel.role = ChannelRoles(rawValue: channelRole)?.protoEnumValue() ?? .secondary
|
||||
if channel.role != Channel.Role.disabled {
|
||||
channel.settings.id = UInt32(channelIndex)
|
||||
channel.settings.name = channelName
|
||||
channel.settings.psk = Data(base64Encoded: channelKey) ?? Data()
|
||||
channel.settings.uplinkEnabled = uplink
|
||||
channel.settings.downlinkEnabled = downlink
|
||||
|
||||
} else {
|
||||
if channelIndex <= node!.myInfo!.channels?.count ?? 0 {
|
||||
let channelEntity = node!.myInfo!.channels?[Int(channelIndex)] as! ChannelEntity
|
||||
context.delete(channelEntity)
|
||||
do {
|
||||
try context.save()
|
||||
print("💾 Deleted Channel: \(channel.settings.name)")
|
||||
} catch {
|
||||
context.rollback()
|
||||
let nsError = error as NSError
|
||||
print("💥 Unresolved Core Data error in the channel editor. Error: \(nsError)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let adminMessageId = bleManager.saveChannel(channel: channel, fromUser: node!.user!, toUser: node!.user!)
|
||||
|
||||
if adminMessageId > 0 {
|
||||
|
||||
// Should show a saved successfully alert once I know that to be true
|
||||
// for now just disable the button after a successful save.
|
||||
|
||||
self.isPresentingEditView = false
|
||||
channelName = ""
|
||||
hasChanges = false
|
||||
// Would rather send a getChannel but I can't seem serialize it properly yet
|
||||
bleManager.getChannel(channel: channel, fromUser: node!.user!, toUser: node!.user!)
|
||||
//bleManager.sendWantConfig()
|
||||
}
|
||||
} label: {
|
||||
Label("save", systemImage: "square.and.arrow.down")
|
||||
}
|
||||
.disabled(bleManager.connectedPeripheral == nil || !hasChanges)
|
||||
.buttonStyle(.bordered)
|
||||
.buttonBorderShape(.capsule)
|
||||
.controlSize(.large)
|
||||
.padding(.bottom)
|
||||
#if targetEnvironment(macCatalyst)
|
||||
Button {
|
||||
isPresentingEditView = false
|
||||
} label: {
|
||||
Label("close", systemImage: "xmark")
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
.buttonBorderShape(.capsule)
|
||||
.controlSize(.large)
|
||||
.padding(.bottom)
|
||||
#endif
|
||||
}
|
||||
.presentationDetents([.medium, .large])
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("channels")
|
||||
.navigationBarItems(trailing:
|
||||
ZStack {
|
||||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????")
|
||||
})
|
||||
.onAppear {
|
||||
bleManager.context = context
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,89 +33,87 @@ struct BluetoothConfig: View {
|
|||
|
||||
var body: some View {
|
||||
|
||||
VStack {
|
||||
|
||||
Form {
|
||||
|
||||
Section(header: Text("options")) {
|
||||
|
||||
Toggle(isOn: $enabled) {
|
||||
|
||||
Label("enabled", systemImage: "antenna.radiowaves.left.and.right")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
|
||||
|
||||
Picker("Pairing Mode", selection: $mode ) {
|
||||
ForEach(BluetoothModes.allCases) { bm in
|
||||
Text(bm.description)
|
||||
}
|
||||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
|
||||
if mode == 1 {
|
||||
|
||||
HStack {
|
||||
Label("Fixed PIN", systemImage: "wallet.pass")
|
||||
TextField("Fixed PIN", text: $fixedPin)
|
||||
.foregroundColor(.gray)
|
||||
.onChange(of: fixedPin, perform: { value in
|
||||
//Require that pin is no more than 6 numbers and no less than 6 numbers
|
||||
if fixedPin.utf8.count == pinLength {
|
||||
shortPin = false
|
||||
} else if fixedPin.utf8.count > pinLength {
|
||||
shortPin = false
|
||||
fixedPin = String(fixedPin.prefix(pinLength))
|
||||
} else if fixedPin.utf8.count < pinLength {
|
||||
shortPin = true
|
||||
}
|
||||
})
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
.keyboardType(.decimalPad)
|
||||
if shortPin {
|
||||
|
||||
Text("BLE Pin must be 6 digits long.")
|
||||
.font(.callout)
|
||||
.foregroundColor(.red)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.disabled(bleManager.connectedPeripheral == nil)
|
||||
Form {
|
||||
Section(header: Text("options")) {
|
||||
|
||||
Button {
|
||||
isPresentingSaveConfirm = true
|
||||
} label: {
|
||||
Label("save", systemImage: "square.and.arrow.down")
|
||||
}
|
||||
.disabled(bleManager.connectedPeripheral == nil || !hasChanges || shortPin)
|
||||
.buttonStyle(.bordered)
|
||||
.buttonBorderShape(.capsule)
|
||||
.controlSize(.large)
|
||||
.padding()
|
||||
.confirmationDialog(
|
||||
"Are you sure you want to save?",
|
||||
isPresented: $isPresentingSaveConfirm,
|
||||
titleVisibility: .visible
|
||||
) {
|
||||
Button("Save Config for \(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown")") {
|
||||
var bc = Config.BluetoothConfig()
|
||||
bc.enabled = enabled
|
||||
bc.mode = BluetoothModes(rawValue: mode)?.protoEnumValue() ?? Config.BluetoothConfig.PairingMode.randomPin
|
||||
bc.fixedPin = UInt32(fixedPin) ?? 123456
|
||||
let adminMessageId = bleManager.saveBluetoothConfig(config: bc, fromUser: node!.user!, toUser: node!.user!)
|
||||
if adminMessageId > 0 {
|
||||
// Should show a saved successfully alert once I know that to be true
|
||||
// for now just disable the button after a successful save
|
||||
hasChanges = false
|
||||
goBack()
|
||||
Toggle(isOn: $enabled) {
|
||||
Label("enabled", systemImage: "antenna.radiowaves.left.and.right")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
|
||||
Picker("bluetooth.pairingmode", selection: $mode ) {
|
||||
ForEach(BluetoothModes.allCases) { bm in
|
||||
Text(bm.description)
|
||||
}
|
||||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
|
||||
if mode == 1 {
|
||||
HStack {
|
||||
Label("bluetooth.mode.fixedpin", systemImage: "wallet.pass")
|
||||
TextField("bluetooth.mode.fixedpin", text: $fixedPin)
|
||||
.foregroundColor(.gray)
|
||||
.onChange(of: fixedPin, perform: { value in
|
||||
// Don't let the first character be 0 because it will get stripped when saving a UInt32
|
||||
if fixedPin.first == "0" {
|
||||
fixedPin = fixedPin.replacing("0", with: "")
|
||||
}
|
||||
//Require that pin is no more than 6 numbers and no less than 6 numbers
|
||||
if fixedPin.utf8.count == pinLength {
|
||||
shortPin = false
|
||||
} else if fixedPin.utf8.count > pinLength {
|
||||
shortPin = false
|
||||
fixedPin = String(fixedPin.prefix(pinLength))
|
||||
} else if fixedPin.utf8.count < pinLength {
|
||||
shortPin = true
|
||||
}
|
||||
})
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
.keyboardType(.decimalPad)
|
||||
if shortPin {
|
||||
Text("bluetooth.pin.validation")
|
||||
.font(.callout)
|
||||
.foregroundColor(.red)
|
||||
}
|
||||
}
|
||||
} message: {
|
||||
Text("After bluetooth config saves the node will reboot.")
|
||||
}
|
||||
}
|
||||
.disabled(bleManager.connectedPeripheral == nil)
|
||||
|
||||
Button {
|
||||
isPresentingSaveConfirm = true
|
||||
} label: {
|
||||
Label("save", systemImage: "square.and.arrow.down")
|
||||
}
|
||||
.disabled(bleManager.connectedPeripheral == nil || !hasChanges || shortPin)
|
||||
.buttonStyle(.bordered)
|
||||
.buttonBorderShape(.capsule)
|
||||
.controlSize(.large)
|
||||
.padding()
|
||||
.confirmationDialog(
|
||||
"are.you.sure",
|
||||
isPresented: $isPresentingSaveConfirm,
|
||||
titleVisibility: .visible
|
||||
) {
|
||||
let nodeName = bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : NSLocalizedString("unknown", comment: "Unknown")
|
||||
let buttonText = String.localizedStringWithFormat(NSLocalizedString("save.config %@", comment: "Save Config for %@"), nodeName)
|
||||
Button(buttonText) {
|
||||
var bc = Config.BluetoothConfig()
|
||||
bc.enabled = enabled
|
||||
bc.mode = BluetoothModes(rawValue: mode)?.protoEnumValue() ?? Config.BluetoothConfig.PairingMode.randomPin
|
||||
bc.fixedPin = UInt32(fixedPin) ?? 123456
|
||||
let adminMessageId = bleManager.saveBluetoothConfig(config: bc, fromUser: node!.user!, toUser: node!.user!)
|
||||
if adminMessageId > 0 {
|
||||
// Should show a saved successfully alert once I know that to be true
|
||||
// for now just disable the button after a successful save
|
||||
hasChanges = false
|
||||
goBack()
|
||||
}
|
||||
}
|
||||
} message: {
|
||||
Text("config.save.confirm")
|
||||
}
|
||||
.navigationTitle("bluetooth.config")
|
||||
.navigationBarItems(trailing:
|
||||
ZStack {
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ struct DeviceConfig: View {
|
|||
Picker("Button GPIO", selection: $buttonGPIO) {
|
||||
ForEach(0..<40) {
|
||||
if $0 == 0 {
|
||||
Text("Unset")
|
||||
Text("unset")
|
||||
} else {
|
||||
Text("Pin \($0)")
|
||||
}
|
||||
|
|
@ -73,7 +73,7 @@ struct DeviceConfig: View {
|
|||
Picker("Buzzer GPIO", selection: $buzzerGPIO) {
|
||||
ForEach(0..<40) {
|
||||
if $0 == 0 {
|
||||
Text("Unset")
|
||||
Text("unset")
|
||||
} else {
|
||||
Text("Pin \($0)")
|
||||
}
|
||||
|
|
@ -90,7 +90,7 @@ struct DeviceConfig: View {
|
|||
Button("Reset NodeDB", role: .destructive) {
|
||||
isPresentingNodeDBResetConfirm = true
|
||||
}
|
||||
.disabled(bleManager.connectedPeripheral == nil)
|
||||
.disabled(node?.user == nil)
|
||||
.buttonStyle(.bordered)
|
||||
.buttonBorderShape(.capsule)
|
||||
.controlSize(.large)
|
||||
|
|
@ -101,7 +101,7 @@ struct DeviceConfig: View {
|
|||
titleVisibility: .visible
|
||||
) {
|
||||
Button("Erase all device and app data?", role: .destructive) {
|
||||
if bleManager.sendNodeDBReset(destNum: bleManager.connectedPeripheral.num) {
|
||||
if bleManager.sendNodeDBReset(fromUser: node!.user!, toUser: node!.user!) {
|
||||
bleManager.disconnectPeripheral()
|
||||
clearCoreDataDatabase(context: context)
|
||||
} else {
|
||||
|
|
@ -112,7 +112,7 @@ struct DeviceConfig: View {
|
|||
Button("Factory Reset", role: .destructive) {
|
||||
isPresentingFactoryResetConfirm = true
|
||||
}
|
||||
.disabled(bleManager.connectedPeripheral == nil)
|
||||
.disabled(node?.user == nil)
|
||||
.buttonStyle(.bordered)
|
||||
.buttonBorderShape(.capsule)
|
||||
.controlSize(.large)
|
||||
|
|
@ -124,7 +124,7 @@ struct DeviceConfig: View {
|
|||
) {
|
||||
Button("Factory reset your device and app? ", role: .destructive) {
|
||||
|
||||
if bleManager.sendFactoryReset(destNum: bleManager.connectedPeripheral.num) {
|
||||
if bleManager.sendFactoryReset(fromUser: node!.user!, toUser: node!.user!) {
|
||||
bleManager.disconnectPeripheral()
|
||||
clearCoreDataDatabase(context: context)
|
||||
} else {
|
||||
|
|
@ -152,11 +152,13 @@ struct DeviceConfig: View {
|
|||
.padding()
|
||||
.confirmationDialog(
|
||||
|
||||
"Are you sure you want to save?",
|
||||
"are.you.sure",
|
||||
isPresented: $isPresentingSaveConfirm,
|
||||
titleVisibility: .visible
|
||||
) {
|
||||
Button("Save Device Config to \(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown")?") {
|
||||
let nodeName = bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : NSLocalizedString("unknown", comment: "Unknown")
|
||||
let buttonText = String.localizedStringWithFormat(NSLocalizedString("save.config %@", comment: "Save Config for %@"), nodeName)
|
||||
Button(buttonText) {
|
||||
|
||||
var dc = Config.DeviceConfig()
|
||||
dc.role = DeviceRoles(rawValue: deviceRole)!.protoEnumValue()
|
||||
|
|
@ -175,18 +177,14 @@ struct DeviceConfig: View {
|
|||
}
|
||||
}
|
||||
message: {
|
||||
|
||||
Text("After device config saves the node will reboot.")
|
||||
Text("config.save.confirm")
|
||||
}
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
|
||||
.navigationTitle("device.config")
|
||||
.navigationBarItems(trailing:
|
||||
|
||||
ZStack {
|
||||
|
||||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????")
|
||||
})
|
||||
.onAppear {
|
||||
|
|
|
|||
|
|
@ -27,110 +27,109 @@ struct DisplayConfig: View {
|
|||
|
||||
var body: some View {
|
||||
|
||||
VStack {
|
||||
|
||||
Form {
|
||||
Section(header: Text("Device Screen")) {
|
||||
|
||||
Picker("Screen on for", selection: $screenOnSeconds ) {
|
||||
ForEach(ScreenOnIntervals.allCases) { soi in
|
||||
Text(soi.description)
|
||||
}
|
||||
Form {
|
||||
Section(header: Text("Device Screen")) {
|
||||
|
||||
Picker("Screen on for", selection: $screenOnSeconds ) {
|
||||
ForEach(ScreenOnIntervals.allCases) { soi in
|
||||
Text(soi.description)
|
||||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
|
||||
Text("How long the screen remains on after the user button is pressed or messages are received.")
|
||||
.font(.caption)
|
||||
|
||||
Picker("Carousel Interval", selection: $screenCarouselInterval ) {
|
||||
ForEach(ScreenCarouselIntervals.allCases) { sci in
|
||||
Text(sci.description)
|
||||
}
|
||||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
Text("Automatically toggles to the next page on the screen like a carousel, based the specified interval.")
|
||||
.font(.caption)
|
||||
|
||||
Toggle(isOn: $compassNorthTop) {
|
||||
|
||||
Label("Always point north", systemImage: "location.north.circle")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
Text("The compass heading on the screen outside of the circle will always point north.")
|
||||
.font(.caption)
|
||||
|
||||
Toggle(isOn: $flipScreen) {
|
||||
|
||||
Label("Flip Screen", systemImage: "pip.swap")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
Text("Flip screen vertically")
|
||||
.font(.caption)
|
||||
Picker("OLED Type", selection: $oledType ) {
|
||||
ForEach(OledTypes.allCases) { ot in
|
||||
Text(ot.description)
|
||||
}
|
||||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
Text("Override automatic OLED screen detection.")
|
||||
.font(.caption)
|
||||
|
||||
}
|
||||
Section(header: Text("Format")) {
|
||||
Picker("GPS Format", selection: $gpsFormat ) {
|
||||
ForEach(GpsFormats.allCases) { lu in
|
||||
Text(lu.description)
|
||||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
|
||||
Text("How long the screen remains on after the user button is pressed or messages are received.")
|
||||
.font(.caption)
|
||||
|
||||
Picker("Carousel Interval", selection: $screenCarouselInterval ) {
|
||||
ForEach(ScreenCarouselIntervals.allCases) { sci in
|
||||
Text(sci.description)
|
||||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
|
||||
Text("The format used to display GPS coordinates on the device screen.")
|
||||
.font(.caption)
|
||||
.listRowSeparator(.visible)
|
||||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
Text("Automatically toggles to the next page on the screen like a carousel, based the specified interval.")
|
||||
.font(.caption)
|
||||
|
||||
Toggle(isOn: $compassNorthTop) {
|
||||
|
||||
Label("Always point north", systemImage: "location.north.circle")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
Text("The compass heading on the screen outside of the circle will always point north.")
|
||||
.font(.caption)
|
||||
|
||||
Toggle(isOn: $flipScreen) {
|
||||
|
||||
Label("Flip Screen", systemImage: "pip.swap")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
Text("Flip screen vertically")
|
||||
.font(.caption)
|
||||
Picker("OLED Type", selection: $oledType ) {
|
||||
ForEach(OledTypes.allCases) { ot in
|
||||
Text(ot.description)
|
||||
}
|
||||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
Text("Override automatic OLED screen detection.")
|
||||
.font(.caption)
|
||||
|
||||
}
|
||||
.disabled(bleManager.connectedPeripheral == nil)
|
||||
|
||||
Button {
|
||||
|
||||
isPresentingSaveConfirm = true
|
||||
Section(header: Text("Format")) {
|
||||
Picker("GPS Format", selection: $gpsFormat ) {
|
||||
ForEach(GpsFormats.allCases) { lu in
|
||||
Text(lu.description)
|
||||
}
|
||||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
|
||||
} label: {
|
||||
|
||||
Label("save", systemImage: "square.and.arrow.down")
|
||||
Text("The format used to display GPS coordinates on the device screen.")
|
||||
.font(.caption)
|
||||
.listRowSeparator(.visible)
|
||||
}
|
||||
.disabled(bleManager.connectedPeripheral == nil || !hasChanges)
|
||||
.buttonStyle(.bordered)
|
||||
.buttonBorderShape(.capsule)
|
||||
.controlSize(.large)
|
||||
.padding()
|
||||
.confirmationDialog(
|
||||
|
||||
"are.you.sure",
|
||||
isPresented: $isPresentingSaveConfirm
|
||||
) {
|
||||
Button("Save Display Config to \(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown")?") {
|
||||
|
||||
var dc = Config.DisplayConfig()
|
||||
dc.gpsFormat = GpsFormats(rawValue: gpsFormat)!.protoEnumValue()
|
||||
dc.screenOnSecs = UInt32(screenOnSeconds)
|
||||
dc.autoScreenCarouselSecs = UInt32(screenCarouselInterval)
|
||||
dc.compassNorthTop = compassNorthTop
|
||||
dc.flipScreen = flipScreen
|
||||
dc.oled = OledTypes(rawValue: oledType)!.protoEnumValue()
|
||||
|
||||
let adminMessageId = bleManager.saveDisplayConfig(config: dc, fromUser: node!.user!, toUser: node!.user!)
|
||||
|
||||
if adminMessageId > 0 {
|
||||
}
|
||||
.disabled(bleManager.connectedPeripheral == nil)
|
||||
|
||||
Button {
|
||||
|
||||
// Should show a saved successfully alert once I know that to be true
|
||||
// for now just disable the button after a successful save
|
||||
hasChanges = false
|
||||
goBack()
|
||||
}
|
||||
isPresentingSaveConfirm = true
|
||||
|
||||
} label: {
|
||||
|
||||
Label("save", systemImage: "square.and.arrow.down")
|
||||
}
|
||||
.disabled(bleManager.connectedPeripheral == nil || !hasChanges)
|
||||
.buttonStyle(.bordered)
|
||||
.buttonBorderShape(.capsule)
|
||||
.controlSize(.large)
|
||||
.padding()
|
||||
.confirmationDialog(
|
||||
"are.you.sure",
|
||||
isPresented: $isPresentingSaveConfirm
|
||||
) {
|
||||
let nodeName = bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : NSLocalizedString("unknown", comment: "Unknown")
|
||||
let buttonText = String.localizedStringWithFormat(NSLocalizedString("save.config %@", comment: "Save Config for %@"), nodeName)
|
||||
Button(buttonText) {
|
||||
var dc = Config.DisplayConfig()
|
||||
dc.gpsFormat = GpsFormats(rawValue: gpsFormat)!.protoEnumValue()
|
||||
dc.screenOnSecs = UInt32(screenOnSeconds)
|
||||
dc.autoScreenCarouselSecs = UInt32(screenCarouselInterval)
|
||||
dc.compassNorthTop = compassNorthTop
|
||||
dc.flipScreen = flipScreen
|
||||
dc.oled = OledTypes(rawValue: oledType)!.protoEnumValue()
|
||||
|
||||
let adminMessageId = bleManager.saveDisplayConfig(config: dc, fromUser: node!.user!, toUser: node!.user!)
|
||||
if adminMessageId > 0 {
|
||||
|
||||
// Should show a saved successfully alert once I know that to be true
|
||||
// for now just disable the button after a successful save
|
||||
hasChanges = false
|
||||
goBack()
|
||||
}
|
||||
}
|
||||
}
|
||||
message: {
|
||||
Text("config.save.confirm")
|
||||
}
|
||||
.navigationTitle("display.config")
|
||||
.navigationBarItems(trailing:
|
||||
ZStack {
|
||||
|
|
|
|||
|
|
@ -71,11 +71,13 @@ struct LoRaConfig: View {
|
|||
.controlSize(.large)
|
||||
.padding()
|
||||
.confirmationDialog(
|
||||
"Are you sure you want to save?",
|
||||
"are.you.sure",
|
||||
isPresented: $isPresentingSaveConfirm,
|
||||
titleVisibility: .visible
|
||||
) {
|
||||
Button("Save Config for \(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown")") {
|
||||
let nodeName = bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : NSLocalizedString("unknown", comment: "Unknown")
|
||||
let buttonText = String.localizedStringWithFormat(NSLocalizedString("save.config %@", comment: "Save Config for %@"), nodeName)
|
||||
Button(buttonText) {
|
||||
var lc = Config.LoRaConfig()
|
||||
lc.hopLimit = UInt32(hopLimit)
|
||||
lc.region = RegionCodes(rawValue: region)!.protoEnumValue()
|
||||
|
|
@ -91,7 +93,7 @@ struct LoRaConfig: View {
|
|||
}
|
||||
}
|
||||
} message: {
|
||||
Text("After LoRa config saves the node will reboot.")
|
||||
Text("config.save.confirm")
|
||||
}
|
||||
}
|
||||
.navigationTitle("lora.config")
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ struct CannedMessagesConfig: View {
|
|||
|
||||
HStack {
|
||||
Label("Messages", systemImage: "message.fill")
|
||||
TextField("Messages seperate with |", text: $messages)
|
||||
TextField("Messages seperate with |", text: $messages, axis: .vertical)
|
||||
.foregroundColor(.gray)
|
||||
.autocapitalization(.none)
|
||||
.disableAutocorrection(true)
|
||||
|
|
@ -124,9 +124,7 @@ struct CannedMessagesConfig: View {
|
|||
ForEach(0..<40) {
|
||||
|
||||
if $0 == 0 {
|
||||
|
||||
Text("Unset")
|
||||
|
||||
Text("unset")
|
||||
} else {
|
||||
|
||||
Text("Pin \($0)")
|
||||
|
|
@ -141,9 +139,7 @@ struct CannedMessagesConfig: View {
|
|||
ForEach(0..<40) {
|
||||
|
||||
if $0 == 0 {
|
||||
|
||||
Text("Unset")
|
||||
|
||||
Text("unset")
|
||||
} else {
|
||||
|
||||
Text("Pin \($0)")
|
||||
|
|
@ -158,9 +154,7 @@ struct CannedMessagesConfig: View {
|
|||
ForEach(0..<40) {
|
||||
|
||||
if $0 == 0 {
|
||||
|
||||
Text("Unset")
|
||||
|
||||
Text("unset")
|
||||
} else {
|
||||
|
||||
Text("Pin \($0)")
|
||||
|
|
@ -224,8 +218,9 @@ struct CannedMessagesConfig: View {
|
|||
isPresented: $isPresentingSaveConfirm,
|
||||
titleVisibility: .visible
|
||||
) {
|
||||
Button("Save Canned Messages Module Config to \(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown")?") {
|
||||
|
||||
let nodeName = bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : NSLocalizedString("unknown", comment: "Unknown")
|
||||
let buttonText = String.localizedStringWithFormat(NSLocalizedString("save.config %@", comment: "Save Config for %@"), nodeName)
|
||||
Button(buttonText) {
|
||||
if hasChanges {
|
||||
var cmc = ModuleConfig.CannedMessageConfig()
|
||||
cmc.enabled = enabled
|
||||
|
|
@ -261,10 +256,17 @@ struct CannedMessagesConfig: View {
|
|||
// Should show a saved successfully alert once I know that to be true
|
||||
// for now just disable the button after a successful save
|
||||
hasMessagesChanges = false
|
||||
if !hasChanges {
|
||||
bleManager.sendWantConfig()
|
||||
goBack()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
message: {
|
||||
Text("config.save.confirm")
|
||||
}
|
||||
.navigationTitle("canned.messages.config")
|
||||
.navigationBarItems(trailing:
|
||||
ZStack {
|
||||
|
|
@ -282,7 +284,9 @@ struct CannedMessagesConfig: View {
|
|||
self.inputbrokerEventCw = Int(node?.cannedMessageConfig?.inputbrokerEventCw ?? 0)
|
||||
self.inputbrokerEventCcw = Int(node?.cannedMessageConfig?.inputbrokerEventCcw ?? 0)
|
||||
self.inputbrokerEventPress = Int(node?.cannedMessageConfig?.inputbrokerEventPress ?? 0)
|
||||
self.messages = node?.cannedMessageConfig?.messages ?? ""
|
||||
self.hasChanges = false
|
||||
self.hasMessagesChanges = false
|
||||
}
|
||||
.onChange(of: configPreset) { newPreset in
|
||||
|
||||
|
|
|
|||
|
|
@ -6,49 +6,6 @@
|
|||
//
|
||||
import SwiftUI
|
||||
|
||||
enum OutputIntervals: Int, CaseIterable, Identifiable {
|
||||
|
||||
case unset = 0
|
||||
case oneSecond = 1000
|
||||
case twoSeconds = 2000
|
||||
case threeSeconds = 3000
|
||||
case fourSeconds = 4000
|
||||
case fiveSeconds = 5000
|
||||
case tenSeconds = 10000
|
||||
case fifteenSeconds = 15000
|
||||
case thirtySeconds = 30000
|
||||
case oneMinute = 60000
|
||||
|
||||
var id: Int { self.rawValue }
|
||||
var description: String {
|
||||
get {
|
||||
switch self {
|
||||
|
||||
case .unset:
|
||||
return "Unset"
|
||||
case .oneSecond:
|
||||
return "One Second"
|
||||
case .twoSeconds:
|
||||
return "Two Seconds"
|
||||
case .threeSeconds:
|
||||
return "Three Seconds"
|
||||
case .fourSeconds:
|
||||
return "Four Seconds"
|
||||
case .fiveSeconds:
|
||||
return "Five Seconds"
|
||||
case .tenSeconds:
|
||||
return "Ten Seconds"
|
||||
case .fifteenSeconds:
|
||||
return "Fifteen Seconds"
|
||||
case .thirtySeconds:
|
||||
return "Thirty Seconds"
|
||||
case .oneMinute:
|
||||
return "One Minute"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ExternalNotificationConfig: View {
|
||||
|
||||
@Environment(\.managedObjectContext) var context
|
||||
|
|
@ -97,8 +54,12 @@ struct ExternalNotificationConfig: View {
|
|||
Text("Use a PWM output (like the RAK Buzzer) for tunes instead of an on/off output. This will ignore the output, output duration and active settings and use the device config buzzer GPIO option instead.")
|
||||
.font(.caption)
|
||||
}
|
||||
if !usePWM {
|
||||
Section(header: Text("Primary GPIO")) {
|
||||
Section(header: Text("Advanced GPIO Options")) {
|
||||
Section(header: Text("Primary GPIO")
|
||||
.font(.caption)
|
||||
.foregroundColor(.gray)
|
||||
.textCase(.uppercase))
|
||||
{
|
||||
Toggle(isOn: $active) {
|
||||
Label("Active", systemImage: "togglepower")
|
||||
}
|
||||
|
|
@ -108,7 +69,7 @@ struct ExternalNotificationConfig: View {
|
|||
Picker("Output pin GPIO", selection: $output) {
|
||||
ForEach(0..<40) {
|
||||
if $0 == 0 {
|
||||
Text("Unset")
|
||||
Text("unset")
|
||||
} else {
|
||||
Text("Pin \($0)")
|
||||
}
|
||||
|
|
@ -133,7 +94,11 @@ struct ExternalNotificationConfig: View {
|
|||
.font(.caption)
|
||||
}
|
||||
|
||||
Section(header: Text("Optional GPIO")) {
|
||||
Section(header: Text("Optional GPIO")
|
||||
.font(.caption)
|
||||
.foregroundColor(.gray)
|
||||
.textCase(.uppercase))
|
||||
{
|
||||
Toggle(isOn: $alertBellBuzzer) {
|
||||
Label("Alert GPIO buzzer when receiving a bell", systemImage: "bell")
|
||||
}
|
||||
|
|
@ -153,7 +118,7 @@ struct ExternalNotificationConfig: View {
|
|||
Picker("Output pin buzzer GPIO ", selection: $outputBuzzer) {
|
||||
ForEach(0..<40) {
|
||||
if $0 == 0 {
|
||||
Text("Unset")
|
||||
Text("unset")
|
||||
} else {
|
||||
Text("Pin \($0)")
|
||||
}
|
||||
|
|
@ -163,7 +128,7 @@ struct ExternalNotificationConfig: View {
|
|||
Picker("Output pin vibra GPIO", selection: $outputVibra) {
|
||||
ForEach(0..<40) {
|
||||
if $0 == 0 {
|
||||
Text("Unset")
|
||||
Text("unset")
|
||||
} else {
|
||||
Text("Pin \($0)")
|
||||
}
|
||||
|
|
@ -189,7 +154,9 @@ struct ExternalNotificationConfig: View {
|
|||
isPresented: $isPresentingSaveConfirm,
|
||||
titleVisibility: .visible
|
||||
) {
|
||||
Button("Save External Notification Module Config to \(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown")?") {
|
||||
let nodeName = bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : NSLocalizedString("unknown", comment: "Unknown")
|
||||
let buttonText = String.localizedStringWithFormat(NSLocalizedString("save.config %@", comment: "Save Config for %@"), nodeName)
|
||||
Button(buttonText) {
|
||||
var enc = ModuleConfig.ExternalNotificationConfig()
|
||||
enc.enabled = enabled
|
||||
enc.alertBell = alertBell
|
||||
|
|
@ -213,6 +180,9 @@ struct ExternalNotificationConfig: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
message: {
|
||||
Text("config.save.confirm")
|
||||
}
|
||||
.navigationTitle("external.notification.config")
|
||||
.navigationBarItems(trailing:
|
||||
ZStack {
|
||||
|
|
|
|||
|
|
@ -22,158 +22,149 @@ struct MQTTConfig: View {
|
|||
@State var jsonEnabled = false
|
||||
|
||||
var body: some View {
|
||||
|
||||
VStack {
|
||||
|
||||
Form {
|
||||
Section(header: Text("options")) {
|
||||
|
||||
Form {
|
||||
Section(header: Text("options")) {
|
||||
Toggle(isOn: $enabled) {
|
||||
|
||||
Label("enabled", systemImage: "dot.radiowaves.right")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
|
||||
Toggle(isOn: $encryptionEnabled) {
|
||||
|
||||
Label("Encryption Enabled", systemImage: "lock.icloud")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
|
||||
Toggle(isOn: $jsonEnabled) {
|
||||
|
||||
Label("JSON Enabled", systemImage: "ellipsis.curlybraces")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
}
|
||||
Section(header: Text("Custom Server")) {
|
||||
HStack {
|
||||
Label("Address", systemImage: "server.rack")
|
||||
TextField("Server Address", text: $address)
|
||||
.foregroundColor(.gray)
|
||||
.autocapitalization(.none)
|
||||
.disableAutocorrection(true)
|
||||
.onChange(of: address, perform: { value in
|
||||
let totalBytes = address.utf8.count
|
||||
// Only mess with the value if it is too big
|
||||
if totalBytes > 30 {
|
||||
let firstNBytes = Data(username.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
|
||||
address = maxBytesString
|
||||
}
|
||||
}
|
||||
hasChanges = true
|
||||
})
|
||||
.foregroundColor(.gray)
|
||||
.keyboardType(.default)
|
||||
}
|
||||
.autocorrectionDisabled()
|
||||
|
||||
HStack {
|
||||
Label("mqtt.username", systemImage: "person.text.rectangle")
|
||||
TextField("mqtt.username", text: $username)
|
||||
.foregroundColor(.gray)
|
||||
.autocapitalization(.none)
|
||||
.disableAutocorrection(true)
|
||||
.onChange(of: username, perform: { value in
|
||||
|
||||
let totalBytes = username.utf8.count
|
||||
|
||||
// Only mess with the value if it is too big
|
||||
if totalBytes > 62 {
|
||||
|
||||
let firstNBytes = Data(username.utf8.prefix(62))
|
||||
|
||||
Toggle(isOn: $enabled) {
|
||||
|
||||
Label("enabled", systemImage: "dot.radiowaves.right")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
|
||||
Toggle(isOn: $encryptionEnabled) {
|
||||
|
||||
Label("Encryption Enabled", systemImage: "lock.icloud")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
|
||||
Toggle(isOn: $jsonEnabled) {
|
||||
|
||||
Label("JSON Enabled", systemImage: "ellipsis.curlybraces")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
|
||||
if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) {
|
||||
|
||||
// Set the shortName back to the last place where it was the right size
|
||||
username = maxBytesString
|
||||
}
|
||||
}
|
||||
hasChanges = true
|
||||
})
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
Section(header: Text("Custom Server")) {
|
||||
|
||||
HStack {
|
||||
Label("Address", systemImage: "server.rack")
|
||||
TextField("Server Address", text: $address)
|
||||
.foregroundColor(.gray)
|
||||
.autocapitalization(.none)
|
||||
.disableAutocorrection(true)
|
||||
.onChange(of: address, perform: { value in
|
||||
.keyboardType(.default)
|
||||
.scrollDismissesKeyboard(.interactively)
|
||||
HStack {
|
||||
Label("password", systemImage: "wallet.pass")
|
||||
TextField("password", text: $password)
|
||||
.foregroundColor(.gray)
|
||||
.autocapitalization(.none)
|
||||
.disableAutocorrection(true)
|
||||
.onChange(of: password, perform: { value in
|
||||
|
||||
let totalBytes = address.utf8.count
|
||||
|
||||
// Only mess with the value if it is too big
|
||||
if totalBytes > 30 {
|
||||
|
||||
let firstNBytes = Data(username.utf8.prefix(30))
|
||||
let totalBytes = password.utf8.count
|
||||
|
||||
if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) {
|
||||
|
||||
// Set the shortName back to the last place where it was the right size
|
||||
address = maxBytesString
|
||||
}
|
||||
// Only mess with the value if it is too big
|
||||
if totalBytes > 62 {
|
||||
|
||||
let firstNBytes = Data(password.utf8.prefix(62))
|
||||
|
||||
if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) {
|
||||
|
||||
// Set the shortName back to the last place where it was the right size
|
||||
password = maxBytesString
|
||||
}
|
||||
hasChanges = true
|
||||
})
|
||||
.foregroundColor(.gray)
|
||||
.keyboardType(.default)
|
||||
}
|
||||
.autocorrectionDisabled()
|
||||
|
||||
HStack {
|
||||
Label("mqtt.username", systemImage: "person.text.rectangle")
|
||||
TextField("mqtt.username", text: $username)
|
||||
.foregroundColor(.gray)
|
||||
.autocapitalization(.none)
|
||||
.disableAutocorrection(true)
|
||||
.onChange(of: username, perform: { value in
|
||||
|
||||
let totalBytes = username.utf8.count
|
||||
|
||||
// Only mess with the value if it is too big
|
||||
if totalBytes > 62 {
|
||||
|
||||
let firstNBytes = Data(username.utf8.prefix(62))
|
||||
|
||||
if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) {
|
||||
|
||||
// Set the shortName back to the last place where it was the right size
|
||||
username = maxBytesString
|
||||
}
|
||||
}
|
||||
hasChanges = true
|
||||
})
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
.keyboardType(.default)
|
||||
.scrollDismissesKeyboard(.interactively)
|
||||
HStack {
|
||||
Label("password", systemImage: "wallet.pass")
|
||||
TextField("password", text: $password)
|
||||
.foregroundColor(.gray)
|
||||
.autocapitalization(.none)
|
||||
.disableAutocorrection(true)
|
||||
.onChange(of: password, perform: { value in
|
||||
|
||||
let totalBytes = password.utf8.count
|
||||
|
||||
// Only mess with the value if it is too big
|
||||
if totalBytes > 62 {
|
||||
|
||||
let firstNBytes = Data(password.utf8.prefix(62))
|
||||
|
||||
if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) {
|
||||
|
||||
// Set the shortName back to the last place where it was the right size
|
||||
password = maxBytesString
|
||||
}
|
||||
}
|
||||
hasChanges = true
|
||||
})
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
.keyboardType(.default)
|
||||
.scrollDismissesKeyboard(.interactively)
|
||||
}
|
||||
hasChanges = true
|
||||
})
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
Text("WiFi or Ethernet must also be enabled for MQTT to work. You can set uplink and downlink for each channel.")
|
||||
.font(.callout)
|
||||
.keyboardType(.default)
|
||||
.scrollDismissesKeyboard(.interactively)
|
||||
}
|
||||
.scrollDismissesKeyboard(.interactively)
|
||||
.disabled(!(node != nil))
|
||||
|
||||
Button {
|
||||
|
||||
isPresentingSaveConfirm = true
|
||||
|
||||
} label: {
|
||||
|
||||
Label("save", systemImage: "square.and.arrow.down")
|
||||
}
|
||||
.disabled(bleManager.connectedPeripheral == nil || !hasChanges)
|
||||
.buttonStyle(.bordered)
|
||||
.buttonBorderShape(.capsule)
|
||||
.controlSize(.large)
|
||||
.padding()
|
||||
.confirmationDialog(
|
||||
"are.you.sure",
|
||||
isPresented: $isPresentingSaveConfirm,
|
||||
titleVisibility: .visible
|
||||
) {
|
||||
Button("Save MQTT Config to \(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown")?") {
|
||||
var mqtt = ModuleConfig.MQTTConfig()
|
||||
mqtt.enabled = self.enabled
|
||||
mqtt.address = self.address
|
||||
mqtt.username = self.username
|
||||
mqtt.password = self.password
|
||||
mqtt.encryptionEnabled = self.encryptionEnabled
|
||||
mqtt.jsonEnabled = self.jsonEnabled
|
||||
let adminMessageId = bleManager.saveMQTTConfig(config: mqtt, fromUser: node!.user!, toUser: node!.user!)
|
||||
if adminMessageId > 0 {
|
||||
// Should show a saved successfully alert once I know that to be true
|
||||
// for now just disable the button after a successful save
|
||||
hasChanges = false
|
||||
goBack()
|
||||
}
|
||||
Text("WiFi or Ethernet must also be enabled for MQTT to work. You can set uplink and downlink for each channel.")
|
||||
.font(.callout)
|
||||
}
|
||||
.scrollDismissesKeyboard(.interactively)
|
||||
.disabled(!(node != nil))
|
||||
|
||||
Button {
|
||||
isPresentingSaveConfirm = true
|
||||
} label: {
|
||||
Label("save", systemImage: "square.and.arrow.down")
|
||||
}
|
||||
.disabled(bleManager.connectedPeripheral == nil || !hasChanges)
|
||||
.buttonStyle(.bordered)
|
||||
.buttonBorderShape(.capsule)
|
||||
.controlSize(.large)
|
||||
.padding()
|
||||
.confirmationDialog(
|
||||
"are.you.sure",
|
||||
isPresented: $isPresentingSaveConfirm,
|
||||
titleVisibility: .visible
|
||||
) {
|
||||
let nodeName = bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : NSLocalizedString("unknown", comment: "Unknown")
|
||||
let buttonText = String.localizedStringWithFormat(NSLocalizedString("save.config %@", comment: "Save Config for %@"), nodeName)
|
||||
Button(buttonText) {
|
||||
var mqtt = ModuleConfig.MQTTConfig()
|
||||
mqtt.enabled = self.enabled
|
||||
mqtt.address = self.address
|
||||
mqtt.username = self.username
|
||||
mqtt.password = self.password
|
||||
mqtt.encryptionEnabled = self.encryptionEnabled
|
||||
mqtt.jsonEnabled = self.jsonEnabled
|
||||
let adminMessageId = bleManager.saveMQTTConfig(config: mqtt, fromUser: node!.user!, toUser: node!.user!)
|
||||
if adminMessageId > 0 {
|
||||
// Should show a saved successfully alert once I know that to be true
|
||||
// for now just disable the button after a successful save
|
||||
hasChanges = false
|
||||
goBack()
|
||||
}
|
||||
}
|
||||
}
|
||||
message: {
|
||||
Text("config.save.confirm")
|
||||
}
|
||||
.navigationTitle("mqtt.config")
|
||||
.navigationBarItems(trailing:
|
||||
ZStack {
|
||||
|
|
@ -190,17 +181,17 @@ struct MQTTConfig: View {
|
|||
self.hasChanges = false
|
||||
}
|
||||
.onChange(of: enabled) { newEnabled in
|
||||
if node != nil && node!.mqttConfig != nil {
|
||||
if node != nil && node?.mqttConfig != nil {
|
||||
if newEnabled != node!.mqttConfig!.enabled { hasChanges = true }
|
||||
}
|
||||
}
|
||||
.onChange(of: encryptionEnabled) { newEncryptionEnabled in
|
||||
if node != nil && node!.mqttConfig != nil {
|
||||
if node != nil && node?.mqttConfig != nil {
|
||||
if newEncryptionEnabled != node!.mqttConfig!.encryptionEnabled { hasChanges = true }
|
||||
}
|
||||
}
|
||||
.onChange(of: jsonEnabled) { newJsonEnabled in
|
||||
if node != nil && node!.mqttConfig != nil {
|
||||
if node != nil && node?.mqttConfig != nil {
|
||||
if newJsonEnabled != node!.mqttConfig!.jsonEnabled { hasChanges = true }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,44 +6,6 @@
|
|||
//
|
||||
import SwiftUI
|
||||
|
||||
// Default of 0 is off
|
||||
enum SenderIntervals: Int, CaseIterable, Identifiable {
|
||||
|
||||
case off = 0
|
||||
case fifteenSeconds = 15
|
||||
case thirtySeconds = 30
|
||||
case oneMinute = 60
|
||||
case fiveMinutes = 300
|
||||
case tenMinutes = 600
|
||||
case fifteenMinutes = 900
|
||||
case thirtyMinutes = 1800
|
||||
|
||||
|
||||
var id: Int { self.rawValue }
|
||||
var description: String {
|
||||
get {
|
||||
switch self {
|
||||
case .off:
|
||||
return "Off"
|
||||
case .fifteenSeconds:
|
||||
return "Fifteen Seconds"
|
||||
case .thirtySeconds:
|
||||
return "Thirty Seconds"
|
||||
case .oneMinute:
|
||||
return "One Minute"
|
||||
case .fiveMinutes:
|
||||
return "Five Minutes"
|
||||
case .tenMinutes:
|
||||
return "Ten Minutes"
|
||||
case .fifteenMinutes:
|
||||
return "Fifteen Minutes"
|
||||
case .thirtyMinutes:
|
||||
return "Thirty Minutes"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct RangeTestConfig: View {
|
||||
|
||||
@Environment(\.managedObjectContext) var context
|
||||
|
|
@ -100,7 +62,9 @@ struct RangeTestConfig: View {
|
|||
isPresented: $isPresentingSaveConfirm,
|
||||
titleVisibility: .visible
|
||||
) {
|
||||
Button("Save Range Test Module Config to \(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown")?") {
|
||||
let nodeName = bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : NSLocalizedString("unknown", comment: "Unknown")
|
||||
let buttonText = String.localizedStringWithFormat(NSLocalizedString("save.config %@", comment: "Save Config for %@"), nodeName)
|
||||
Button(buttonText) {
|
||||
var rtc = ModuleConfig.RangeTestConfig()
|
||||
rtc.enabled = enabled
|
||||
rtc.save = save
|
||||
|
|
@ -114,6 +78,9 @@ struct RangeTestConfig: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
message: {
|
||||
Text("config.save.confirm")
|
||||
}
|
||||
.navigationTitle("range.test.config")
|
||||
.navigationBarItems(trailing:
|
||||
ZStack {
|
||||
|
|
|
|||
|
|
@ -74,13 +74,9 @@ struct SerialConfig: View {
|
|||
|
||||
Picker("Receive data (rxd) GPIO pin", selection: $rxd) {
|
||||
ForEach(0..<40) {
|
||||
|
||||
if $0 == 0 {
|
||||
|
||||
Text("Unset")
|
||||
|
||||
Text("unset")
|
||||
} else {
|
||||
|
||||
Text("Pin \($0)")
|
||||
}
|
||||
}
|
||||
|
|
@ -89,13 +85,9 @@ struct SerialConfig: View {
|
|||
|
||||
Picker("Transmit data (txd) GPIO pin", selection: $txd) {
|
||||
ForEach(0..<40) {
|
||||
|
||||
if $0 == 0 {
|
||||
|
||||
Text("Unset")
|
||||
|
||||
Text("unset")
|
||||
} else {
|
||||
|
||||
Text("Pin \($0)")
|
||||
}
|
||||
}
|
||||
|
|
@ -126,8 +118,9 @@ struct SerialConfig: View {
|
|||
isPresented: $isPresentingSaveConfirm,
|
||||
titleVisibility: .visible
|
||||
) {
|
||||
Button("Save Serial Module Config to \(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown")?") {
|
||||
|
||||
let nodeName = bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : NSLocalizedString("unknown", comment: "Unknown")
|
||||
let buttonText = String.localizedStringWithFormat(NSLocalizedString("save.config %@", comment: "Save Config for %@"), nodeName)
|
||||
Button(buttonText) {
|
||||
var sc = ModuleConfig.SerialConfig()
|
||||
sc.enabled = enabled
|
||||
sc.echo = echo
|
||||
|
|
@ -147,7 +140,9 @@ struct SerialConfig: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
message: {
|
||||
Text("config.save.confirm")
|
||||
}
|
||||
.navigationTitle("serial.config")
|
||||
.navigationBarItems(trailing:
|
||||
|
||||
|
|
|
|||
|
|
@ -6,66 +6,6 @@
|
|||
//
|
||||
import SwiftUI
|
||||
|
||||
enum UpdateIntervals: Int, CaseIterable, Identifiable {
|
||||
|
||||
case fifteenSeconds = 15
|
||||
case thirtySeconds = 30
|
||||
case oneMinute = 60
|
||||
case fiveMinutes = 300
|
||||
case tenMinutes = 600
|
||||
case fifteenMinutes = 900
|
||||
case thirtyMinutes = 1800
|
||||
case oneHour = 3600
|
||||
case twoHours = 7200
|
||||
case threeHours = 10800
|
||||
case fourHours = 14400
|
||||
case fiveHours = 18000
|
||||
case sixHours = 21600
|
||||
case twelveHours = 43200
|
||||
case eighteenHours = 64800
|
||||
case twentyFourHours = 86400
|
||||
|
||||
var id: Int { self.rawValue }
|
||||
var description: String {
|
||||
get {
|
||||
switch self {
|
||||
case .fifteenSeconds:
|
||||
return "Fifteen Seconds"
|
||||
case .thirtySeconds:
|
||||
return "Thirty Seconds"
|
||||
case .oneMinute:
|
||||
return "One Minute"
|
||||
case .fiveMinutes:
|
||||
return "Five Minutes"
|
||||
case .tenMinutes:
|
||||
return "Ten Minutes"
|
||||
case .fifteenMinutes:
|
||||
return "Fifteen Minutes"
|
||||
case .thirtyMinutes:
|
||||
return "Thirty Minutes"
|
||||
case .oneHour:
|
||||
return "One Hour"
|
||||
case .twoHours:
|
||||
return "Two Hours"
|
||||
case .threeHours:
|
||||
return "Three Hours"
|
||||
case .fourHours:
|
||||
return "Four Hours"
|
||||
case .fiveHours:
|
||||
return "Five Hours"
|
||||
case .sixHours:
|
||||
return "Six Hours"
|
||||
case .twelveHours:
|
||||
return "Twelve Hours"
|
||||
case .eighteenHours:
|
||||
return "Eighteen Hours"
|
||||
case .twentyFourHours:
|
||||
return "Twenty Four Hours"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct TelemetryConfig: View {
|
||||
|
||||
@Environment(\.managedObjectContext) var context
|
||||
|
|
@ -137,7 +77,9 @@ struct TelemetryConfig: View {
|
|||
isPresented: $isPresentingSaveConfirm,
|
||||
titleVisibility: .visible
|
||||
) {
|
||||
Button("Save Telemetry Module Config to \(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown")?") {
|
||||
let nodeName = bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : NSLocalizedString("unknown", comment: "Unknown")
|
||||
let buttonText = String.localizedStringWithFormat(NSLocalizedString("save.config %@", comment: "Save Config for %@"), nodeName)
|
||||
Button(buttonText) {
|
||||
var tc = ModuleConfig.TelemetryConfig()
|
||||
tc.deviceUpdateInterval = UInt32(deviceUpdateInterval)
|
||||
tc.environmentUpdateInterval = UInt32(environmentUpdateInterval)
|
||||
|
|
@ -153,7 +95,9 @@ struct TelemetryConfig: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
message: {
|
||||
Text("config.save.confirm")
|
||||
}
|
||||
.navigationTitle("telemetry.config")
|
||||
.navigationBarItems(trailing:
|
||||
ZStack {
|
||||
|
|
|
|||
|
|
@ -104,17 +104,19 @@ struct NetworkConfig: View {
|
|||
.controlSize(.large)
|
||||
.padding()
|
||||
.confirmationDialog(
|
||||
"Are you sure you want to save?",
|
||||
"are.you.sure",
|
||||
isPresented: $isPresentingSaveConfirm,
|
||||
titleVisibility: .visible
|
||||
) {
|
||||
Button("Save Config for \(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown")") {
|
||||
let nodeName = bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : NSLocalizedString("unknown", comment: "Unknown")
|
||||
let buttonText = String.localizedStringWithFormat(NSLocalizedString("save.config %@", comment: "Save Config for %@"), nodeName)
|
||||
Button(buttonText) {
|
||||
var network = Config.NetworkConfig()
|
||||
network.wifiEnabled = self.wifiEnabled
|
||||
network.wifiSsid = self.wifiSsid
|
||||
network.wifiPsk = self.wifiPsk
|
||||
network.ethEnabled = self.ethEnabled
|
||||
network.ethMode = Config.NetworkConfig.EthMode.dhcp
|
||||
//network.addressMode = Config.NetworkConfig.AddressMode.dhcp
|
||||
|
||||
let adminMessageId = bleManager.saveWiFiConfig(config: network, fromUser: node!.user!, toUser: node!.user!)
|
||||
if adminMessageId > 0 {
|
||||
|
|
@ -125,7 +127,7 @@ struct NetworkConfig: View {
|
|||
}
|
||||
}
|
||||
} message: {
|
||||
Text("After network config saves the node will reboot.")
|
||||
Text("config.save.confirm")
|
||||
}
|
||||
}
|
||||
.navigationTitle("network.config")
|
||||
|
|
|
|||
|
|
@ -113,7 +113,7 @@ struct PositionConfig: View {
|
|||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
|
||||
Picker("Position Broadcast Interval", selection: $positionBroadcastSeconds) {
|
||||
ForEach(PositionBroadcastIntervals.allCases) { at in
|
||||
ForEach(UpdateIntervals.allCases) { at in
|
||||
Text(at.description)
|
||||
}
|
||||
}
|
||||
|
|
@ -155,7 +155,7 @@ struct PositionConfig: View {
|
|||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
|
||||
Toggle(isOn: $includeTimestamp) { //128
|
||||
Label("Timestamp", systemImage: "clock")
|
||||
Label("timestamp", systemImage: "clock")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
|
||||
|
|
@ -205,7 +205,9 @@ struct PositionConfig: View {
|
|||
isPresented: $isPresentingSaveConfirm,
|
||||
titleVisibility: .visible
|
||||
) {
|
||||
Button("Save Position Config to \(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown")?") {
|
||||
let nodeName = bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : NSLocalizedString("unknown", comment: "Unknown")
|
||||
let buttonText = String.localizedStringWithFormat(NSLocalizedString("save.config %@", comment: "Save Config for %@"), nodeName)
|
||||
Button(buttonText) {
|
||||
|
||||
if fixedPosition {
|
||||
_ = bleManager.sendPosition(destNum: bleManager.connectedPeripheral.num, wantResponse: false)
|
||||
|
|
@ -239,6 +241,9 @@ struct PositionConfig: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
message: {
|
||||
Text("config.save.confirm")
|
||||
}
|
||||
}
|
||||
.navigationTitle("position.config")
|
||||
.navigationBarItems(trailing:
|
||||
|
|
|
|||
|
|
@ -10,40 +10,32 @@ struct MeshLog: View {
|
|||
@State private var document: LogDocument = LogDocument(logFile: "MESHTASTIC MESH ACTIVITY LOG\n")
|
||||
|
||||
var body: some View {
|
||||
|
||||
|
||||
List(logs, id: \.self, rowContent: Text.init)
|
||||
.task {
|
||||
do {
|
||||
|
||||
let url = logFile!
|
||||
logs.removeAll()
|
||||
|
||||
var lineCount = 0
|
||||
let lineLimit = 500
|
||||
|
||||
// Get the number of lines
|
||||
for try await _ in url.lines {
|
||||
lineCount += 1
|
||||
}
|
||||
|
||||
// Set the record to start with if we have more lines than the limit
|
||||
var startingLog = 0
|
||||
if lineCount > lineLimit {
|
||||
startingLog = lineCount - lineLimit
|
||||
}
|
||||
|
||||
var lineNumber = 0
|
||||
|
||||
for try await log in url.lines {
|
||||
if lineNumber >= startingLog {
|
||||
|
||||
logs.append(log)
|
||||
document.logFile.append("\(log) \n")
|
||||
}
|
||||
lineNumber += 1
|
||||
}
|
||||
logs.reverse()
|
||||
|
||||
} catch {
|
||||
// Stop adding logs when an error is thrown
|
||||
}
|
||||
|
|
@ -54,7 +46,6 @@ struct MeshLog: View {
|
|||
contentType: UTType.plainText,
|
||||
defaultFilename: "mesh-activity-log",
|
||||
onCompletion: { result in
|
||||
|
||||
if case .success = result {
|
||||
print("Mesh activity log download: success.")
|
||||
} else {
|
||||
|
|
@ -62,15 +53,12 @@ struct MeshLog: View {
|
|||
}
|
||||
}
|
||||
)
|
||||
|
||||
.textSelection(.enabled)
|
||||
.font(.caption)
|
||||
|
||||
|
||||
HStack(alignment: .center) {
|
||||
Spacer()
|
||||
|
||||
Button(role: .destructive) {
|
||||
|
||||
let text = ""
|
||||
do {
|
||||
try text.write(to: logFile!, atomically: false, encoding: .utf8)
|
||||
|
|
@ -78,35 +66,27 @@ struct MeshLog: View {
|
|||
} catch {
|
||||
print(error)
|
||||
}
|
||||
|
||||
} label: {
|
||||
|
||||
Label("Clear Log", systemImage: "trash.fill")
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
.buttonBorderShape(.capsule)
|
||||
.controlSize(.large)
|
||||
.padding()
|
||||
|
||||
Spacer()
|
||||
|
||||
Button {
|
||||
|
||||
isExporting = true
|
||||
|
||||
} label: {
|
||||
|
||||
Label("Save Log", systemImage: "square.and.arrow.down")
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
.buttonBorderShape(.capsule)
|
||||
.controlSize(.large)
|
||||
.padding()
|
||||
|
||||
Spacer()
|
||||
|
||||
}
|
||||
.padding(.bottom, 10)
|
||||
.navigationTitle("Mesh Activity Log")
|
||||
.navigationTitle("mesh.log")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,7 +44,6 @@ struct Settings: View {
|
|||
|
||||
Image(systemName: "person.crop.rectangle.fill")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
|
||||
Text("user")
|
||||
}
|
||||
|
||||
|
|
@ -55,20 +54,19 @@ struct Settings: View {
|
|||
|
||||
Image(systemName: "dot.radiowaves.left.and.right")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
|
||||
Text("lora")
|
||||
}
|
||||
|
||||
// NavigationLink() {
|
||||
//
|
||||
// Channels(node: nodes.first(where: { $0.num == connectedNodeNum }))
|
||||
// } label: {
|
||||
//
|
||||
// Image(systemName: "fibrechannel")
|
||||
// .symbolRenderingMode(.hierarchical)
|
||||
//
|
||||
// Text("channels")
|
||||
// }
|
||||
NavigationLink() {
|
||||
|
||||
Channels(node: nodes.first(where: { $0.num == connectedNodeNum }))
|
||||
} label: {
|
||||
|
||||
Image(systemName: "fibrechannel")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
|
||||
Text("channels")
|
||||
}
|
||||
|
||||
NavigationLink() {
|
||||
|
||||
|
|
@ -179,15 +177,10 @@ struct Settings: View {
|
|||
Text("admin.log")
|
||||
}
|
||||
}
|
||||
|
||||
Section(header: Text("about")) {
|
||||
|
||||
NavigationLink {
|
||||
|
||||
AboutMeshtastic()
|
||||
|
||||
} label: {
|
||||
|
||||
Image(systemName: "questionmark.app")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
|
||||
|
|
@ -196,10 +189,8 @@ struct Settings: View {
|
|||
}
|
||||
}
|
||||
.onAppear {
|
||||
|
||||
self.bleManager.context = context
|
||||
self.bleManager.userSettings = userSettings
|
||||
|
||||
}
|
||||
.listStyle(GroupedListStyle())
|
||||
.navigationTitle("settings")
|
||||
|
|
|
|||
|
|
@ -56,15 +56,15 @@ struct ShareChannels: View {
|
|||
Grid() {
|
||||
GridRow {
|
||||
Spacer()
|
||||
Text("Include")
|
||||
Text("include")
|
||||
.font(.caption)
|
||||
.fontWeight(.bold)
|
||||
.padding(.trailing)
|
||||
Text("Channel")
|
||||
Text("channel")
|
||||
.font(.caption)
|
||||
.fontWeight(.bold)
|
||||
.padding(.trailing)
|
||||
Text("Encrypted")
|
||||
Text("encrypted")
|
||||
.font(.caption)
|
||||
.fontWeight(.bold)
|
||||
}
|
||||
|
|
@ -246,7 +246,7 @@ struct ShareChannels: View {
|
|||
Button {
|
||||
isPresentingHelp = false
|
||||
} label: {
|
||||
Label("Close", systemImage: "xmark")
|
||||
Label("close", systemImage: "xmark")
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
.buttonBorderShape(.capsule)
|
||||
|
|
@ -254,7 +254,7 @@ struct ShareChannels: View {
|
|||
.padding()
|
||||
#endif
|
||||
}
|
||||
.navigationTitle("Generate QR Code")
|
||||
.navigationTitle("generate.qr.code")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.navigationBarItems(trailing:
|
||||
ZStack {
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ struct UserConfig: View {
|
|||
.controlSize(.large)
|
||||
.padding()
|
||||
.confirmationDialog(
|
||||
"Are you sure you want to save?",
|
||||
"are.you.sure",
|
||||
isPresented: $isPresentingSaveConfirm,
|
||||
titleVisibility: .visible
|
||||
) {
|
||||
|
|
@ -95,7 +95,7 @@ struct UserConfig: View {
|
|||
}
|
||||
}
|
||||
} message: {
|
||||
Text("After user config saves the node will reboot.")
|
||||
Text("config.save.confirm")
|
||||
}
|
||||
}
|
||||
Spacer()
|
||||
|
|
|
|||
|
|
@ -8,43 +8,60 @@
|
|||
"about"="Über";
|
||||
"about.meshtastic"="Über Meshtastic";
|
||||
"admin"="admin";
|
||||
"admin.log"="Admin Message Log";
|
||||
"admin.log"="Admin Log";
|
||||
"ago"="her";
|
||||
"airtime"="Airtime";
|
||||
"always.on"="Immer an";
|
||||
"app.settings"="App Einstellungen";
|
||||
"are.you.sure"="Bist Du sicher?";
|
||||
"ascii.capable"="ASCII fähig";
|
||||
"available.radios"="Geräte in der Nähe";
|
||||
"automatic.detection"="Automatische erkennung";
|
||||
"ble.name"="BLE Name";
|
||||
"battery.level"="Batterie Ladung";
|
||||
"battery.level.trend"="Batterie Ladungstrend";
|
||||
"ble.name"="BLE Name";"ble.connection.timeout %d %@"="Verbindung nach %d Versuchen zu %@ fehlgeschlagen. Evtl. hilft es, die Verbindung unter Einstellungen > Bluetooth manuell zu löschen.";
|
||||
"ble.connection.timeout %d %@"="Verbindung nach %d Versuchen zu %@ fehlgeschlagen. Evtl. hilft es, die Verbindung unter Einstellungen > Bluetooth manuell zu löschen.";
|
||||
"ble.errorcode.6 %@"="%@ Die App wird automatisch zum präferierten Gerät wiederverbinden, sobald es in Reichweite kommt.";
|
||||
"ble.errorcode.14 %@"="%@ Dieser fehler kann üblicherweise behoben werden, in dem man unter Einstellungen > Bluetooth die Verbindung manuell löscht und sich erneut mit dem Gerät verbindet.";
|
||||
"ble.errorcode.pin %@"="%@ Bitte versuche es erneut. achte sorgfältig auf die richtige PIN.";
|
||||
"bluetooth"="Bluetooth";
|
||||
"bluetooth.off"="Bluetooth ist aus";
|
||||
"bluetooth.config"="Bluetooth Konfiguration";
|
||||
"bluetooth.mode.randompin"="Zufällige PIN";
|
||||
"bluetooth.mode.fixedpin"="Feste PIN";
|
||||
"bluetooth.mode.nopin"="Keine PIN (geht einfach)";
|
||||
"bluetooth.pairingmode"="Pairing Modus";
|
||||
"bluetooth.pin.validation"="Die Bluetooth Pin muss 6 Stellen lang sein.";
|
||||
"bytes"="Bytes";
|
||||
"cancel"="Abbrechen";
|
||||
"canned.messages"="Canned Messages";
|
||||
"canned.messages.config"="Canned Messages Config";
|
||||
"canned.messages.preset.manual"="Manualle Konfiguration";
|
||||
"canned.messages.preset.manual"="Manuelle Konfiguration";
|
||||
"canned.messages.preset.rakrotary"="RAK Drehimpulsgeber Modul";
|
||||
"canned.messages.preset.cardkb"="M5 Stack Card KB / RAK Tastenfeld";
|
||||
"channel"="Kanal";
|
||||
"channel.role.disabled"="Deaktiviert";
|
||||
"channel.role.primary"="Primär";
|
||||
"channel.role.secondary"="Sekundär";
|
||||
"channel.utilization"="Kanalbelegung";
|
||||
"channels"="Kanäle";
|
||||
"clear.app.data"="App Daten löschen";
|
||||
"clear.log"="Log löschen";
|
||||
"close"="Schließen";
|
||||
"config.save.confirm"="Nach dem ändern der Einstellungen wird das Gerät neu starten.";
|
||||
"connected.radio"="Verbundenes Gerät";
|
||||
"communicating"="Verbinde mit Gerät...";
|
||||
"connected"="Derzeit verbunden";
|
||||
"connecting"="Verbinde...";
|
||||
"contacts"="Kontakte";
|
||||
"copy"="Kopieren";
|
||||
"current"="Current";
|
||||
"default"="Standard";
|
||||
"delete"="Löschen";
|
||||
"device"="Gerät";
|
||||
"device.config"="Gerätekonfiguration";
|
||||
"device.metrics.delete"="Delete all device metrics?";
|
||||
"device.metrics.log"="Device Metrics Log";
|
||||
"device.role.client"="Client (Standard) - Mit App verbundener Client.";
|
||||
"device.role.clientmute"="Client Leise - Das selbe wie Client, außer das die Pakete nicht über diesen Node weitergeleitet werden. Nimmt nicht am Mesh-Routing teil.";
|
||||
"device.role.router"="Router - Mesh Pakete werden bevorzugt über diesen Node gerouted. Dieser Node wird nicht von einer Client App benutzt. WLAN, Bluetooth und Display sind aus.";
|
||||
|
|
@ -58,9 +75,14 @@
|
|||
"echo"="Echo";
|
||||
"email.address"="Email Adresse";
|
||||
"enabled"="Aktiviert";
|
||||
"encrypted"="Verschlüsselt";
|
||||
"external.notification"="Externe Benachrichtigung";
|
||||
"external.notification.config"="Einstellungen der externen Benachrichtigung";
|
||||
"firmware.version"="Firmware Version";
|
||||
"firmware.version.unsupported"="Nicht unterstützte Firmware Version erkannt. Kann nicht verbinden.";
|
||||
"gas"="Gas";
|
||||
"gas.resistance"="Gas Resistance";
|
||||
"generate.qr.code"="QR Code Erzeugen";
|
||||
"gpsformat.dec"="Dezimalgrad Format";
|
||||
"gpsformat.dms"="Grad Minuten Sekunden";
|
||||
"gpsformat.utm"="Universal Transversal Mercator";
|
||||
|
|
@ -70,6 +92,7 @@
|
|||
"heard"="Gehört";
|
||||
"heard.last"="Zuletzt gehört";
|
||||
"hybrid"="Hybrid";
|
||||
"include"="Include";
|
||||
"inputevent.none"="Keins";
|
||||
"inputevent.up"="Hoch";
|
||||
"inputevent.down"="Runter";
|
||||
|
|
@ -80,6 +103,8 @@
|
|||
"inputevent.cancel"="Abbrechen";
|
||||
"interval.one.second"="Eine Sekunde";
|
||||
"interval.two.seconds"="Zwei Sekunden";
|
||||
"interval.three.seconds"="Three Seconds";
|
||||
"interval.four.seconds"="Four Seconds";
|
||||
"interval.five.seconds"="Fünf Sekunden";
|
||||
"interval.ten.seconds"="Zehn Sekunden";
|
||||
"interval.fifteen.seconds"="Fünfzehn Sekunden";
|
||||
|
|
@ -93,9 +118,17 @@
|
|||
"interval.fifteen.minutes"="Fünfzehn Minutes";
|
||||
"interval.thirty.minutes"="Dreißig Minutes";
|
||||
"interval.one.hour"="Eine Stunde";
|
||||
"interval.two.hours"="Two Hours";
|
||||
"interval.three.hours"="Three Hours";
|
||||
"interval.four.hours"="Four Hours";
|
||||
"interval.five.hours"="Five Hours";
|
||||
"interval.six.hours"="Sechs Stunden";
|
||||
"interval.twelve.hours"="Zwölf Stunden";
|
||||
"interval.eighteen.hours"="Eighteen Hours";
|
||||
"interval.twentyfour.hours"="Vierundzwanzig Stunden";
|
||||
"interval.thirtysix.hours"="Thirty Six Hours";
|
||||
"interval.fortyeight.hours"="Forty Eight Hours Hours";
|
||||
"interval.seventytwo.hours"="Seventy Two Hours";
|
||||
"keyboard.type"="Keyboard Typ";
|
||||
"logging"="Logging";
|
||||
"lora"="LoRa";
|
||||
|
|
@ -103,6 +136,38 @@
|
|||
"map"="Mesh Karte";
|
||||
"map.type"="kartentyp";
|
||||
"mesh.log"="Mesh Log";
|
||||
"mesh.log.bluetooth.config %@"="Bluetooth Konfiguration empfangen: %@";
|
||||
"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 %@"="Geräte Konfiguration empfangen: %@";
|
||||
"mesh.log.display.config %@"="Display Konfiguration empfangen: %@";
|
||||
"mesh.log.devicemetadata %@"="Anforderung der Geräte Metadaten für %@";
|
||||
"mesh.log.externalnotification.config %@"="External Notifiation module config received: %@";
|
||||
"mesh.log.lora.config %@"="LoRa config received: %@";
|
||||
"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 %@"="Netzwerk onfiguration empfangen: %@";
|
||||
"mesh.log.nodeinfo.received %@"="Node info empfangen für: %@";
|
||||
"mesh.log.position.config %@"="Positions Konfiguration empfangen: %@";
|
||||
"mesh.log.position.received %@"="Positionspaket empfangen von Node: %@";
|
||||
"mesh.log.rangetest.config %@"="Range Test Modul konfiguration empfangen: %@";
|
||||
"mesh.log.routing.message %@ %@"="Routing empfangen für RequestID: %@ Ack Status: %@";
|
||||
"mesh.log.serial.config %@"="Serial Modul Konfiguration empfangen: %@";
|
||||
"mesh.log.sharelocation %@"="Sent a Position Packet from the Apple device GPS to node: %@";
|
||||
"mesh.log.telemetry.config %@"="Telemetrie Modul Konfiguration empfangen: %@";
|
||||
"mesh.log.telemetry.received %@"="Telemetrie empfangen für: %@";
|
||||
"mesh.log.textmessage.received"="Nachricht von der Textnachricht-App empfangen.";
|
||||
"mesh.log.textmessage.send.failed %@"="Nachricht senden fehlgeschlagen. Nicht korrekt verbunden zu %@";
|
||||
"mesh.log.textmessage.sent %@ %@ %@"="Sende Nachricht %@ von %@ an %@";
|
||||
"mesh.log.traceroute.sent %@"="Sende Traceroute Anforderung zu Mode: %@";
|
||||
"mesh.log.traceroute.received.direct %@"="Traceroute Anforderung an node gesendet: %@ wurde direkt empfangen.";
|
||||
"mesh.log.traceroute.received.route %@"="Traceroute Ergebnis: %@";
|
||||
"mesh.log.wantconfig %@"="Issuing Want Config to %@";
|
||||
"mesh.log.waypoint.sent %@"="Sent a Waypoint Packet from: %@";
|
||||
"message"="Nachricht";
|
||||
"message.details"="Nachrichtendetails";
|
||||
"messages"="Nachrichten";
|
||||
|
|
@ -131,8 +196,11 @@
|
|||
"radio.configuration"="Geräteeinstellungen";
|
||||
"range.test"="Entfernungstest";
|
||||
"range.test.config"="Entfernungstest Konfiguration";
|
||||
"reboot"="Reboot";
|
||||
"reboot.node"="Node neustarten?";
|
||||
"reply"="Antworten";
|
||||
"received.ack"="Empfangsbestätigung";
|
||||
"received.ack.real"="Recipient Ack";
|
||||
"routing.acknowledged"="Bestätigt";
|
||||
"routing.noroute"="Keine Route";
|
||||
"routing.gotnak"="Negative Empfangsbestätigung empfangen";
|
||||
|
|
@ -147,6 +215,7 @@
|
|||
"routing.notauthorized"="Nicht authorisiert";
|
||||
"satellite"="Satellit";
|
||||
"save"="Speichern";
|
||||
"save.config %@"="Save Config for %@";
|
||||
"serial"="Serial";
|
||||
"serial.config"="Serial Konfiguration";
|
||||
"serial.mode.default"="Standard";
|
||||
|
|
@ -175,9 +244,14 @@
|
|||
"telemetry"="Telemetrie (Sensoren)";
|
||||
"telemetry.config"="Telemetrie Einstellungen";
|
||||
"timeout"="Zeitlimit erreicht";
|
||||
"timestamp"="Timestamp";
|
||||
"twitter"="Twitter";
|
||||
"unknown"="Unknown";
|
||||
"unknown.age"="Unbekanntes alter";
|
||||
"unset"="Unset";
|
||||
"update.firmware"="Update Your Firmware";
|
||||
"update.interval"="Update intervall";
|
||||
"user"="Benutzer";
|
||||
"user.details"="Benutzer Details";
|
||||
"voltage"="Voltage";
|
||||
"waiting"="Warte...";
|
||||
|
|
|
|||
|
|
@ -10,18 +10,28 @@
|
|||
"admin"="Admin";
|
||||
"admin.log"="Admin Message Log";
|
||||
"ago"="ago";
|
||||
"airtime"="Airtime";
|
||||
"always.on"="Always On";
|
||||
"app.settings"="App Settings";
|
||||
"are.you.sure"="Are you sure?";
|
||||
"ascii.capable"="ASCII Capable";
|
||||
"available.radios"="Available Radios";
|
||||
"automatic.detection"="Automatic Detection";
|
||||
"battery.level"="Battery Level";
|
||||
"battery.level.trend"="Battery Level Trend";
|
||||
"ble.name"="BLE Name";
|
||||
"ble.connection.timeout %d %@"="Connection failed after %d attempts to connect to %@. You may need to forget your device under Settings > Bluetooth.";
|
||||
"ble.errorcode.6 %@"="%@ The app will automatically reconnect to the preferred radio if it come back in range.";
|
||||
"ble.errorcode.14 %@"="%@ This error usually cannot be fixed without forgetting the device unders Settings > Bluetooth and re-connecting to the radio.";
|
||||
"ble.errorcode.pin %@"="%@ Please try connecting again and check the PIN carefully.";
|
||||
"bluetooth"="Bluetooth";
|
||||
"bluetooth.off"="Bluetooth is off";
|
||||
"bluetooth.config"="Bluetooth Config";
|
||||
"bluetooth.mode.randompin"="Random PIN";
|
||||
"bluetooth.mode.fixedpin"="Fixed PIN";
|
||||
"bluetooth.mode.nopin"="No PIN (Just Works)";
|
||||
"bluetooth.pairingmode"="Pairing Mode";
|
||||
"bluetooth.pin.validation"="BLE Pin must be 6 digits long.";
|
||||
"bytes"="Bytes";
|
||||
"cancel"="Cancel";
|
||||
"canned.messages"="Canned Messages";
|
||||
|
|
@ -33,18 +43,25 @@
|
|||
"channel.role.disabled"="Disabled";
|
||||
"channel.role.primary"="Primary";
|
||||
"channel.role.secondary"="Secondary";
|
||||
"channel.utilization"="Channel Utilization";
|
||||
"channels"="Channels";
|
||||
"clear.app.data"="Clear App Data";
|
||||
"clear.log"="Clear Log";
|
||||
"close"="Close";
|
||||
"config.save.confirm"="After config values save the node will reboot.";
|
||||
"connected.radio"="Connected Radio";
|
||||
"communicating"="Communicating with device. .";
|
||||
"connected"="Currently Connected";
|
||||
"connecting"="Connecting . .";
|
||||
"contacts"="Contacts";
|
||||
"copy"="Copy";
|
||||
"current"="Current";
|
||||
"default"="Default";
|
||||
"delete"="Delete";
|
||||
"device"="Device";
|
||||
"device.config"="Device Config";
|
||||
"device.metrics.delete"="Delete all device metrics?";
|
||||
"device.metrics.log"="Device Metrics Log";
|
||||
"device.role.client"="Client (default) - App connected client.";
|
||||
"device.role.clientmute"="Client Mute - Same as a client except packets will not hop over this node, does not contribute to routing packets for mesh.";
|
||||
"device.role.router"="Router - Mesh packets will prefer to be routed over this node. This node will not be used by client apps. The wifi/ble radios and the oled screen will be put to sleep.";
|
||||
|
|
@ -58,9 +75,14 @@
|
|||
"echo"="Echo";
|
||||
"email.address"="Email Address";
|
||||
"enabled"="Enabled";
|
||||
"encrypted"="Encrypted";
|
||||
"external.notification"="External Notification";
|
||||
"external.notification.config"="External Notification Config";
|
||||
"firmware.version"="Firmware Version";
|
||||
"firmware.version.unsupported"="Unsupported Firmware Version Detected, unable to connect to device.";
|
||||
"gas"="Gas";
|
||||
"gas.resistance"="Gas Resistance";
|
||||
"generate.qr.code"="Generate QR Code";
|
||||
"gpsformat.dec"="Decimal Degrees Format";
|
||||
"gpsformat.dms"="Degrees Minutes Seconds";
|
||||
"gpsformat.utm"="Universal Transverse Mercator";
|
||||
|
|
@ -70,6 +92,7 @@
|
|||
"heard"="Heard";
|
||||
"heard.last"="Last Heard";
|
||||
"hybrid"="Hybrid";
|
||||
"include"="Include";
|
||||
"inputevent.none"="None";
|
||||
"inputevent.up"="Up";
|
||||
"inputevent.down"="Down";
|
||||
|
|
@ -80,6 +103,8 @@
|
|||
"inputevent.cancel"="Cancel";
|
||||
"interval.one.second"="One Second";
|
||||
"interval.two.seconds"="Two Seconds";
|
||||
"interval.three.seconds"="Three Seconds";
|
||||
"interval.four.seconds"="Four Seconds";
|
||||
"interval.five.seconds"="Five Seconds";
|
||||
"interval.ten.seconds"="Ten Seconds";
|
||||
"interval.fifteen.seconds"="Fifteen Seconds";
|
||||
|
|
@ -93,9 +118,17 @@
|
|||
"interval.fifteen.minutes"="Fifteen Minutes";
|
||||
"interval.thirty.minutes"="Thirty Minutes";
|
||||
"interval.one.hour"="One Hour";
|
||||
"interval.two.hours"="Two Hours";
|
||||
"interval.three.hours"="Three Hours";
|
||||
"interval.four.hours"="Four Hours";
|
||||
"interval.five.hours"="Five Hours";
|
||||
"interval.six.hours"="Six Hours";
|
||||
"interval.twelve.hours"="Twelve Hours";
|
||||
"interval.eighteen.hours"="Eighteen Hours";
|
||||
"interval.twentyfour.hours"="Twenty Four Hours";
|
||||
"interval.thirtysix.hours"="Thirty Six Hours";
|
||||
"interval.fortyeight.hours"="Forty Eight Hours Hours";
|
||||
"interval.seventytwo.hours"="Seventy Two Hours";
|
||||
"keyboard.type"="Keyboard Type";
|
||||
"logging"="Logging";
|
||||
"lora"="LoRa";
|
||||
|
|
@ -103,6 +136,38 @@
|
|||
"map"="Mesh Map";
|
||||
"map.type"="Map Type";
|
||||
"mesh.log"="Mesh Log";
|
||||
"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 %@"="Device config received: %@";
|
||||
"mesh.log.display.config %@"="Display config received: %@";
|
||||
"mesh.log.devicemetadata %@"="Requesting Device Metadata for %@";
|
||||
"mesh.log.externalnotification.config %@"="External Notifiation module config received: %@";
|
||||
"mesh.log.lora.config %@"="LoRa config received: %@";
|
||||
"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 %@"="Network config received: %@";
|
||||
"mesh.log.nodeinfo.received %@"="Node info received for: %@";
|
||||
"mesh.log.position.config %@"="Positon config received: %@";
|
||||
"mesh.log.position.received %@"="Position Packet received from node: %@";
|
||||
"mesh.log.rangetest.config %@"="Range Test module config received: %@";
|
||||
"mesh.log.routing.message %@ %@"="Routing received for RequestID: %@ Ack Status: %@";
|
||||
"mesh.log.serial.config %@"="Serial module config received: %@";
|
||||
"mesh.log.sharelocation %@"="Sent a Position Packet from the Apple device GPS to node: %@";
|
||||
"mesh.log.telemetry.config %@"="Telemetry module config received: %@";
|
||||
"mesh.log.telemetry.received %@"="Telemetry received for: %@";
|
||||
"mesh.log.textmessage.received"="Message received from the text message app.";
|
||||
"mesh.log.textmessage.send.failed %@"="Message Send Failed, not properly connected to %@";
|
||||
"mesh.log.textmessage.sent %@ %@ %@"="Sent message %@ from %@ to %@";
|
||||
"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: %@";
|
||||
"message"="Message";
|
||||
"message.details"="Message Details";
|
||||
"messages"="Messages";
|
||||
|
|
@ -132,7 +197,10 @@
|
|||
"range.test"="Range Test";
|
||||
"range.test.config"="Range Test Config";
|
||||
"reply"="Reply";
|
||||
"reboot"="Reboot";
|
||||
"reboot.node"="Reboot node?";
|
||||
"received.ack"="Received Ack";
|
||||
"received.ack.real"="Recipient Ack";
|
||||
"routing.acknowledged"="Acknowledged";
|
||||
"routing.noroute"="No Route";
|
||||
"routing.gotnak"="Received a negative acknowledgment";
|
||||
|
|
@ -147,6 +215,7 @@
|
|||
"routing.notauthorized"="Not Authorized";
|
||||
"satellite"="Satellite";
|
||||
"save"="Save";
|
||||
"save.config %@"="Save Config for %@";
|
||||
"serial"="Serial";
|
||||
"serial.config"="Serial Config";
|
||||
"serial.mode.default"="Default";
|
||||
|
|
@ -174,10 +243,15 @@
|
|||
"tapback.poop"="Poop";
|
||||
"telemetry"="Telemetry (Sensors)";
|
||||
"telemetry.config"="Telemetry Config";
|
||||
"timeout"="timeout";
|
||||
"timeout"="Timeout";
|
||||
"timestamp"="Timestamp";
|
||||
"twitter"="Twitter";
|
||||
"unknown"="Unknown";
|
||||
"unknown.age"="Unknown Age";
|
||||
"unset"="Unset";
|
||||
"update.firmware"="Update Your Firmware";
|
||||
"update.interval"="Update Interval";
|
||||
"user"="User";
|
||||
"user.details"="User Details";
|
||||
"voltage"="Voltage";
|
||||
"waiting"="Waiting. . .";
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue