Merge pull request #327 from meshtastic/2.0.22_Working_Changes

2.0.22 Working Changes
This commit is contained in:
Garth Vander Houwen 2023-03-06 16:49:51 -08:00 committed by GitHub
commit a77442a3d0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
108 changed files with 2972 additions and 3192 deletions

View file

@ -9,8 +9,6 @@
/* Begin PBXBuildFile section */
C9697F9D279336B700250207 /* LocalMBTileOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9697F9C279336B700250207 /* LocalMBTileOverlay.swift */; };
C9697FA527933B8C00250207 /* SQLite in Frameworks */ = {isa = PBXBuildFile; productRef = C9697FA427933B8C00250207 /* SQLite */; };
C9A7BC1027759A9600760B50 /* PositionAnnotationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9A7BC0F27759A9600760B50 /* PositionAnnotationView.swift */; };
C9A88B55278B503C00BD810A /* MapViewModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9A88B54278B503C00BD810A /* MapViewModule.swift */; };
DD0F791B28713C8A00A6FDAD /* AdminMessageList.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD0F791A28713C8A00A6FDAD /* AdminMessageList.swift */; };
DD1925B728CDA5A400720036 /* CannedMessagesConfigEnums.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1925B628CDA5A400720036 /* CannedMessagesConfigEnums.swift */; };
DD1925B928CDA93900720036 /* SerialConfigEnums.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1925B828CDA93900720036 /* SerialConfigEnums.swift */; };
@ -97,7 +95,7 @@
DDB6ABD628AE742000384BA1 /* BluetoothConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB6ABD528AE742000384BA1 /* BluetoothConfig.swift */; };
DDB6ABD928B0A4BA00384BA1 /* BluetoothModes.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB6ABD828B0A4BA00384BA1 /* BluetoothModes.swift */; };
DDB6ABDB28B0AC6000384BA1 /* DistanceText.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB6ABDA28B0AC6000384BA1 /* DistanceText.swift */; };
DDB6ABE028B13AC700384BA1 /* DeviceRoles.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB6ABDF28B13AC700384BA1 /* DeviceRoles.swift */; };
DDB6ABE028B13AC700384BA1 /* DeviceEnums.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB6ABDF28B13AC700384BA1 /* DeviceEnums.swift */; };
DDB6ABE228B13FB500384BA1 /* PositionConfigEnums.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB6ABE128B13FB500384BA1 /* PositionConfigEnums.swift */; };
DDB6ABE428B13FFF00384BA1 /* DisplayEnums.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB6ABE328B13FFF00384BA1 /* DisplayEnums.swift */; };
DDB6ABE628B1406100384BA1 /* LoraConfigEnums.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB6ABE528B1406100384BA1 /* LoraConfigEnums.swift */; };
@ -170,8 +168,6 @@
/* Begin PBXFileReference section */
A65FA974296876BF00A97686 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = "<group>"; };
C9697F9C279336B700250207 /* LocalMBTileOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalMBTileOverlay.swift; sourceTree = "<group>"; };
C9A7BC0F27759A9600760B50 /* PositionAnnotationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PositionAnnotationView.swift; sourceTree = "<group>"; };
C9A88B54278B503C00BD810A /* MapViewModule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MapViewModule.swift; sourceTree = "<group>"; };
DD0F791A28713C8A00A6FDAD /* AdminMessageList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdminMessageList.swift; sourceTree = "<group>"; };
DD1925B628CDA5A400720036 /* CannedMessagesConfigEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CannedMessagesConfigEnums.swift; sourceTree = "<group>"; };
DD1925B828CDA93900720036 /* SerialConfigEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SerialConfigEnums.swift; sourceTree = "<group>"; };
@ -264,7 +260,7 @@
DDB6ABD528AE742000384BA1 /* BluetoothConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BluetoothConfig.swift; sourceTree = "<group>"; };
DDB6ABD828B0A4BA00384BA1 /* BluetoothModes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BluetoothModes.swift; sourceTree = "<group>"; };
DDB6ABDA28B0AC6000384BA1 /* DistanceText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DistanceText.swift; sourceTree = "<group>"; };
DDB6ABDF28B13AC700384BA1 /* DeviceRoles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceRoles.swift; sourceTree = "<group>"; };
DDB6ABDF28B13AC700384BA1 /* DeviceEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceEnums.swift; sourceTree = "<group>"; };
DDB6ABE128B13FB500384BA1 /* PositionConfigEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PositionConfigEnums.swift; sourceTree = "<group>"; };
DDB6ABE328B13FFF00384BA1 /* DisplayEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplayEnums.swift; sourceTree = "<group>"; };
DDB6ABE528B1406100384BA1 /* LoraConfigEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoraConfigEnums.swift; sourceTree = "<group>"; };
@ -290,6 +286,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>"; };
DDDD527729B5B83F0045BC3C /* MeshtasticDataModelV9.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV9.xcdatamodel; sourceTree = "<group>"; };
DDDE59F429AF163D00490C6C /* WidgetsExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = WidgetsExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
DDDE59F829AF163D00490C6C /* WidgetsBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetsBundle.swift; sourceTree = "<group>"; };
DDDE59FA29AF163D00490C6C /* WidgetsLiveActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetsLiveActivity.swift; sourceTree = "<group>"; };
@ -355,8 +352,6 @@
C9697F9C279336B700250207 /* LocalMBTileOverlay.swift */,
DD964FC32974767D007C176F /* MapViewFitExtension.swift */,
DD2AD8A7296D2DF9001FF0E7 /* MapViewSwiftUI.swift */,
C9A88B54278B503C00BD810A /* MapViewModule.swift */,
C9A7BC0F27759A9600760B50 /* PositionAnnotationView.swift */,
);
path = Custom;
sourceTree = "<group>";
@ -478,7 +473,7 @@
DDB6ABD828B0A4BA00384BA1 /* BluetoothModes.swift */,
DD1925B628CDA5A400720036 /* CannedMessagesConfigEnums.swift */,
DDA1C48D28DB49D3009933EC /* ChannelRoles.swift */,
DDB6ABDF28B13AC700384BA1 /* DeviceRoles.swift */,
DDB6ABDF28B13AC700384BA1 /* DeviceEnums.swift */,
DDB6ABE328B13FFF00384BA1 /* DisplayEnums.swift */,
DD5D0A9B2931B9F200F7EA61 /* EthernetModes.swift */,
DDB6ABE528B1406100384BA1 /* LoraConfigEnums.swift */,
@ -866,7 +861,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "if which swiftlint >/dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n";
shellScript = "if which swiftlint >/dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https: //github.com/realm/SwiftLint\"\nfi\n";
};
/* End PBXShellScriptBuildPhase section */
@ -922,7 +917,6 @@
DD23A50F26FD1B4400D9B90C /* PeripheralModel.swift in Sources */,
DDB6ABDB28B0AC6000384BA1 /* DistanceText.swift in Sources */,
DD5E520D298EE33B00D21B61 /* storeforward.pb.swift in Sources */,
C9A7BC1027759A9600760B50 /* PositionAnnotationView.swift in Sources */,
DD882F5D2772E4640005BF05 /* Contacts.swift in Sources */,
DD964FC2297272AE007C176F /* WaypointEntityExtension.swift in Sources */,
DD47E3CE26F103C600029299 /* NodeList.swift in Sources */,
@ -934,10 +928,9 @@
DD2553572855B02500E55709 /* LoRaConfig.swift in Sources */,
DDB6ABD928B0A4BA00384BA1 /* BluetoothModes.swift in Sources */,
DDD9E4E4284B208E003777C5 /* UserEntityExtension.swift in Sources */,
C9A88B55278B503C00BD810A /* MapViewModule.swift in Sources */,
DD2553592855B52700E55709 /* PositionConfig.swift in Sources */,
DD97E96828EFE9A00056DDA4 /* About.swift in Sources */,
DDB6ABE028B13AC700384BA1 /* DeviceRoles.swift in Sources */,
DDB6ABE028B13AC700384BA1 /* DeviceEnums.swift in Sources */,
DD86D40C287F401000BAEB7A /* SaveChannelQRCode.swift in Sources */,
DDA1C48E28DB49D3009933EC /* ChannelRoles.swift in Sources */,
DD8ED9C8289CE4B900B3B0AB /* RoutingError.swift in Sources */,
@ -1187,7 +1180,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 2.0.21;
MARKETING_VERSION = 2.0.22;
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTS_MACCATALYST = YES;
@ -1221,7 +1214,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 2.0.21;
MARKETING_VERSION = 2.0.22;
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTS_MACCATALYST = YES;
@ -1468,6 +1461,7 @@
DD3CC6BA28E366DF00FA9159 /* Meshtastic.xcdatamodeld */ = {
isa = XCVersionGroup;
children = (
DDDD527729B5B83F0045BC3C /* MeshtasticDataModelV9.xcdatamodel */,
DDBA45EC299ED78100DEEDDC /* MeshtasticDataModelV8.xcdatamodel */,
DD5E51CC2986643400D21B61 /* MeshtasticDataModelV7.xcdatamodel */,
DD964FC029724F6D007C176F /* MeshtasticDataModelV6.xcdatamodel */,
@ -1477,7 +1471,7 @@
DD5D0A9A2931AD6B00F7EA61 /* MeshtasticDataModelV2.xcdatamodel */,
DD3CC6BB28E366DF00FA9159 /* MeshtasticDataModel.xcdatamodel */,
);
currentVersion = DDBA45EC299ED78100DEEDDC /* MeshtasticDataModelV8.xcdatamodel */;
currentVersion = DDDD527729B5B83F0045BC3C /* MeshtasticDataModelV9.xcdatamodel */;
name = Meshtastic.xcdatamodeld;
path = Meshtastic/Meshtastic.xcdatamodeld;
sourceTree = "<group>";

View file

@ -18,73 +18,66 @@ enum KeyboardType: Int, CaseIterable, Identifiable {
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")
}
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 CenteringMode: Int, CaseIterable, Identifiable {
case allAnnotations = 0
case allPositions = 1
var id: Int { self.rawValue }
var description: String {
get {
switch self {
case .allAnnotations:
return "All Annotations"// NSLocalizedString("default", comment: "Default Keyboard")
case .allPositions:
return "All Node Postions"// NSLocalizedString("ascii.capable", comment: "ASCII Capable Keyboard")
}
switch self {
case .allAnnotations:
return "All Annotations"// NSLocalizedString("default", comment: "Default Keyboard")
case .allPositions:
return "All Node Postions"// NSLocalizedString("ascii.capable", comment: "ASCII Capable Keyboard")
}
}
}
enum MeshMapType: String, CaseIterable, Identifiable {
case standard = "standard"
case mutedStandard = "mutedStandard"
case hybrid = "hybrid"
case hybridFlyover = "hybridFlyover"
case satellite = "satellite"
case satelliteFlyover = "satelliteFlyover"
case standard
case mutedStandard
case hybrid
case hybridFlyover
case satellite
case satelliteFlyover
var id: String { self.rawValue }
var description: String {
get {
switch self {
case .standard:
return NSLocalizedString("standard", comment: "Standard")
case .mutedStandard:
return NSLocalizedString("standard.muted", comment: "Standard Muted")
case .hybrid:
return NSLocalizedString("hybrid", comment: "Hybrid")
case .hybridFlyover:
return NSLocalizedString("hybrid.flyover", comment: "Hybrid Flyover")
case .satellite:
return NSLocalizedString("satellite", comment: "Satellite")
case .satelliteFlyover:
return NSLocalizedString("satellite.flyover", comment: "Satellite Flyover")
}
switch self {
case .standard:
return NSLocalizedString("standard", comment: "Standard")
case .mutedStandard:
return NSLocalizedString("standard.muted", comment: "Standard Muted")
case .hybrid:
return NSLocalizedString("hybrid", comment: "Hybrid")
case .hybridFlyover:
return NSLocalizedString("hybrid.flyover", comment: "Hybrid Flyover")
case .satellite:
return NSLocalizedString("satellite", comment: "Satellite")
case .satelliteFlyover:
return NSLocalizedString("satellite.flyover", comment: "Satellite Flyover")
}
}
func MKMapTypeValue() -> MKMapType {
switch self {
case .standard:
return MKMapType.standard
@ -111,19 +104,17 @@ enum UserTrackingModes: Int, CaseIterable, Identifiable {
var id: Int { self.rawValue }
var description: String {
get {
switch self {
case .none:
return NSLocalizedString("map.usertrackingmode.none", comment: "None")
case .follow:
return NSLocalizedString("map.usertrackingmode.follow", comment: "Follow")
case .followWithHeading:
return NSLocalizedString("map.usertrackingmode.followwithheading", comment: "Follow with Heading")
}
switch self {
case .none:
return NSLocalizedString("map.usertrackingmode.none", comment: "None")
case .follow:
return NSLocalizedString("map.usertrackingmode.follow", comment: "Follow")
case .followWithHeading:
return NSLocalizedString("map.usertrackingmode.followwithheading", comment: "Follow with Heading")
}
}
func MKUserTrackingModeValue() -> MKUserTrackingMode {
switch self {
case .none:
return MKUserTrackingMode.none
@ -148,25 +139,23 @@ enum LocationUpdateInterval: Int, CaseIterable, Identifiable {
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")
}
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")
}
}
}

View file

@ -14,21 +14,17 @@ enum BluetoothModes: Int, CaseIterable, Identifiable {
var id: Int { self.rawValue }
var description: String {
get {
switch self {
case .randomPin:
return NSLocalizedString("bluetooth.mode.randompin", comment: "Random PIN")
case .fixedPin:
return NSLocalizedString("bluetooth.mode.fixedpin", comment: "Fixed PIN")
case .noPin:
return NSLocalizedString("bluetooth.mode.nopin", comment: "No PIN (Just Works)")
}
switch self {
case .randomPin:
return NSLocalizedString("bluetooth.mode.randompin", comment: "Random PIN")
case .fixedPin:
return NSLocalizedString("bluetooth.mode.fixedpin", comment: "Fixed PIN")
case .noPin:
return NSLocalizedString("bluetooth.mode.nopin", comment: "No PIN (Just Works)")
}
}
func protoEnumValue() -> Config.BluetoothConfig.PairingMode {
switch self {
case .randomPin:
return Config.BluetoothConfig.PairingMode.randomPin
case .fixedPin:

View file

@ -7,24 +7,22 @@
import Foundation
// Default of 0 is unset
enum ConfigPresets : Int, CaseIterable, Identifiable {
enum ConfigPresets: Int, CaseIterable, Identifiable {
case unset = 0
case rakRotaryEncoder = 1
case cardKB = 2
var id: Int { self.rawValue }
var description: String {
get {
switch self {
case .unset:
return NSLocalizedString("canned.messages.preset.manual", comment: "Manual Configuration")
case .rakRotaryEncoder:
return NSLocalizedString("canned.messages.preset.rakrotary", comment: "RAK Rotary Encoder Module")
case .cardKB:
return NSLocalizedString("canned.messages.preset.cardkb", comment: "M5 Stack Card KB / RAK Keypad")
}
switch self {
case .unset:
return NSLocalizedString("canned.messages.preset.manual", comment: "Manual Configuration")
case .rakRotaryEncoder:
return NSLocalizedString("canned.messages.preset.rakrotary", comment: "RAK Rotary Encoder Module")
case .cardKB:
return NSLocalizedString("canned.messages.preset.cardkb", comment: "M5 Stack Card KB / RAK Keypad")
}
}
}
@ -43,30 +41,28 @@ enum InputEventChars: Int, CaseIterable, Identifiable {
var id: Int { self.rawValue }
var description: String {
get {
switch self {
case .none:
return NSLocalizedString("inputevent.none", comment: "None")
case .up:
return NSLocalizedString("inputevent.up", comment: "Up")
case .down:
return NSLocalizedString("inputevent.down", comment: "Down")
case .left:
return NSLocalizedString("inputevent.left", comment: "Left")
case .right:
return NSLocalizedString("inputevent.right", comment: "Right")
case .select:
return NSLocalizedString("inputevent.select", comment: "Select")
case .back:
return NSLocalizedString("inputevent.back", comment: "Back")
case .cancel:
return NSLocalizedString("inputevent.cancel", comment: "Cancel")
}
switch self {
case .none:
return NSLocalizedString("inputevent.none", comment: "None")
case .up:
return NSLocalizedString("inputevent.up", comment: "Up")
case .down:
return NSLocalizedString("inputevent.down", comment: "Down")
case .left:
return NSLocalizedString("inputevent.left", comment: "Left")
case .right:
return NSLocalizedString("inputevent.right", comment: "Right")
case .select:
return NSLocalizedString("inputevent.select", comment: "Select")
case .back:
return NSLocalizedString("inputevent.back", comment: "Back")
case .cancel:
return NSLocalizedString("inputevent.cancel", comment: "Cancel")
}
}
func protoEnumValue() -> ModuleConfig.CannedMessageConfig.InputEventChar {
switch self {
case .none:

View file

@ -15,22 +15,20 @@ enum ChannelRoles: Int, CaseIterable, Identifiable {
var id: Int { self.rawValue }
var description: String {
get {
switch self {
case .disabled:
return NSLocalizedString("channel.role.disabled", comment: "Disabled")
case .primary:
return NSLocalizedString("channel.role.primary", comment: "Primary")
case .secondary:
return NSLocalizedString("channel.role.secondary", comment: "Secondary")
}
switch self {
case .disabled:
return NSLocalizedString("channel.role.disabled", comment: "Disabled")
case .primary:
return NSLocalizedString("channel.role.primary", comment: "Primary")
case .secondary:
return NSLocalizedString("channel.role.secondary", comment: "Secondary")
}
}
func protoEnumValue() -> Channel.Role {
switch self {
case .disabled:
return Channel.Role.disabled
case .primary:

View file

@ -0,0 +1,118 @@
//
// DeviceRoles.swift
// Meshtastic
//
// Copyright(c) Garth Vander Houwen 8/20/22.
//
import Foundation
// Default of 0 is Client
enum DeviceRoles: Int, CaseIterable, Identifiable {
case client = 0
case clientMute = 1
case router = 2
case routerClient = 3
case repeater = 4
case tracker = 5
case sensor = 6
var id: Int { self.rawValue }
var name: String {
switch self {
case .client:
return "Client"
case .clientMute:
return "Muted Client"
case .router:
return "Router"
case .routerClient:
return "Router & Client"
case .repeater:
return "Repeater"
case .tracker:
return "Tracker"
case .sensor:
return "Sensor"
}
}
var description: String {
switch self {
case .client:
return NSLocalizedString("device.role.client", comment: "Client (default) - App connected client.")
case .clientMute:
return NSLocalizedString("device.role.clientmute", comment: "Client Mute - Same as a client except packets will not hop over this node, does not contribute to routing packets for mesh.")
case .router:
return NSLocalizedString("device.role.router", comment: "Router - Mesh packets will prefer to be routed over this node. Assumes device will operate in a standalone manner while placed in a location with a coverage advantage. WARNING: The BLE/Wi-Fi radios and the OLED screen will be put to sleep.")
case .routerClient:
return NSLocalizedString("device.role.routerclient", comment: "Router Client - Hybrid of the Client and Router roles. Similar to Router, except the Router Client can be used as both a Router and an app connected Client. BLE/Wi-Fi and OLED screen will not be put to sleep.")
case .repeater:
return NSLocalizedString("device.role.repeater", comment: "Repeater - Mesh packets will prefer to be routed over this node. This role eliminates unnecessary overhead such as NodeInfo, DeviceTelemetry, and any other mesh packet, resulting in the device not appearing as part of the network. Please see Rebroadcast Mode for additional settings specific to this role.")
case .tracker:
return NSLocalizedString("device.role.tracker", comment: "Tracker - For use with devices intended as a GPS tracker. Position packets sent from this device will be higher priority, with position broadcasting every two minutes. Smart Position Broadcast will default to off.")
case .sensor:
return NSLocalizedString("device.role.sensor", comment: "Sensor - For use with remote telemetry sensors. Setting this role will turn on environment telemetry. Telemetry packets sent from this device will be higher priority, with telemetry broadcasting every 7 minutes")
}
}
func protoEnumValue() -> Config.DeviceConfig.Role {
switch self {
case .client:
return Config.DeviceConfig.Role.client
case .clientMute:
return Config.DeviceConfig.Role.clientMute
case .router:
return Config.DeviceConfig.Role.router
case .routerClient:
return Config.DeviceConfig.Role.routerClient
case .repeater:
return Config.DeviceConfig.Role.repeater
case .tracker:
return Config.DeviceConfig.Role.tracker
case .sensor:
return Config.DeviceConfig.Role.sensor
}
}
}
enum RebroadcastModes: Int, CaseIterable, Identifiable {
case all = 0
case allSkipDecoding = 1
case localOnly = 2
var id: Int { self.rawValue }
var name: String {
switch self {
case .all:
return "All"
case .allSkipDecoding:
return "All Skip Decoding"
case .localOnly:
return "Local Only"
}
}
var description: String {
switch self {
case .all:
return "Rebroadcast any observed message, if it was on our private channel or from another mesh with the same lora params."
case .allSkipDecoding:
return "Same as behavior as ALL but skips packet decoding and simply rebroadcasts them. Only available in Repeater role. Setting this on any other roles will result in ALL behavior."
case .localOnly:
return "Inverted top bar for 2 Color display"
}
}
func protoEnumValue() -> Config.DeviceConfig.RebroadcastMode {
switch self {
case .all:
return Config.DeviceConfig.RebroadcastMode.all
case .allSkipDecoding:
return Config.DeviceConfig.RebroadcastMode.allSkipDecoding
case .localOnly:
return Config.DeviceConfig.RebroadcastMode.localOnly
}
}
}

View file

@ -1,84 +0,0 @@
//
// DeviceRoles.swift
// Meshtastic
//
// Copyright(c) Garth Vander Houwen 8/20/22.
//
import Foundation
// Default of 0 is Client
enum DeviceRoles: Int, CaseIterable, Identifiable {
case client = 0
case clientMute = 1
case router = 2
case routerClient = 3
case repeater = 4
case tracker = 5
case sensor = 6
var id: Int { self.rawValue }
var name: String {
get {
switch self {
case .client:
return "Client"
case .clientMute:
return "Muted Client"
case .router:
return "Router"
case .routerClient:
return "Router & Client"
case .repeater:
return "Repeater"
case .tracker:
return "Tracker"
case .sensor:
return "Sensor"
}
}
}
var description: String {
get {
switch self {
case .client:
return NSLocalizedString("device.role.client", comment: "Client (default) - App connected client.")
case .clientMute:
return NSLocalizedString("device.role.clientmute", comment: "Client Mute - Same as a client except packets will not hop over this node, does not contribute to routing packets for mesh.")
case .router:
return NSLocalizedString("device.role.router", comment: "Router - Mesh packets will prefer to be routed over this node. Assumes device will operate in a standalone manner while placed in a location with a coverage advantage. WARNING: The BLE/Wi-Fi radios and the OLED screen will be put to sleep.")
case .routerClient:
return NSLocalizedString("device.role.routerclient", comment: "Router Client - Hybrid of the Client and Router roles. Similar to Router, except the Router Client can be used as both a Router and an app connected Client. BLE/Wi-Fi and OLED screen will not be put to sleep.")
case .repeater:
return NSLocalizedString("device.role.repeater", comment: "Repeater - Mesh packets will prefer to be routed over this node. This role eliminates unnecessary overhead such as NodeInfo, DeviceTelemetry, and any other mesh packet, resulting in the device not appearing as part of the network. Please see Rebroadcast Mode for additional settings specific to this role.")
case .tracker:
return NSLocalizedString("device.role.tracker", comment: "Tracker - For use with devices intended as a GPS tracker. Position packets sent from this device will be higher priority, with position broadcasting every two minutes. Smart Position Broadcast will default to off.")
case .sensor:
return NSLocalizedString("device.role.sensor", comment: "Sensor - For use with remote telemetry sensors. Setting this role will turn on environment telemetry. Telemetry packets sent from this device will be higher priority, with telemetry broadcasting every 7 minutes")
}
}
}
func protoEnumValue() -> Config.DeviceConfig.Role {
switch self {
case .client:
return Config.DeviceConfig.Role.client
case .clientMute:
return Config.DeviceConfig.Role.clientMute
case .router:
return Config.DeviceConfig.Role.router
case .routerClient:
return Config.DeviceConfig.Role.routerClient
case .repeater:
return Config.DeviceConfig.Role.repeater
case .tracker:
return Config.DeviceConfig.Role.tracker
case .sensor:
return Config.DeviceConfig.Role.sensor
}
}
}

View file

@ -8,23 +8,21 @@
import Foundation
enum ScreenUnits: Int, CaseIterable, Identifiable {
case metric = 0
case imperial = 1
var id: Int { self.rawValue }
var description: String {
get {
switch self {
case .metric:
return "Metric"
case .imperial:
return "Imperial"
}
switch self {
case .metric:
return "Metric"
case .imperial:
return "Imperial"
}
}
func protoEnumValue() -> Config.DisplayConfig.DisplayUnits {
switch self {
case .metric:
return Config.DisplayConfig.DisplayUnits.metric
@ -36,6 +34,8 @@ enum ScreenUnits: Int, CaseIterable, Identifiable {
enum ScreenOnIntervals: Int, CaseIterable, Identifiable {
case fifteenSeconds = 15
case thirtySeconds = 30
case oneMinute = 60
case fiveMinutes = 300
case tenMinutes = 600
@ -46,23 +46,25 @@ enum ScreenOnIntervals: Int, CaseIterable, Identifiable {
var id: Int { self.rawValue }
var description: String {
get {
switch self {
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 .max:
return NSLocalizedString("always.on", comment: "Always On")
}
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 .max:
return NSLocalizedString("always.on", comment: "Always On")
}
}
}
@ -79,24 +81,21 @@ enum ScreenCarouselIntervals: Int, CaseIterable, Identifiable {
var id: Int { self.rawValue }
var description: String {
get {
switch self {
case .off:
return NSLocalizedString("off", comment: "Off")
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")
}
switch self {
case .off:
return NSLocalizedString("off", comment: "Off")
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")
}
}
}
// Default of 0 is auto
enum OledTypes: Int, CaseIterable, Identifiable {
@ -108,21 +107,19 @@ enum OledTypes: Int, CaseIterable, Identifiable {
var id: Int { self.rawValue }
var description: String {
get {
switch self {
case .auto:
return NSLocalizedString("automatic.detection", comment: "Automatic Detection")
case .ssd1306:
return "SSD 1306"
case .sh1106:
return "SH 1106"
case .sh1107:
return "SH 1107"
}
switch self {
case .auto:
return NSLocalizedString("automatic.detection", comment: "Automatic Detection")
case .ssd1306:
return "SSD 1306"
case .sh1106:
return "SH 1106"
case .sh1107:
return "SH 1107"
}
}
func protoEnumValue() -> Config.DisplayConfig.OledType {
switch self {
case .auto:
return Config.DisplayConfig.OledType.oledAuto
@ -146,21 +143,19 @@ enum DisplayModes: Int, CaseIterable, Identifiable {
var id: Int { self.rawValue }
var description: String {
get {
switch self {
case .defaultMode:
return "Default 128x64 screen layout"
case .twoColor:
return "Optimized for 2 color displays"
case .inverted:
return "Inverted top bar for 2 Color display"
case .color:
return "TFT Full Color Displays"
}
switch self {
case .defaultMode:
return "Default 128x64 screen layout"
case .twoColor:
return "Optimized for 2 color displays"
case .inverted:
return "Inverted top bar for 2 Color display"
case .color:
return "TFT Full Color Displays"
}
}
func protoEnumValue() -> Config.DisplayConfig.DisplayMode {
switch self {
case .defaultMode:
return Config.DisplayConfig.DisplayMode.default

View file

@ -14,20 +14,18 @@ enum EthernetMode: Int, CaseIterable, Identifiable {
var id: Int { self.rawValue }
var description: String {
get {
switch self {
case .dhcp:
return "DHCP"
case .staticip:
return "Static IP"
}
switch self {
case .dhcp:
return "DHCP"
case .staticip:
return "Static IP"
}
}
func protoEnumValue() -> Config.NetworkConfig.AddressMode {
switch self {
case .dhcp:
return Config.NetworkConfig.AddressMode.dhcp
case .staticip:

View file

@ -8,7 +8,7 @@
import Foundation
enum OutputIntervals: Int, CaseIterable, Identifiable {
case unset = 0
case oneSecond = 1000
case twoSeconds = 2000
@ -19,33 +19,31 @@ enum OutputIntervals: Int, CaseIterable, Identifiable {
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")
}
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")
}
}
}
@ -63,30 +61,27 @@ enum SenderIntervals: Int, CaseIterable, Identifiable {
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")
}
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")
}
}
}
@ -116,50 +111,48 @@ enum UpdateIntervals: Int, CaseIterable, Identifiable {
var id: Int { self.rawValue }
var description: String {
get {
switch self {
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")
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")
}
switch self {
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")
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")
}
}
}

View file

@ -7,7 +7,7 @@
import Foundation
enum RegionCodes : Int, CaseIterable, Identifiable {
enum RegionCodes: Int, CaseIterable, Identifiable {
case unset = 0
case us = 1
@ -28,174 +28,131 @@ enum RegionCodes : Int, CaseIterable, Identifiable {
var id: Int { self.rawValue }
var description: String {
get {
switch self {
case .unset:
return "Please set a region"
case .us:
return "United States"
case .eu433:
return "European Union 433mhz"
case .eu868:
return "European Union 868mhz"
case .cn:
return "China"
case .jp:
return "Japan"
case .anz:
return "Australia / New Zealand"
case .kr:
return "Korea"
case .tw:
return "Taiwan"
case .ru:
return "Russia"
case .in:
return "India"
case .nz865:
return "New Zealand 865mhz"
case .th:
return "Thailand"
case .ua433:
return "Ukraine 433mhz"
case .ua868:
return "Ukraine 868mhz"
case .lora24:
return "2.4 GHZ"
}
switch self {
case .unset:
return "Please set a region"
case .us:
return "United States"
case .eu433:
return "European Union 433mhz"
case .eu868:
return "European Union 868mhz"
case .cn:
return "China"
case .jp:
return "Japan"
case .anz:
return "Australia / New Zealand"
case .kr:
return "Korea"
case .tw:
return "Taiwan"
case .ru:
return "Russia"
case .in:
return "India"
case .nz865:
return "New Zealand 865mhz"
case .th:
return "Thailand"
case .ua433:
return "Ukraine 433mhz"
case .ua868:
return "Ukraine 868mhz"
case .lora24:
return "2.4 GHZ"
}
}
func protoEnumValue() -> Config.LoRaConfig.RegionCode {
switch self {
case .unset:
return Config.LoRaConfig.RegionCode.unset
case .us:
return Config.LoRaConfig.RegionCode.us
case .eu433:
return Config.LoRaConfig.RegionCode.eu433
case .eu868:
return Config.LoRaConfig.RegionCode.eu868
case .cn:
return Config.LoRaConfig.RegionCode.cn
case .jp:
return Config.LoRaConfig.RegionCode.jp
case .anz:
return Config.LoRaConfig.RegionCode.anz
case .kr:
return Config.LoRaConfig.RegionCode.kr
case .tw:
return Config.LoRaConfig.RegionCode.tw
case .ru:
return Config.LoRaConfig.RegionCode.ru
case .in:
return Config.LoRaConfig.RegionCode.in
case .nz865:
return Config.LoRaConfig.RegionCode.nz865
case .th:
return Config.LoRaConfig.RegionCode.th
case .ua433:
return Config.LoRaConfig.RegionCode.ua433
case .ua868:
return Config.LoRaConfig.RegionCode.ua868
case .lora24:
return Config.LoRaConfig.RegionCode.lora24
case .unset:
return Config.LoRaConfig.RegionCode.unset
case .us:
return Config.LoRaConfig.RegionCode.us
case .eu433:
return Config.LoRaConfig.RegionCode.eu433
case .eu868:
return Config.LoRaConfig.RegionCode.eu868
case .cn:
return Config.LoRaConfig.RegionCode.cn
case .jp:
return Config.LoRaConfig.RegionCode.jp
case .anz:
return Config.LoRaConfig.RegionCode.anz
case .kr:
return Config.LoRaConfig.RegionCode.kr
case .tw:
return Config.LoRaConfig.RegionCode.tw
case .ru:
return Config.LoRaConfig.RegionCode.ru
case .in:
return Config.LoRaConfig.RegionCode.in
case .nz865:
return Config.LoRaConfig.RegionCode.nz865
case .th:
return Config.LoRaConfig.RegionCode.th
case .ua433:
return Config.LoRaConfig.RegionCode.ua433
case .ua868:
return Config.LoRaConfig.RegionCode.ua868
case .lora24:
return Config.LoRaConfig.RegionCode.lora24
}
}
}
enum ModemPresets : Int, CaseIterable, Identifiable {
case LongFast = 0
case LongSlow = 1
case LongModerate = 7
case VLongSlow = 2
case MedSlow = 3
case MedFast = 4
case ShortSlow = 5
case ShortFast = 6
enum ModemPresets: Int, CaseIterable, Identifiable {
case longFast = 0
case longSlow = 1
case longModerate = 7
case vLongSlow = 2
case medSlow = 3
case medFast = 4
case shortSlow = 5
case shortFast = 6
var id: Int { self.rawValue }
var description: String {
get {
switch self {
case .LongFast:
return "Long Range - Fast"
case .LongSlow:
return "Long Range - Slow"
case .LongModerate:
return "Long Range - Moderate"
case .VLongSlow:
return "Very Long Range - Slow"
case .MedSlow:
return "Medium Range - Slow"
case .MedFast:
return "Medium Range - Fast"
case .ShortSlow:
return "Short Range - Slow"
case .ShortFast:
return "Short Range - Fast"
}
switch self {
case .longFast:
return "Long Range - Fast"
case .longSlow:
return "Long Range - Slow"
case .longModerate:
return "Long Range - Moderate"
case .vLongSlow:
return "Very Long Range - Slow"
case .medSlow:
return "Medium Range - Slow"
case .medFast:
return "Medium Range - Fast"
case .shortSlow:
return "Short Range - Slow"
case .shortFast:
return "Short Range - Fast"
}
}
func protoEnumValue() -> Config.LoRaConfig.ModemPreset {
switch self {
case .LongFast:
return Config.LoRaConfig.ModemPreset.longFast
case .LongSlow:
return Config.LoRaConfig.ModemPreset.longSlow
case .LongModerate:
return Config.LoRaConfig.ModemPreset.longModerate
case .VLongSlow:
return Config.LoRaConfig.ModemPreset.veryLongSlow
case .MedSlow:
return Config.LoRaConfig.ModemPreset.mediumSlow
case .MedFast:
return Config.LoRaConfig.ModemPreset.mediumFast
case .ShortSlow:
return Config.LoRaConfig.ModemPreset.shortSlow
case .ShortFast:
return Config.LoRaConfig.ModemPreset.shortFast
}
}
}
enum HopValues : Int, CaseIterable, Identifiable {
case oneHop = 1
case twoHops = 2
case threeHops = 3
case fourHops = 4
case fiveHops = 5
case sixHops = 6
case sevenHops = 7
var id: Int { self.rawValue }
var description: String {
get {
switch self {
case .oneHop:
return "One Hop"
case .twoHops:
return "Two Hops"
case .threeHops:
return "Three Hops"
case .fourHops:
return "Four Hops"
case .fiveHops:
return "Five Hops"
case .sixHops:
return "Six Hops"
case .sevenHops:
return "Seven Hops"
}
case .longFast:
return Config.LoRaConfig.ModemPreset.longFast
case .longSlow:
return Config.LoRaConfig.ModemPreset.longSlow
case .longModerate:
return Config.LoRaConfig.ModemPreset.longModerate
case .vLongSlow:
return Config.LoRaConfig.ModemPreset.veryLongSlow
case .medSlow:
return Config.LoRaConfig.ModemPreset.mediumSlow
case .medFast:
return Config.LoRaConfig.ModemPreset.mediumFast
case .shortSlow:
return Config.LoRaConfig.ModemPreset.shortSlow
case .shortFast:
return Config.LoRaConfig.ModemPreset.shortFast
}
}
}

View file

@ -23,43 +23,39 @@ enum Tapbacks: Int, CaseIterable, Identifiable {
var id: Int { self.rawValue }
var emojiString: String {
get {
switch self {
case .heart:
return "❤️"
case .thumbsUp:
return "👍"
case .thumbsDown:
return "👎"
case .haHa:
return "🤣"
case .exclamation:
return "‼️"
case .question:
return ""
case .poop:
return "💩"
}
switch self {
case .heart:
return "❤️"
case .thumbsUp:
return "👍"
case .thumbsDown:
return "👎"
case .haHa:
return "🤣"
case .exclamation:
return "‼️"
case .question:
return ""
case .poop:
return "💩"
}
}
var description: String {
get {
switch self {
case .heart:
return NSLocalizedString("tapback.heart", comment: "Heart")
case .thumbsUp:
return NSLocalizedString("tapback.thumbsup", comment: "Thumbs Up")
case .thumbsDown:
return NSLocalizedString("tapback.thumbsdown", comment: "Thumbs Down")
case .haHa:
return NSLocalizedString("tapback.haha", comment: "HaHa")
case .exclamation:
return NSLocalizedString("tapback.exclamation", comment: "Exclamation Mark")
case .question:
return NSLocalizedString("tapback.question", comment: "Question Mark")
case .poop:
return NSLocalizedString("tapback.poop", comment: "Poop")
}
switch self {
case .heart:
return NSLocalizedString("tapback.heart", comment: "Heart")
case .thumbsUp:
return NSLocalizedString("tapback.thumbsup", comment: "Thumbs Up")
case .thumbsDown:
return NSLocalizedString("tapback.thumbsdown", comment: "Thumbs Down")
case .haHa:
return NSLocalizedString("tapback.haha", comment: "HaHa")
case .exclamation:
return NSLocalizedString("tapback.exclamation", comment: "Exclamation Mark")
case .question:
return NSLocalizedString("tapback.question", comment: "Question Mark")
case .poop:
return NSLocalizedString("tapback.poop", comment: "Poop")
}
}
}

View file

@ -18,27 +18,25 @@ enum GpsFormats: Int, CaseIterable, Identifiable {
var id: Int { self.rawValue }
var description: String {
get {
switch self {
case .gpsFormatDec:
return NSLocalizedString("gpsformat.dec", comment: "Decimal Degrees Format")
case .gpsFormatDms:
return NSLocalizedString("gpsformat.dms", comment: "Degrees Minutes Seconds")
case .gpsFormatUtm:
return NSLocalizedString("gpsformat.utm", comment: "Universal Transverse Mercator")
case .gpsFormatMgrs:
return NSLocalizedString("gpsformat.mgrs", comment: "Military Grid Reference System")
case .gpsFormatOlc:
return NSLocalizedString("gpsformat.olc", comment: "Open Location Code (aka Plus Codes)")
case .gpsFormatOsgr:
return NSLocalizedString("gpsformat.osgr", comment: "Ordnance Survey Grid Reference")
}
switch self {
case .gpsFormatDec:
return NSLocalizedString("gpsformat.dec", comment: "Decimal Degrees Format")
case .gpsFormatDms:
return NSLocalizedString("gpsformat.dms", comment: "Degrees Minutes Seconds")
case .gpsFormatUtm:
return NSLocalizedString("gpsformat.utm", comment: "Universal Transverse Mercator")
case .gpsFormatMgrs:
return NSLocalizedString("gpsformat.mgrs", comment: "Military Grid Reference System")
case .gpsFormatOlc:
return NSLocalizedString("gpsformat.olc", comment: "Open Location Code (aka Plus Codes)")
case .gpsFormatOsgr:
return NSLocalizedString("gpsformat.osgr", comment: "Ordnance Survey Grid Reference")
}
}
func protoEnumValue() -> Config.DisplayConfig.GpsCoordinateFormat {
switch self {
case .gpsFormatDec:
return Config.DisplayConfig.GpsCoordinateFormat.dec
case .gpsFormatDms:
@ -55,7 +53,6 @@ enum GpsFormats: Int, CaseIterable, Identifiable {
}
}
enum GpsUpdateIntervals: Int, CaseIterable, Identifiable {
case fiveSeconds = 5
@ -78,44 +75,41 @@ enum GpsUpdateIntervals: Int, CaseIterable, Identifiable {
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 .twentySeconds:
return NSLocalizedString("interval.twenty.seconds", comment: "Twenty Seconds")
case .twentyFiveSeconds:
return NSLocalizedString("interval.twentyfive.seconds", comment: "Twenty Five Seconds")
case .thirtySeconds:
return NSLocalizedString("interval.thirty.seconds", comment: "Thirty Seconds")
case .oneMinute:
return NSLocalizedString("interval.one.minute", comment: "One Minute")
case .twoMinutes:
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")
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")
case .maxInt32:
return NSLocalizedString("on.boot", comment: "On Boot Only")
}
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 .twentySeconds:
return NSLocalizedString("interval.twenty.seconds", comment: "Twenty Seconds")
case .twentyFiveSeconds:
return NSLocalizedString("interval.twentyfive.seconds", comment: "Twenty Five Seconds")
case .thirtySeconds:
return NSLocalizedString("interval.thirty.seconds", comment: "Thirty Seconds")
case .oneMinute:
return NSLocalizedString("interval.one.minute", comment: "One Minute")
case .twoMinutes:
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")
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")
case .maxInt32:
return NSLocalizedString("on.boot", comment: "On Boot Only")
}
}
}
@ -137,34 +131,31 @@ enum GpsAttemptTimes: Int, CaseIterable, Identifiable {
var id: Int { self.rawValue }
var description: String {
get {
switch self {
case .twoSeconds:
return NSLocalizedString("interval.two.seconds", comment: "Two 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 .twentySeconds:
return NSLocalizedString("interval.twenty.seconds", comment: "Twenty Seconds")
case .twentyFiveSeconds:
return NSLocalizedString("interval.twentyfive.seconds", comment: "Twenty Five Seconds")
case .thirtySeconds:
return NSLocalizedString("interval.thirty.seconds", comment: "Thirty Seconds")
case .oneMinute:
return NSLocalizedString("interval.one.minute", comment: "One Minute")
case .twoMinutes:
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")
}
switch self {
case .twoSeconds:
return NSLocalizedString("interval.two.seconds", comment: "Two 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 .twentySeconds:
return NSLocalizedString("interval.twenty.seconds", comment: "Twenty Seconds")
case .twentyFiveSeconds:
return NSLocalizedString("interval.twentyfive.seconds", comment: "Twenty Five Seconds")
case .thirtySeconds:
return NSLocalizedString("interval.thirty.seconds", comment: "Thirty Seconds")
case .oneMinute:
return NSLocalizedString("interval.one.minute", comment: "One Minute")
case .twoMinutes:
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")
}
}
}

View file

@ -23,40 +23,38 @@ enum RoutingError: Int, CaseIterable, Identifiable {
var id: Int { self.rawValue }
var display: String {
get {
switch self {
case .none:
return NSLocalizedString("routing.acknowledged", comment: "Acknowledged")
case .noRoute:
return NSLocalizedString("routing.noroute", comment: "No Route")
case .gotNak:
return NSLocalizedString("routing.gotnak", comment: "Received a negative acknowledgment")
case .timeout:
return NSLocalizedString("routing.timeout", comment: "Timeout")
case .noInterface:
return NSLocalizedString("routing.nointerface", comment: "No Interface")
case .maxRetransmit:
return NSLocalizedString("routing.maxretransmit", comment: "Max Retransmission Reached")
case .noChannel:
return NSLocalizedString("routing.nochannel", comment: "No Channel")
case .tooLarge:
return NSLocalizedString("routing.toolarge", comment: "The packet is too large")
case .noResponse:
return NSLocalizedString("routing.noresponse", comment: "No Response")
case .dutyCycleLimit:
return NSLocalizedString("routing.dutycyclelimit", comment: "Regional Duty Cycle Limit Reached")
case .badRequest:
return NSLocalizedString("routing.badRequest", comment: "Bad Request")
case .notAuthorized:
return NSLocalizedString("routing.notauthorized", comment: "Not Authorized")
}
switch self {
case .none:
return NSLocalizedString("routing.acknowledged", comment: "Acknowledged")
case .noRoute:
return NSLocalizedString("routing.noroute", comment: "No Route")
case .gotNak:
return NSLocalizedString("routing.gotnak", comment: "Received a negative acknowledgment")
case .timeout:
return NSLocalizedString("routing.timeout", comment: "Timeout")
case .noInterface:
return NSLocalizedString("routing.nointerface", comment: "No Interface")
case .maxRetransmit:
return NSLocalizedString("routing.maxretransmit", comment: "Max Retransmission Reached")
case .noChannel:
return NSLocalizedString("routing.nochannel", comment: "No Channel")
case .tooLarge:
return NSLocalizedString("routing.toolarge", comment: "The packet is too large")
case .noResponse:
return NSLocalizedString("routing.noresponse", comment: "No Response")
case .dutyCycleLimit:
return NSLocalizedString("routing.dutycyclelimit", comment: "Regional Duty Cycle Limit Reached")
case .badRequest:
return NSLocalizedString("routing.badRequest", comment: "Bad Request")
case .notAuthorized:
return NSLocalizedString("routing.notauthorized", comment: "Not Authorized")
}
}
func protoEnumValue() -> Routing.Error {
switch self {
case .none:
return Routing.Error.none
case .noRoute:
@ -81,7 +79,7 @@ enum RoutingError: Int, CaseIterable, Identifiable {
return Routing.Error.badRequest
case .notAuthorized:
return Routing.Error.notAuthorized
}
}
}

View file

@ -27,49 +27,47 @@ enum SerialBaudRates: Int, CaseIterable, Identifiable {
var id: Int { self.rawValue }
var description: String {
get {
switch self {
switch self {
case .baudDefault:
return NSLocalizedString("default", comment: "Default")
case .baud110:
return "110 Baud"
case .baud300:
return "300 Baud"
case .baud600:
return "600 Baud"
case .baud1200:
return "1200 Baud"
case .baud2400:
return "2400 Baud"
case .baud4800:
return "4800 Baud"
case .baud9600:
return "9600 Baud"
case .baud19200:
return "19200 Baud"
case .baud38400:
return "38400 Baud"
case .baud57600:
return "57600 Baud"
case .baud115200:
return "115200 Baud"
case .baud230400:
return "230400 Baud"
case .baud460800:
return "460800 Baud"
case .baud576000:
return "576000 Baud"
case .baud921600:
return "921600 Baud"
}
case .baudDefault:
return NSLocalizedString("default", comment: "Default")
case .baud110:
return "110 Baud"
case .baud300:
return "300 Baud"
case .baud600:
return "600 Baud"
case .baud1200:
return "1200 Baud"
case .baud2400:
return "2400 Baud"
case .baud4800:
return "4800 Baud"
case .baud9600:
return "9600 Baud"
case .baud19200:
return "19200 Baud"
case .baud38400:
return "38400 Baud"
case .baud57600:
return "57600 Baud"
case .baud115200:
return "115200 Baud"
case .baud230400:
return "230400 Baud"
case .baud460800:
return "460800 Baud"
case .baud576000:
return "576000 Baud"
case .baud921600:
return "921600 Baud"
}
}
func protoEnumValue() -> ModuleConfig.SerialConfig.Serial_Baud {
switch self {
case .baudDefault:
return ModuleConfig.SerialConfig.Serial_Baud.baudDefault
case .baud110:
@ -113,28 +111,26 @@ enum SerialModeTypes: Int, CaseIterable, Identifiable {
case proto = 2
case txtmsg = 3
case nmea = 4
var id: Int { self.rawValue }
var description: String {
get {
switch self {
case .default:
return NSLocalizedString("serial.mode.default", comment: "Default")
case .simple:
return NSLocalizedString("serial.mode.simple", comment: "Simple")
case .proto:
return NSLocalizedString("serial.mode.proto", comment: "Protobufs")
case .txtmsg:
return NSLocalizedString("serial.mode.txtmsg", comment: "Text Message")
case .nmea:
return NSLocalizedString("serial.mode.nmea", comment: "NMEA Positions")
}
switch self {
case .default:
return NSLocalizedString("serial.mode.default", comment: "Default")
case .simple:
return NSLocalizedString("serial.mode.simple", comment: "Simple")
case .proto:
return NSLocalizedString("serial.mode.proto", comment: "Protobufs")
case .txtmsg:
return NSLocalizedString("serial.mode.txtmsg", comment: "Text Message")
case .nmea:
return NSLocalizedString("serial.mode.nmea", comment: "NMEA Positions")
}
}
func protoEnumValue() -> ModuleConfig.SerialConfig.Serial_Mode {
switch self {
case .default:
return ModuleConfig.SerialConfig.Serial_Mode.default
case .simple:
@ -162,25 +158,23 @@ enum SerialTimeoutIntervals: Int, CaseIterable, Identifiable {
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 .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")
}
switch self {
case .unset:
return NSLocalizedString("unset", comment: "Unset")
case .oneSecond:
return NSLocalizedString("interval.one.second", comment: "One Second")
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")
}
}
}

View file

@ -17,24 +17,22 @@ enum WeatherConditions: Int, CaseIterable, Identifiable {
var id: Int { self.rawValue }
var symbolName: String {
get {
switch self {
case .clear:
return "sparkle"
case .cloudy:
return "cloud"
case .hot:
return "sun.max.trianglebadge.exclamationmark.fill"
case .rain:
return "cloud.rain"
case .frigid:
return "thermometer.snowflake"
case .smoky:
return "smoke"
case .snow:
return "cloud.snow"
}
switch self {
case .clear:
return "sparkle"
case .cloudy:
return "cloud"
case .hot:
return "sun.max.trianglebadge.exclamationmark.fill"
case .rain:
return "cloud.rain"
case .frigid:
return "thermometer.snowflake"
case .smoky:
return "smoke"
case .snow:
return "cloud.snow"
}
}
}

View file

@ -13,24 +13,24 @@ struct CsvDocument: FileDocument {
static var readableContentTypes = [UTType.commaSeparatedText]
@State var csvData: String
init(emptyCsv: String = "" ) {
csvData = emptyCsv
}
init(configuration: ReadConfiguration) throws {
if let data = configuration.file.regularFileContents {
csvData = String(decoding: data, as: UTF8.self)
} else {
throw CocoaError(.fileReadCorruptFile)
}
}
func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
let data = Data(csvData.utf8)
return FileWrapper(regularFileWithContents: data)

View file

@ -7,14 +7,14 @@
import SwiftUI
func TelemetryToCsvFile(telemetry: [TelemetryEntity], metricsType: Int) -> String {
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").replacingOccurrences(of: ",", with: "")
if metricsType == 0 {
// Create Device Metrics Header
csvString = "\(NSLocalizedString("battery.level", comment: "")), \(NSLocalizedString("voltage", comment: "")), \(NSLocalizedString("channel.utilization", comment: "")), \(NSLocalizedString("airtime", comment: "")), \(NSLocalizedString("timestamp", comment: ""))"
for dm in telemetry{
for dm in telemetry {
if dm.metricsType == 0 {
csvString += "\n"
csvString += String(dm.batteryLevel)
@ -31,7 +31,7 @@ func TelemetryToCsvFile(telemetry: [TelemetryEntity], metricsType: Int) -> Strin
} else if metricsType == 1 {
// Create Environment Telemetry Header
csvString = "Temperature, Relative Humidity, Barometric Pressure, Gas Resistance, \(NSLocalizedString("voltage", comment: "")), \(NSLocalizedString("current", comment: "")), \(NSLocalizedString("timestamp", comment: ""))"
for dm in telemetry{
for dm in telemetry {
if dm.metricsType == 1 {
csvString += "\n"
csvString += String(dm.temperature.localeTemperature())
@ -53,7 +53,7 @@ func TelemetryToCsvFile(telemetry: [TelemetryEntity], metricsType: Int) -> Strin
return csvString
}
func PositionToCsvFile(positions: [PositionEntity]) -> String {
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").replacingOccurrences(of: ",", with: "")

File diff suppressed because it is too large Load diff

View file

@ -7,19 +7,19 @@
import SwiftUI
class SwiftUIEmojiTextField: UITextField {
override func awakeFromNib() {
super.awakeFromNib()
}
func setEmoji() {
_ = self.textInputMode
}
override var textInputContextIdentifier: String? {
return ""
}
override var textInputMode: UITextInputMode? {
for mode in UITextInputMode.activeInputModes {
if mode.primaryLanguage == "emoji" {
@ -34,7 +34,7 @@ class SwiftUIEmojiTextField: UITextField {
struct EmojiOnlyTextField: UIViewRepresentable {
@Binding var text: String
var placeholder: String = ""
func makeUIView(context: Context) -> SwiftUIEmojiTextField {
let emojiTextField = SwiftUIEmojiTextField()
emojiTextField.placeholder = placeholder
@ -42,15 +42,15 @@ struct EmojiOnlyTextField: UIViewRepresentable {
emojiTextField.delegate = context.coordinator
return emojiTextField
}
func updateUIView(_ uiView: SwiftUIEmojiTextField, context: Context) {
uiView.text = text
}
func makeCoordinator() -> Coordinator {
Coordinator(parent: self)
}
class Coordinator: NSObject, UITextFieldDelegate {
var parent: EmojiOnlyTextField
init(parent: EmojiOnlyTextField) {
@ -63,12 +63,3 @@ struct EmojiOnlyTextField: UIViewRepresentable {
}
}
}
//struct EmojiContentView: View {
//
// @State private var text: String = ""
//
// var body: some View {
// EmojiTextField(text: $text, placeholder: "Enter emoji")
// }
//}

View file

@ -27,7 +27,7 @@ extension Data {
}
var hexDescription: String {
return reduce("") {$0 + String(format: "%02x", $1)}
}
}
}
extension Date {
@ -53,8 +53,8 @@ extension Float {
let locale = NSLocale.current as NSLocale
let localeUnit = locale.object(forKey: NSLocale.Key(rawValue: "kCFLocaleTemperatureUnitKey"))
var format: UnitTemperature = .celsius
if localeUnit! as! String == "Fahrenheit" {
if localeUnit! as? String == "Fahrenheit" {
format = .fahrenheit
}
return temperature.converted(to: format).value
@ -90,7 +90,7 @@ extension UIImage {
}
extension String {
func base64urlToBase64() -> String {
var base64 = self
.replacingOccurrences(of: "-", with: "+")
@ -100,7 +100,7 @@ extension String {
}
return base64
}
func base64ToBase64url() -> String {
let base64url = self
.replacingOccurrences(of: "+", with: "-")
@ -108,13 +108,12 @@ extension String {
.replacingOccurrences(of: "=", with: "")
return base64url
}
func onlyEmojis() -> Bool {
return count > 0 && !contains { !$0.isEmoji }
}
func image(fontSize:CGFloat = 40, bgColor:UIColor = UIColor.clear, imageSize:CGSize? = nil) -> UIImage?
{
func image(fontSize: CGFloat = 40, bgColor: UIColor = UIColor.clear, imageSize: CGSize? = nil) -> UIImage? {
let font = UIFont.systemFont(ofSize: fontSize)
let attributes = [NSAttributedString.Key.font: font]
let imageSize = imageSize ?? self.size(withAttributes: attributes)
@ -127,7 +126,7 @@ extension String {
UIGraphicsEndImageContext()
return image
}
func camelCaseToWords() -> String {
return unicodeScalars.dropFirst().reduce(String(prefix(1))) {
return CharacterSet.uppercaseLetters.contains($1)

View file

@ -25,7 +25,7 @@ class LocationHelper: NSObject, ObservableObject {
}
return altitude
}
static var currentSpeed: CLLocationSpeed {
guard let speed = shared.locationManager.location?.speed else {
@ -33,7 +33,7 @@ class LocationHelper: NSObject, ObservableObject {
}
return speed
}
static var currentHeading: CLLocationDirection {
guard let heading = shared.locationManager.location?.course else {
@ -41,7 +41,7 @@ class LocationHelper: NSObject, ObservableObject {
}
return heading
}
static var currentTimestamp: Date {
guard let timestamp = shared.locationManager.location?.timestamp else {
@ -49,21 +49,21 @@ class LocationHelper: NSObject, ObservableObject {
}
return timestamp
}
static var satsInView: Int {
// If we have a position we have a sat
var sats = 1
if shared.locationManager.location?.verticalAccuracy ?? 0 > 0 {
sats = 4
if 0...5 ~= shared.locationManager.location?.horizontalAccuracy ?? 0{
if 0...5 ~= shared.locationManager.location?.horizontalAccuracy ?? 0 {
sats = 12
} else if 6...15 ~= shared.locationManager.location?.horizontalAccuracy ?? 0{
} else if 6...15 ~= shared.locationManager.location?.horizontalAccuracy ?? 0 {
sats = 10
} else if 16...30 ~= shared.locationManager.location?.horizontalAccuracy ?? 0{
} else if 16...30 ~= shared.locationManager.location?.horizontalAccuracy ?? 0 {
sats = 9
} else if 31...45 ~= shared.locationManager.location?.horizontalAccuracy ?? 0{
} else if 31...45 ~= shared.locationManager.location?.horizontalAccuracy ?? 0 {
sats = 7
} else if 46...60 ~= shared.locationManager.location?.horizontalAccuracy ?? 0{
} else if 46...60 ~= shared.locationManager.location?.horizontalAccuracy ?? 0 {
sats = 5
}
} else if shared.locationManager.location?.verticalAccuracy ?? 0 < 0 && 60...300 ~= shared.locationManager.location?.horizontalAccuracy ?? 0 {

View file

@ -14,7 +14,7 @@ class MeshLogger {
}
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 = dateFormatString
let timestamp = formatter.string(from: Date())

View file

@ -13,13 +13,13 @@ import ActivityKit
#endif
func generateMessageMarkdown (message: String) -> String {
let types: NSTextCheckingResult.CheckingType = [.address, .link, .phoneNumber]
let detector = try! NSDataDetector(types: types.rawValue)
let matches = detector.matches(in: message, options: [], range: NSRange(location: 0, length: message.utf16.count))
var messageWithMarkdown = message
if matches.count > 0 {
for match in matches {
guard let range = Range(match.range, in: message) else { continue }
if match.resultType == .address {
@ -39,8 +39,8 @@ func generateMessageMarkdown (message: String) -> String {
return messageWithMarkdown
}
func localConfig (config: Config, context:NSManagedObjectContext, nodeNum: Int64, nodeLongName: String) {
func localConfig (config: Config, context: NSManagedObjectContext, nodeNum: Int64, nodeLongName: String) {
// We don't care about any of the Power settings, config is available for everything else
if config.payloadVariant == Config.OneOf_PayloadVariant.bluetooth(config.bluetooth) {
upsertBluetoothConfigPacket(config: config.bluetooth, nodeNum: nodeNum, context: context)
@ -57,8 +57,8 @@ func localConfig (config: Config, context:NSManagedObjectContext, nodeNum: Int64
}
}
func moduleConfig (config: ModuleConfig, context:NSManagedObjectContext, nodeNum: Int64, nodeLongName: String) {
func moduleConfig (config: ModuleConfig, context: NSManagedObjectContext, nodeNum: Int64, nodeLongName: String) {
if config.payloadVariant == ModuleConfig.OneOf_PayloadVariant.cannedMessage(config.cannedMessage) {
upsertCannedMessagesModuleConfigPacket(config: config.cannedMessage, nodeNum: nodeNum, context: context)
} else if config.payloadVariant == ModuleConfig.OneOf_PayloadVariant.externalNotification(config.externalNotification) {
@ -75,18 +75,20 @@ func moduleConfig (config: ModuleConfig, context:NSManagedObjectContext, nodeNum
}
func myInfoPacket (myInfo: MyNodeInfo, peripheralId: String, context: NSManagedObjectContext) -> MyInfoEntity? {
let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.myinfo %@", comment: "MyInfo received: %@"), String(myInfo.myNodeNum))
MeshLogger.log(" \(logString)")
let fetchMyInfoRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "MyInfoEntity")
fetchMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %lld", Int64(myInfo.myNodeNum))
do {
let fetchedMyInfo = try context.fetch(fetchMyInfoRequest) as! [MyInfoEntity]
guard let fetchedMyInfo = try context.fetch(fetchMyInfoRequest) as? [MyInfoEntity] else {
return nil
}
// Not Found Insert
if fetchedMyInfo.isEmpty {
let myInfoEntity = MyInfoEntity(context: context)
myInfoEntity.peripheralId = peripheralId
myInfoEntity.myNodeNum = Int64(myInfo.myNodeNum)
@ -111,19 +113,19 @@ func myInfoPacket (myInfo: MyNodeInfo, peripheralId: String, context: NSManagedO
print("💥 Error Inserting New Core Data MyInfoEntity: \(nsError)")
}
} else {
fetchedMyInfo[0].peripheralId = peripheralId
fetchedMyInfo[0].myNodeNum = Int64(myInfo.myNodeNum)
fetchedMyInfo[0].hasGps = myInfo.hasGps_p
fetchedMyInfo[0].bitrate = myInfo.bitrate
let lastDotIndex = myInfo.firmwareVersion.lastIndex(of: ".")//.lastIndex(of: ".", offsetBy: -1)
var version = myInfo.firmwareVersion[...(lastDotIndex ?? String.Index(utf16Offset:6, in: myInfo.firmwareVersion))]
let lastDotIndex = myInfo.firmwareVersion.lastIndex(of: ".")// .lastIndex(of: ".", offsetBy: -1)
var version = myInfo.firmwareVersion[...(lastDotIndex ?? String.Index(utf16Offset: 6, in: myInfo.firmwareVersion))]
version = version.dropLast()
fetchedMyInfo[0].firmwareVersion = String(version)
fetchedMyInfo[0].messageTimeoutMsec = Int32(bitPattern: myInfo.messageTimeoutMsec)
fetchedMyInfo[0].minAppVersion = Int32(bitPattern: myInfo.minAppVersion)
fetchedMyInfo[0].maxChannels = Int32(bitPattern: myInfo.maxChannels)
do {
try context.save()
print("💾 Updated myInfo for node number: \(String(myInfo.myNodeNum))")
@ -141,18 +143,19 @@ func myInfoPacket (myInfo: MyNodeInfo, peripheralId: String, context: NSManagedO
}
func channelPacket (channel: Channel, fromNum: Int64, context: NSManagedObjectContext) {
if channel.isInitialized && channel.hasSettings && channel.role != Channel.Role.disabled {
if channel.isInitialized && channel.hasSettings && channel.role != Channel.Role.disabled {
let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.channel.received %d %@", comment: "Channel %d received from: %@"), channel.index, String(fromNum))
MeshLogger.log("🎛️ \(logString)")
let fetchedMyInfoRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "MyInfoEntity")
fetchedMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %lld", fromNum)
do {
let fetchedMyInfo = try context.fetch(fetchedMyInfoRequest) as! [MyInfoEntity]
guard let fetchedMyInfo = try context.fetch(fetchedMyInfoRequest) as? [MyInfoEntity] else {
return
}
if fetchedMyInfo.count == 1 {
let newChannel = ChannelEntity(context: context)
newChannel.id = Int32(channel.index)
@ -162,7 +165,9 @@ func channelPacket (channel: Channel, fromNum: Int64, context: NSManagedObjectCo
newChannel.name = channel.settings.name
newChannel.role = Int32(channel.role.rawValue)
newChannel.psk = channel.settings.psk
let mutableChannels = fetchedMyInfo[0].channels!.mutableCopy() as! NSMutableOrderedSet
guard let mutableChannels = fetchedMyInfo[0].channels!.mutableCopy() as? NSMutableOrderedSet else {
return
}
if mutableChannels.contains(newChannel) {
mutableChannels.replaceObject(at: Int(newChannel.index), with: newChannel)
} else {
@ -190,17 +195,18 @@ func channelPacket (channel: Channel, fromNum: Int64, context: NSManagedObjectCo
}
func deviceMetadataPacket (metadata: DeviceMetadata, fromNum: Int64, context: NSManagedObjectContext) {
if metadata.isInitialized {
let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.device.metadata.received %@", comment: "Device Metadata admin message received from: %@"), String(fromNum))
MeshLogger.log("🏷️ \(logString)")
let fetchedNodeRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "NodeInfoEntity")
fetchedNodeRequest.predicate = NSPredicate(format: "num == %lld", fromNum)
do {
let fetchedNode = try context.fetch(fetchedNodeRequest) as! [NodeInfoEntity]
guard let fetchedNode = try context.fetch(fetchedNodeRequest) as? [NodeInfoEntity] else {
return
}
if fetchedNode.count > 0 {
let newMetadata = DeviceMetadataEntity(context: context)
newMetadata.firmwareVersion = metadata.firmwareVersion
@ -229,26 +235,27 @@ func deviceMetadataPacket (metadata: DeviceMetadata, fromNum: Int64, context: NS
}
func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObjectContext) -> NodeInfoEntity? {
let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.nodeinfo.received %@", comment: "Node info received for: %@"), String(nodeInfo.num))
MeshLogger.log("📟 \(logString)")
guard (nodeInfo.num > 0) else { return nil }
guard nodeInfo.num > 0 else { return nil }
let fetchNodeInfoRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "NodeInfoEntity")
fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeInfo.num))
do {
let fetchedNode = try context.fetch(fetchNodeInfoRequest) as! [NodeInfoEntity]
guard let fetchedNode = try context.fetch(fetchNodeInfoRequest) as? [NodeInfoEntity] else {
return nil
}
// Not Found Insert
if fetchedNode.isEmpty && nodeInfo.hasUser {
let newNode = NodeInfoEntity(context: context)
newNode.id = Int64(nodeInfo.num)
newNode.num = Int64(nodeInfo.num)
newNode.channel = Int32(channel)
if nodeInfo.hasDeviceMetrics {
let telemetry = TelemetryEntity(context: context)
telemetry.batteryLevel = Int32(nodeInfo.deviceMetrics.batteryLevel)
@ -259,7 +266,7 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje
newTelemetries.append(telemetry)
newNode.telemetries? = NSOrderedSet(array: newTelemetries)
}
newNode.lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(nodeInfo.lastHeard)))
newNode.snr = nodeInfo.snr
if nodeInfo.hasUser {
@ -272,9 +279,8 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje
newUser.hwModel = String(describing: nodeInfo.user.hwModel).uppercased()
newNode.user = newUser
}
if nodeInfo.position.longitudeI > 0 || nodeInfo.position.latitudeI > 0 && (nodeInfo.position.latitudeI != 373346000 && nodeInfo.position.longitudeI != -1220090000)
{
if nodeInfo.position.longitudeI > 0 || nodeInfo.position.latitudeI > 0 && (nodeInfo.position.latitudeI != 373346000 && nodeInfo.position.longitudeI != -1220090000) {
let position = PositionEntity(context: context)
position.seqNo = Int32(nodeInfo.position.seqNumber)
position.latitudeI = nodeInfo.position.latitudeI
@ -288,14 +294,15 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje
newPostions.append(position)
newNode.positions? = NSOrderedSet(array: newPostions)
}
// Look for a MyInfo
let fetchMyInfoRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "MyInfoEntity")
fetchMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %lld", Int64(nodeInfo.num))
do {
let fetchedMyInfo = try context.fetch(fetchMyInfoRequest) as! [MyInfoEntity]
guard let fetchedMyInfo = try context.fetch(fetchMyInfoRequest) as? [MyInfoEntity] else {
return nil
}
if fetchedMyInfo.count > 0 {
newNode.myInfo = fetchedMyInfo[0]
}
@ -311,15 +318,15 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje
print("💥 Fetch MyInfo Error")
}
} else if nodeInfo.hasUser && nodeInfo.num > 0 {
fetchedNode[0].id = Int64(nodeInfo.num)
fetchedNode[0].num = Int64(nodeInfo.num)
fetchedNode[0].lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(nodeInfo.lastHeard)))
fetchedNode[0].snr = nodeInfo.snr
fetchedNode[0].channel = Int32(channel)
if nodeInfo.hasUser {
fetchedNode[0].user!.userId = nodeInfo.user.id
fetchedNode[0].user!.num = Int64(nodeInfo.num)
fetchedNode[0].user!.longName = nodeInfo.user.longName
@ -327,9 +334,9 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje
fetchedNode[0].user!.macaddr = nodeInfo.user.macaddr
fetchedNode[0].user!.hwModel = String(describing: nodeInfo.user.hwModel).uppercased()
}
if nodeInfo.hasDeviceMetrics {
let newTelemetry = TelemetryEntity(context: context)
newTelemetry.batteryLevel = Int32(nodeInfo.deviceMetrics.batteryLevel)
newTelemetry.voltage = nodeInfo.deviceMetrics.voltage
@ -338,12 +345,11 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje
let mutableTelemetries = fetchedNode[0].telemetries!.mutableCopy() as! NSMutableOrderedSet
fetchedNode[0].telemetries = mutableTelemetries.copy() as? NSOrderedSet
}
if nodeInfo.hasPosition {
if nodeInfo.position.longitudeI > 0 || nodeInfo.position.latitudeI > 0 && (nodeInfo.position.latitudeI != 373346000 && nodeInfo.position.longitudeI != -1220090000) {
let position = PositionEntity(context: context)
position.latitudeI = nodeInfo.position.latitudeI
position.longitudeI = nodeInfo.position.longitudeI
@ -353,15 +359,17 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje
let mutablePositions = fetchedNode[0].positions!.mutableCopy() as! NSMutableOrderedSet
fetchedNode[0].positions = mutablePositions.copy() as? NSOrderedSet
}
}
// Look for a MyInfo
let fetchMyInfoRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "MyInfoEntity")
fetchMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %lld", Int64(nodeInfo.num))
do {
let fetchedMyInfo = try context.fetch(fetchMyInfoRequest) as! [MyInfoEntity]
guard let fetchedMyInfo = try context.fetch(fetchMyInfoRequest) as? [MyInfoEntity] else {
return nil
}
if fetchedMyInfo.count > 0 {
fetchedNode[0].myInfo = fetchedMyInfo[0]
}
@ -385,26 +393,26 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje
}
func nodeInfoAppPacket (packet: MeshPacket, context: NSManagedObjectContext) {
let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.nodeinfo.received %@", comment: "Node info received for: %@"), String(packet.from))
MeshLogger.log("📟 \(logString)")
guard (packet.from > 0) else { return }
guard packet.from > 0 else { return }
let fetchNodeInfoAppRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "NodeInfoEntity")
fetchNodeInfoAppRequest.predicate = NSPredicate(format: "num == %lld", Int64(packet.from))
do {
let fetchedNode = try context.fetch(fetchNodeInfoAppRequest) as? [NodeInfoEntity] ?? []
if fetchedNode.count == 1 {
fetchedNode[0].id = Int64(packet.from)
fetchedNode[0].num = Int64(packet.from)
fetchedNode[0].lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(packet.rxTime)))
fetchedNode[0].snr = packet.rxSnr
fetchedNode[0].channel = Int32(packet.channel)
if let nodeInfoMessage = try? NodeInfo(serializedData: packet.decoded.payload) {
if nodeInfoMessage.hasDeviceMetrics {
let telemetry = TelemetryEntity(context: context)
@ -442,23 +450,25 @@ func nodeInfoAppPacket (packet: MeshPacket, context: NSManagedObjectContext) {
}
func adminAppPacket (packet: MeshPacket, context: NSManagedObjectContext) {
if let adminMessage = try? AdminMessage(serializedData: packet.decoded.payload) {
if adminMessage.payloadVariant == AdminMessage.OneOf_PayloadVariant.getCannedMessageModuleMessagesResponse(adminMessage.getCannedMessageModuleMessagesResponse) {
if let cmmc = try? CannedMessageModuleConfig(serializedData: packet.decoded.payload) {
if !cmmc.messages.isEmpty {
let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.cannedmessages.messages.received %@", comment: "Canned Messages Messages Received For: %@"), String(packet.from))
MeshLogger.log("🥫 \(logString)")
let fetchNodeRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "NodeInfoEntity")
fetchNodeRequest.predicate = NSPredicate(format: "num == %lld", Int64(packet.from))
do {
let fetchedNode = try context.fetch(fetchNodeRequest) as! [NodeInfoEntity]
guard let fetchedNode = try context.fetch(fetchNodeRequest) as? [NodeInfoEntity] else {
return
}
if fetchedNode.count == 1 {
let messages = String(cmmc.textFormatString())
.replacingOccurrences(of: "11: ", with: "")
@ -481,69 +491,71 @@ func adminAppPacket (packet: MeshPacket, context: NSManagedObjectContext) {
}
} else if adminMessage.payloadVariant == AdminMessage.OneOf_PayloadVariant.getChannelResponse(adminMessage.getChannelResponse) {
channelPacket(channel: adminMessage.getChannelResponse, fromNum: Int64(packet.from), context: context)
} else if adminMessage.payloadVariant == AdminMessage.OneOf_PayloadVariant.getDeviceMetadataResponse(adminMessage.getDeviceMetadataResponse) {
deviceMetadataPacket(metadata: adminMessage.getDeviceMetadataResponse, fromNum: Int64(packet.from), context: context)
} else if adminMessage.payloadVariant == AdminMessage.OneOf_PayloadVariant.getConfigResponse(adminMessage.getConfigResponse) {
let config = adminMessage.getConfigResponse
if config.payloadVariant == Config.OneOf_PayloadVariant.bluetooth(config.bluetooth) {
upsertBluetoothConfigPacket(config: config.bluetooth, nodeNum: Int64(packet.from), context: context)
} else if config.payloadVariant == Config.OneOf_PayloadVariant.device(config.device) {
upsertDeviceConfigPacket(config: config.device, nodeNum: Int64(packet.from), context: context)
} else if config.payloadVariant == Config.OneOf_PayloadVariant.lora(config.lora) {
upsertLoRaConfigPacket(config: config.lora, nodeNum: Int64(packet.from), context: context)
} else if config.payloadVariant == Config.OneOf_PayloadVariant.network(config.network) {
upsertNetworkConfigPacket(config: config.network, nodeNum: Int64(packet.from), context: context)
} else if config.payloadVariant == Config.OneOf_PayloadVariant.position(config.position) {
upsertPositionConfigPacket(config: config.position, nodeNum: Int64(packet.from), context: context)
}
} else if adminMessage.payloadVariant == AdminMessage.OneOf_PayloadVariant.getModuleConfigResponse(adminMessage.getModuleConfigResponse) {
let moduleConfig = adminMessage.getModuleConfigResponse
if moduleConfig.payloadVariant == ModuleConfig.OneOf_PayloadVariant.cannedMessage(moduleConfig.cannedMessage) {
upsertCannedMessagesModuleConfigPacket(config: moduleConfig.cannedMessage, nodeNum: Int64(packet.from), context: context)
} else if moduleConfig.payloadVariant == ModuleConfig.OneOf_PayloadVariant.externalNotification(moduleConfig.externalNotification) {
upsertExternalNotificationModuleConfigPacket(config: moduleConfig.externalNotification, nodeNum: Int64(packet.from), context: context)
} else if moduleConfig.payloadVariant == ModuleConfig.OneOf_PayloadVariant.mqtt(moduleConfig.mqtt) {
upsertMqttModuleConfigPacket(config: moduleConfig.mqtt, nodeNum: Int64(packet.from), context: context)
} else if moduleConfig.payloadVariant == ModuleConfig.OneOf_PayloadVariant.rangeTest(moduleConfig.rangeTest) {
upsertRangeTestModuleConfigPacket(config: moduleConfig.rangeTest, nodeNum: Int64(packet.from), context: context)
} else if moduleConfig.payloadVariant == ModuleConfig.OneOf_PayloadVariant.serial(moduleConfig.serial) {
upsertSerialModuleConfigPacket(config: moduleConfig.serial, nodeNum: Int64(packet.from), context: context)
} else if moduleConfig.payloadVariant == ModuleConfig.OneOf_PayloadVariant.telemetry(moduleConfig.telemetry) {
upsertTelemetryModuleConfigPacket(config: moduleConfig.telemetry, nodeNum: Int64(packet.from), context: context)
}
} else {
MeshLogger.log("🕸️ MESH PACKET received for Admin App \(try! packet.decoded.jsonString())")
}
// Save an ack for the admin message log for each admin message response received as we stopped sending acks if there is also a response to reduce airtime.
adminResponseAck(packet: packet, context: context)
}
}
func adminResponseAck (packet: MeshPacket, context: NSManagedObjectContext) {
let fetchedAdminMessageRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "MessageEntity")
fetchedAdminMessageRequest.predicate = NSPredicate(format: "messageId == %lld", packet.decoded.requestID)
do {
let fetchedMessage = try context.fetch(fetchedAdminMessageRequest) as! [MessageEntity]
guard let fetchedMessage = try context.fetch(fetchedAdminMessageRequest) as? [MessageEntity] else {
return
}
if fetchedMessage.count > 0 {
fetchedMessage[0].ackTimestamp = Int32(Date().timeIntervalSince1970)
fetchedMessage[0].ackError = Int32(RoutingError.none.rawValue)
@ -565,22 +577,22 @@ func adminResponseAck (packet: MeshPacket, context: NSManagedObjectContext) {
}
func routingPacket (packet: MeshPacket, connectedNodeNum: Int64, context: NSManagedObjectContext) {
if let routingMessage = try? Routing(serializedData: packet.decoded.payload) {
let routingError = RoutingError(rawValue: routingMessage.errorReason.rawValue)
let routingErrorString = routingError?.display ?? NSLocalizedString("unknown", comment: "")
let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.routing.message %@ %@", comment: "Routing received for RequestID: %@ Ack Status: %@"), String(packet.decoded.requestID), routingErrorString)
MeshLogger.log("🕸️ \(logString)")
let fetchMessageRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "MessageEntity")
fetchMessageRequest.predicate = NSPredicate(format: "messageId == %lld", Int64(packet.decoded.requestID))
do {
let fetchedMessage = try context.fetch(fetchMessageRequest) as? [MessageEntity]
if fetchedMessage?.count ?? 0 > 0 {
if fetchedMessage![0].toUser != nil {
// Real ACK from DM Recipient
if packet.to != packet.from {
@ -588,14 +600,14 @@ func routingPacket (packet: MeshPacket, connectedNodeNum: Int64, context: NSMana
}
}
fetchedMessage![0].ackError = Int32(routingMessage.errorReason.rawValue)
if routingMessage.errorReason == Routing.Error.none {
fetchedMessage![0].receivedACK = true
}
fetchedMessage![0].ackSNR = packet.rxSnr
fetchedMessage![0].ackTimestamp = Int32(packet.rxTime)
if fetchedMessage![0].toUser != nil {
fetchedMessage![0].toUser?.objectWillChange.send()
} else {
@ -604,19 +616,19 @@ func routingPacket (packet: MeshPacket, connectedNodeNum: Int64, context: NSMana
do {
let fetchedMyInfo = try context.fetch(fetchMyInfoRequest) as? [MyInfoEntity]
if fetchedMyInfo?.count ?? 0 > 0 {
for ch in fetchedMyInfo![0].channels!.array as! [ChannelEntity] {
if ch.index == packet.channel {
ch.objectWillChange.send()
}
}
}
} catch {
}
}
} else {
return
}
@ -631,26 +643,28 @@ func routingPacket (packet: MeshPacket, connectedNodeNum: Int64, context: NSMana
}
func telemetryPacket(packet: MeshPacket, connectedNode: Int64, context: NSManagedObjectContext) {
if let telemetryMessage = try? Telemetry(serializedData: packet.decoded.payload) {
// Only log telemetry from the mesh not the connected device
if connectedNode != Int64(packet.from) {
let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.telemetry.received %@", comment: "Telemetry received for: %@"), String(packet.from))
MeshLogger.log("📈 \(logString)")
} else {
// If it is the connected node
}
let telemetry = TelemetryEntity(context: context)
let fetchNodeTelemetryRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "NodeInfoEntity")
fetchNodeTelemetryRequest.predicate = NSPredicate(format: "num == %lld", Int64(packet.from))
do {
let fetchedNode = try context.fetch(fetchNodeTelemetryRequest) as! [NodeInfoEntity]
guard let fetchedNode = try context.fetch(fetchNodeTelemetryRequest) as? [NodeInfoEntity] else {
return
}
if fetchedNode.count == 1 {
if telemetryMessage.variant == Telemetry.OneOf_Variant.deviceMetrics(telemetryMessage.deviceMetrics) {
// Device Metrics
@ -681,23 +695,42 @@ func telemetryPacket(packet: MeshPacket, connectedNode: Int64, context: NSManage
if connectedNode != Int64(packet.from) {
print("💾 Telemetry Saved for Node: \(packet.from)")
} else if telemetry.metricsType == 0 {
// Update our live activity if there is one running
// Connected Device Metrics
// ------------------------
// Low Battery notification
if telemetry.batteryLevel > 0 && telemetry.batteryLevel < 5 {
let content = UNMutableNotificationContent()
content.title = "Critically Low Battery!"
content.body = "Time to charge your radio, there is \(telemetry.batteryLevel)% battery remaining."
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false)
let uuidString = UUID().uuidString
let request = UNNotificationRequest(identifier: uuidString, content: content, trigger: trigger)
let notificationCenter = UNUserNotificationCenter.current()
notificationCenter.add(request) { (error) in
if error != nil {
// Handle any errors.
print("Error creating local low battery notification: \(error?.localizedDescription ?? "no description")")
} else {
print("Created local low battery notification.")
}
}
}
// Update our live activity if there is one running, not available on mac iOS >= 16.2
#if !targetEnvironment(macCatalyst)
if #available(iOS 16.2, *) {
let oneMinuteLater = Calendar.current.date(byAdding: .minute, value: (Int(1) ), to: Date())!
let date = Date.now...oneMinuteLater
let updatedMeshStatus = MeshActivityAttributes.MeshActivityStatus(timerRange: date, connected: true, channelUtilization: telemetry.channelUtilization, airtime: telemetry.airUtilTx, batteryLevel: UInt32(telemetry.batteryLevel))
let alertConfiguration = AlertConfiguration(title: "Mesh activity update", body: "Updated Device Metrics Data.", sound: .default)
let updatedContent = ActivityContent(state: updatedMeshStatus, staleDate: nil)
print("Update live activity.")
let meshActivity = Activity<MeshActivityAttributes>.activities.first(where: { $0.attributes.nodeNum == connectedNode })
if meshActivity != nil {
Task {
await meshActivity?.update(updatedContent, alertConfiguration: alertConfiguration)
//await meshActivity?.update(updatedContent)
// await meshActivity?.update(updatedContent)
print("Updated live activity.")
}
}
}
@ -714,17 +747,17 @@ func telemetryPacket(packet: MeshPacket, connectedNode: Int64, context: NSManage
}
func textMessageAppPacket(packet: MeshPacket, connectedNode: Int64, context: NSManagedObjectContext) {
if let messageText = String(bytes: packet.decoded.payload, encoding: .utf8) {
MeshLogger.log("💬 \(NSLocalizedString("mesh.log.textmessage.received", comment: "Message received from the text message app"))")
let messageUsers: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "UserEntity")
messageUsers.predicate = NSPredicate(format: "num IN %@", [packet.to, packet.from])
do {
let fetchedUsers = try context.fetch(messageUsers) as! [UserEntity]
guard let fetchedUsers = try context.fetch(messageUsers) as? [UserEntity] else {
return
}
let newMessage = MessageEntity(context: context)
newMessage.messageId = Int64(packet.id)
newMessage.messageTimestamp = Int32(bitPattern: packet.rxTime)
@ -732,11 +765,11 @@ func textMessageAppPacket(packet: MeshPacket, connectedNode: Int64, context: NSM
newMessage.snr = packet.rxSnr
newMessage.isEmoji = packet.decoded.emoji == 1
newMessage.channel = Int32(packet.channel)
if packet.decoded.replyID > 0 {
newMessage.replyID = Int64(packet.decoded.replyID)
}
if fetchedUsers.first(where: { $0.num == packet.to }) != nil && packet.to != 4294967295 {
newMessage.toUser = fetchedUsers.first(where: { $0.num == packet.to })
}
@ -745,20 +778,20 @@ func textMessageAppPacket(packet: MeshPacket, connectedNode: Int64, context: NSM
}
newMessage.messagePayload = messageText
newMessage.messagePayloadMarkdown = generateMessageMarkdown(message: messageText)
newMessage.fromUser?.objectWillChange.send()
newMessage.toUser?.objectWillChange.send()
var messageSaved = false
do {
try context.save()
print("💾 Saved a new message for \(newMessage.messageId)")
messageSaved = true
if messageSaved {
if newMessage.fromUser != nil && newMessage.toUser != nil && !(newMessage.fromUser?.mute ?? false) {
// Create an iOS Notification for the received DM message and schedule it immediately
let manager = LocalNotificationManager()
@ -772,17 +805,19 @@ func textMessageAppPacket(packet: MeshPacket, connectedNode: Int64, context: NSM
manager.schedule()
print("💬 iOS Notification Scheduled for text message from \(newMessage.fromUser?.longName ?? NSLocalizedString("unknown", comment: "Unknown"))")
} else if newMessage.fromUser != nil && newMessage.toUser == nil {
let fetchMyInfoRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "MyInfoEntity")
fetchMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %lld", Int64(connectedNode))
do {
let fetchedMyInfo = try context.fetch(fetchMyInfoRequest) as! [MyInfoEntity]
guard let fetchedMyInfo = try context.fetch(fetchMyInfoRequest) as? [MyInfoEntity] else {
return
}
for channel in (fetchedMyInfo[0].channels?.array ?? []) as? [ChannelEntity] ?? [] {
if channel.index == newMessage.channel {
context.refresh(channel, mergeChanges: true)
}
if channel.index == newMessage.channel && !channel.mute {
// Create an iOS Notification for the received private channel message and schedule it immediately
let manager = LocalNotificationManager()
@ -798,7 +833,7 @@ func textMessageAppPacket(packet: MeshPacket, connectedNode: Int64, context: NSM
}
}
} catch {
}
}
}
@ -814,21 +849,22 @@ func textMessageAppPacket(packet: MeshPacket, connectedNode: Int64, context: NSM
}
func waypointPacket (packet: MeshPacket, context: NSManagedObjectContext) {
let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.waypoint.received %@", comment: "Waypoint Packet received from node: %@"), String(packet.from))
MeshLogger.log("📍 \(logString)")
let fetchWaypointRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "WaypointEntity")
fetchWaypointRequest.predicate = NSPredicate(format: "id == %lld", Int64(packet.id))
do {
if let waypointMessage = try? Waypoint(serializedData: packet.decoded.payload) {
let fetchedWaypoint = try context.fetch(fetchWaypointRequest) as! [WaypointEntity]
do {
if let waypointMessage = try? Waypoint(serializedData: packet.decoded.payload) {
guard let fetchedWaypoint = try context.fetch(fetchWaypointRequest) as? [WaypointEntity] else {
return
}
if fetchedWaypoint.isEmpty {
let waypoint = WaypointEntity(context: context)
waypoint.id = Int64(packet.id)
waypoint.name = waypointMessage.name
waypoint.longDescription = waypointMessage.description_p
@ -838,7 +874,7 @@ func waypointPacket (packet: MeshPacket, context: NSManagedObjectContext) {
waypoint.locked = Int64(waypointMessage.lockedTo)
if waypointMessage.expire > 0 {
waypoint.expire = Date(timeIntervalSince1970: TimeInterval(Int64(waypointMessage.expire)))
}else {
} else {
waypoint.expire = nil
}
waypoint.created = Date()

View file

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

View file

@ -0,0 +1,305 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="21513" systemVersion="22D68" 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 &amp;&amp; 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="nodeInfoBroadcastSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="rebroadcastMode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="role" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="serialEnabled" optional="YES" attributeType="Boolean" defaultValueString="0" usesScalarValueType="YES"/>
<relationship name="deviceConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="deviceConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="DeviceMetadataEntity" representedClassName="DeviceMetadataEntity" syncable="YES" codeGenerationType="class">
<attribute name="canShutdown" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="deviceStateVersion" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="firmwareVersion" optional="YES" attributeType="String"/>
<attribute name="hasBluetooth" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="hasEthernet" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="hasWifi" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="hwModel" optional="YES" attributeType="String"/>
<attribute name="positionFlags" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="role" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<relationship name="metadataNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="metadata" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="DisplayConfigEntity" representedClassName="DisplayConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="compassNorthTop" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="displayMode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="flipScreen" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="gpsFormat" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="headingBold" optional="YES" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
<attribute name="oledType" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="screenCarouselInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="screenOnSeconds" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<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="overrideDutyCycle" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="overrideFrequency" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="regionCode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="spreadFactor" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="sx126xRxBoostedGain" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="txEnabled" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
<attribute name="txPower" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="usePreset" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
<relationship name="loRaConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="loRaConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="MessageEntity" representedClassName="MessageEntity" syncable="YES" codeGenerationType="class">
<attribute name="ackError" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="ackSNR" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="ackTimestamp" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="admin" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="adminDescription" optional="YES" attributeType="String"/>
<attribute name="channel" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="isEmoji" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="messageId" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="messagePayload" optional="YES" attributeType="String" defaultValueString=""/>
<attribute name="messagePayloadMarkdown" optional="YES" attributeType="String"/>
<attribute name="messageTimestamp" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="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="adminIndex" optional="YES" attributeType="Integer 32" defaultValueString="NO" usesScalarValueType="YES"/>
<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" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="num" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="peripheralId" optional="YES" attributeType="String"/>
<attribute name="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="metadata" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="DeviceMetadataEntity" inverseName="metadataNode" inverseEntity="DeviceMetadataEntity"/>
<relationship name="mqttConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MQTTConfigEntity" inverseName="mqttConfigNode" inverseEntity="MQTTConfigEntity"/>
<relationship name="myInfo" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MyInfoEntity" inverseName="myInfoNode" inverseEntity="MyInfoEntity"/>
<relationship name="networkConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NetworkConfigEntity" inverseName="networkConfigNode" inverseEntity="NetworkConfigEntity"/>
<relationship name="positionConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="PositionConfigEntity" inverseName="positionConfigNode" inverseEntity="PositionConfigEntity"/>
<relationship name="positions" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="PositionEntity" inverseName="nodePosition" inverseEntity="PositionEntity"/>
<relationship name="rangeTestConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="RangeTestConfigEntity" inverseName="rangeTestConfigNode" inverseEntity="RangeTestConfigEntity"/>
<relationship name="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="latest" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="latitudeI" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="longitudeI" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="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="(fromUser.num == $FETCH_SOURCE.num) AND isEmoji == false AND admin = true"/>
</fetchedProperty>
<fetchedProperty name="allMessages" optional="YES">
<fetchRequest name="fetchedPropertyFetchRequest" entity="MessageEntity" predicateString="((toUser.num == $FETCH_SOURCE.num) OR (fromUser.num == $FETCH_SOURCE.num)) AND toUser != nil AND fromUser != nil AND isEmoji == false AND admin = false"/>
</fetchedProperty>
</entity>
<entity name="WaypointEntity" representedClassName="WaypointEntity" syncable="YES" codeGenerationType="class">
<attribute name="created" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="expire" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="icon" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="id" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="lastUpdated" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="latitudeI" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="locked" attributeType="Integer 64" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="longDescription" optional="YES" attributeType="String" maxValueString="100"/>
<attribute name="longitudeI" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="name" attributeType="String" minValueString="1" maxValueString="30"/>
</entity>
</model>

View file

@ -5,7 +5,7 @@ import CoreData
@main
struct MeshtasticAppleApp: App {
let persistenceController = PersistenceController.shared
@ObservedObject private var bleManager: BLEManager = BLEManager()
@ObservedObject private var userSettings: UserSettings = UserSettings()
@ -30,9 +30,9 @@ struct MeshtasticAppleApp: App {
print("URL received \(userActivity)")
self.incomingUrl = userActivity.webpageURL
if ((self.incomingUrl?.absoluteString.lowercased().contains("meshtastic.org/e/#")) != nil) {
if (self.incomingUrl?.absoluteString.lowercased().contains("meshtastic.org/e/#")) != nil {
if let components = self.incomingUrl?.absoluteString.components(separatedBy: "#") {
self.channelSettings = components.last!
}
@ -44,10 +44,10 @@ struct MeshtasticAppleApp: App {
}
}
.onOpenURL(perform: { (url) in
print("Some sort of URL was received \(url)")
self.incomingUrl = url
if url.absoluteString.lowercased().contains("meshtastic.org/e/#") {
if let components = self.incomingUrl?.absoluteString.components(separatedBy: "#") {
self.channelSettings = components.last!
@ -59,37 +59,37 @@ struct MeshtasticAppleApp: App {
print("User wants to import a MBTILES offline map file: \(self.incomingUrl?.absoluteString ?? "No Tiles link")")
}
//we are expecting a .mbtiles map file that contains raster data
//save it to the documents directory, and name it offline_map.mbtiles
// we are expecting a .mbtiles map file that contains raster data
// save it to the documents directory, and name it offline_map.mbtiles
let fileManager = FileManager.default
let documentsDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!
let destination = documentsDirectory.appendingPathComponent("offline_map.mbtiles", isDirectory: false)
if !self.saveChannels {
//tell the system we want the file please
// tell the system we want the file please
guard url.startAccessingSecurityScopedResource() else {
return
}
//do we need to delete an old one?
if (fileManager.fileExists(atPath: destination.path)) {
// do we need to delete an old one?
if fileManager.fileExists(atPath: destination.path) {
print(" Found an old map file. Deleting it")
try? fileManager.removeItem(atPath: destination.path)
}
do {
try fileManager.copyItem(at: url, to: destination)
} catch {
print("Copy MB Tile file failed. Error: \(error)")
}
if (fileManager.fileExists(atPath: destination.path)) {
if fileManager.fileExists(atPath: destination.path) {
print(" Saved the map file")
//need to tell the map view that it needs to update and try loading the new overlay
// need to tell the map view that it needs to update and try loading the new overlay
UserDefaults.standard.set(Date().timeIntervalSince1970, forKey: "lastUpdatedLocalMapFile")
} else {
print("💥 Didn't save the map file")
}

View file

@ -23,15 +23,13 @@ struct Peripheral: Identifiable {
self.lastUpdate = lastUpdate
self.peripheral = peripheral
}
func getSignalStrength() -> SignalStrength {
if (NSNumber(value: rssi).compare(NSNumber(-65)) == ComparisonResult.orderedDescending) {
if NSNumber(value: rssi).compare(NSNumber(-65)) == ComparisonResult.orderedDescending {
return SignalStrength.strong
}
else if (NSNumber(value: rssi).compare(NSNumber(-85)) == ComparisonResult.orderedDescending) {
} else if NSNumber(value: rssi).compare(NSNumber(-85)) == ComparisonResult.orderedDescending {
return SignalStrength.normal
}
else {
} else {
return SignalStrength.weak
}
}

View file

@ -62,7 +62,7 @@ class UserSettings: ObservableObject {
UserDefaults.standard.synchronize()
}
}
init() {
self.meshtasticUsername = UserDefaults.standard.object(forKey: "meshtasticusername") as? String ?? ""

View file

@ -7,9 +7,9 @@
import Foundation
extension ChannelEntity {
var allPrivateMessages: [MessageEntity] {
self.value(forKey: "allPrivateMessages") as? [MessageEntity] ?? [MessageEntity]()
}
}

View file

@ -32,19 +32,19 @@ class PersistenceController {
let container: NSPersistentContainer
init(inMemory: Bool = false) {
container = NSPersistentContainer(name: "Meshtastic")
if inMemory {
container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
}
container.loadPersistentStores(completionHandler: { (_, error) in
// Merge policy that favors in memory data over data in the db
self.container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
self.container.viewContext.automaticallyMergesChangesFromParent = true
if let error = error as NSError? {
print("💥 CoreData Error: \(error.localizedDescription). Now attempting to truncate CoreData database. All app data will be lost.")
@ -52,18 +52,18 @@ class PersistenceController {
}
})
}
public func clearDatabase() {
guard let url = self.container.persistentStoreDescriptions.first?.url else { return }
let persistentStoreCoordinator = self.container.persistentStoreCoordinator
do {
try persistentStoreCoordinator.destroyPersistentStore(at:url, ofType: NSSQLiteStoreType, options: nil)
try persistentStoreCoordinator.destroyPersistentStore(at: url, ofType: NSSQLiteStoreType, options: nil)
try persistentStoreCoordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: url, options: nil)
print("💥 CoreData database truncated. All app data has been erased.")
} catch let error {
print("💣 Failed to destroy CoreData database, delete the app and re-install to clear data. Attempted to clear persistent store: " + error.localizedDescription)
}
@ -71,17 +71,17 @@ class PersistenceController {
}
extension NSManagedObjectContext {
/// Executes the given `NSBatchDeleteRequest` and directly merges the changes to bring the given managed object context up to date.
///
/// - Parameter batchDeleteRequest: The `NSBatchDeleteRequest` to execute.
/// - Throws: An error if anything went wrong executing the batch deletion.
public func executeAndMergeChanges(using batchDeleteRequest: NSBatchDeleteRequest) throws {
batchDeleteRequest.resultType = .resultTypeObjectIDs
let result = try execute(batchDeleteRequest) as? NSBatchDeleteResult
let changes: [AnyHashable: Any] = [NSDeletedObjectsKey: result?.result as? [NSManagedObjectID] ?? []]
NSManagedObjectContext.mergeChanges(fromRemoteContextSave: changes, into: [self])
}
}

View file

@ -31,7 +31,7 @@ extension PositionEntity {
return nil
}
}
var nodeLocation: CLLocation? {
if latitudeI != 0 && longitudeI != 0 {
let location = CLLocation(latitude: latitude!, longitude: longitude!)
@ -40,7 +40,7 @@ extension PositionEntity {
return nil
}
}
var annotaton: MKPointAnnotation {
let pointAnn = MKPointAnnotation()
if nodeCoordinate != nil {

View file

@ -8,14 +8,16 @@
import CoreData
public func getNodeInfo(id: Int64, context: NSManagedObjectContext) -> NodeInfoEntity? {
let fetchNodeInfoRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "NodeInfoEntity")
fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(id))
do {
let fetchNodeInfo = try context.fetch(fetchNodeInfoRequest) as! [NodeInfoEntity]
if fetchNodeInfo.count == 1 {
return fetchNodeInfo[0]
guard let fetchedNode = try context.fetch(fetchNodeInfoRequest) as? [NodeInfoEntity] else {
return nil
}
if fetchedNode.count == 1 {
return fetchedNode[0]
}
} catch {
return nil
@ -24,12 +26,14 @@ public func getNodeInfo(id: Int64, context: NSManagedObjectContext) -> NodeInfoE
}
public func getUser(id: Int64, context: NSManagedObjectContext) -> UserEntity {
let fetchUserRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "UserEntity")
fetchUserRequest.predicate = NSPredicate(format: "num == %lld", Int64(id))
do {
let fetchedUser = try context.fetch(fetchUserRequest) as! [UserEntity]
guard let fetchedUser = try context.fetch(fetchUserRequest) as? [UserEntity] else {
return UserEntity(context: context)
}
if fetchedUser.count == 1 {
return fetchedUser[0]
}
@ -40,12 +44,14 @@ public func getUser(id: Int64, context: NSManagedObjectContext) -> UserEntity {
}
public func getWaypoint(id: Int64, context: NSManagedObjectContext) -> WaypointEntity {
let fetchWaypointRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "WaypointEntity")
fetchWaypointRequest.predicate = NSPredicate(format: "id == %lld", Int64(id))
do {
let fetchedWaypoint = try context.fetch(fetchWaypointRequest) as! [WaypointEntity]
guard let fetchedWaypoint = try context.fetch(fetchWaypointRequest) as? [WaypointEntity] else {
return WaypointEntity(context: context)
}
if fetchedWaypoint.count == 1 {
return fetchedWaypoint[0]
}

View file

@ -7,26 +7,24 @@
import CoreData
public func clearPositions(destNum: Int64, context: NSManagedObjectContext) -> Bool {
let fetchNodeInfoRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "NodeInfoEntity")
fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(destNum))
do {
let fetchedNode = try context.fetch(fetchNodeInfoRequest) as! [NodeInfoEntity]
guard let fetchedNode = try context.fetch(fetchNodeInfoRequest) as? [NodeInfoEntity] else {
return false
}
let newPostions = [PositionEntity]()
fetchedNode[0].positions? = NSOrderedSet(array: newPostions)
do {
try context.save()
return true
} catch {
context.rollback()
return false
}
} catch {
print("💥 Fetch NodeInfoEntity Error")
return false
@ -34,26 +32,24 @@ public func clearPositions(destNum: Int64, context: NSManagedObjectContext) -> B
}
public func clearTelemetry(destNum: Int64, metricsType: Int32, context: NSManagedObjectContext) -> Bool {
let fetchNodeInfoRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "NodeInfoEntity")
fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(destNum))
do {
let fetchedNode = try context.fetch(fetchNodeInfoRequest) as! [NodeInfoEntity]
guard let fetchedNode = try context.fetch(fetchNodeInfoRequest) as? [NodeInfoEntity] else {
return false
}
let emptyTelemetry = [TelemetryEntity]()
fetchedNode[0].telemetries? = NSOrderedSet(array: emptyTelemetry)
do {
try context.save()
return true
} catch {
context.rollback()
return false
}
} catch {
print("💥 Fetch NodeInfoEntity Error")
return false
@ -73,7 +69,7 @@ public func deleteChannelMessages(channel: ChannelEntity, context: NSManagedObje
}
public func deleteUserMessages(user: UserEntity, context: NSManagedObjectContext) {
do {
let objects = user.messageList
for object in objects {
@ -86,13 +82,13 @@ public func deleteUserMessages(user: UserEntity, context: NSManagedObjectContext
}
public func clearCoreDataDatabase(context: NSManagedObjectContext) {
let persistenceController = PersistenceController.shared.container
for i in 0...persistenceController.managedObjectModel.entities.count-1 {
let entity = persistenceController.managedObjectModel.entities[i]
let query = NSFetchRequest<NSFetchRequestResult>(entityName: entity.name!)
let deleteRequest = NSBatchDeleteRequest(fetchRequest: query)
do {
try context.executeAndMergeChanges(using: deleteRequest)
} catch let error as NSError {
@ -102,33 +98,37 @@ public func clearCoreDataDatabase(context: NSManagedObjectContext) {
}
func upsertPositionPacket (packet: MeshPacket, context: NSManagedObjectContext) {
let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.position.received %@", comment: "Position Packet received from node: %@"), String(packet.from))
MeshLogger.log("📍 \(logString)")
let fetchNodePositionRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "NodeInfoEntity")
fetchNodePositionRequest.predicate = NSPredicate(format: "num == %lld", Int64(packet.from))
do {
if let positionMessage = try? Position(serializedData: packet.decoded.payload) {
// Don't save empty position packets
if positionMessage.longitudeI > 0 || positionMessage.latitudeI > 0 && (positionMessage.latitudeI != 373346000 && positionMessage.longitudeI != -1220090000)
{
let fetchedNode = try context.fetch(fetchNodePositionRequest) as! [NodeInfoEntity]
if positionMessage.longitudeI > 0 || positionMessage.latitudeI > 0 && (positionMessage.latitudeI != 373346000 && positionMessage.longitudeI != -1220090000) {
guard let fetchedNode = try context.fetch(fetchNodePositionRequest) as? [NodeInfoEntity] else {
return
}
if fetchedNode.count == 1 {
// Unset the current latest position for this node
let fetchCurrentLatestPositionsRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "PositionEntity")
fetchCurrentLatestPositionsRequest.predicate = NSPredicate(format: "nodePosition.num == %lld && latest = true", Int64(packet.from))
let fetchedPositions = try context.fetch(fetchCurrentLatestPositionsRequest) as! [PositionEntity]
guard let fetchedPositions = try context.fetch(fetchCurrentLatestPositionsRequest) as? [PositionEntity] else {
return
}
if fetchedPositions.count > 0 {
for position in fetchedPositions {
position.latest = false
}
}
let position = PositionEntity(context: context)
position.latest = true
position.snr = packet.rxSnr
@ -144,15 +144,16 @@ func upsertPositionPacket (packet: MeshPacket, context: NSManagedObjectContext)
} else {
position.time = Date(timeIntervalSince1970: TimeInterval(Int64(positionMessage.time)))
}
let mutablePositions = fetchedNode[0].positions!.mutableCopy() as! NSMutableOrderedSet
guard let mutablePositions = fetchedNode[0].positions!.mutableCopy() as? NSMutableOrderedSet else {
return
}
mutablePositions.add(position)
fetchedNode[0].id = Int64(packet.from)
fetchedNode[0].num = Int64(packet.from)
fetchedNode[0].lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(positionMessage.time)))
fetchedNode[0].snr = packet.rxSnr
fetchedNode[0].positions = mutablePositions.copy() as? NSOrderedSet
do {
try context.save()
print("💾 Updated Node Position Coordinates, SNR and Time from Position App Packet For: \(fetchedNode[0].num)")
@ -164,7 +165,7 @@ func upsertPositionPacket (packet: MeshPacket, context: NSManagedObjectContext)
}
} else {
print("💥 Empty POSITION_APP Packet")
print(try! packet.jsonString())
print((try? packet.jsonString()) ?? "JSON Decode Failure")
}
}
} catch {
@ -173,16 +174,17 @@ func upsertPositionPacket (packet: MeshPacket, context: NSManagedObjectContext)
}
func upsertBluetoothConfigPacket(config: Meshtastic.Config.BluetoothConfig, nodeNum: Int64, context: NSManagedObjectContext) {
let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.bluetooth.config %@", comment: "Bluetooth config received: %@"), String(nodeNum))
MeshLogger.log("📶 \(logString)")
let fetchNodeInfoRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "NodeInfoEntity")
fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum))
do {
let fetchedNode = try context.fetch(fetchNodeInfoRequest) as! [NodeInfoEntity]
guard let fetchedNode = try context.fetch(fetchNodeInfoRequest) as? [NodeInfoEntity] else {
return
}
// Found a node, save Device Config
if !fetchedNode.isEmpty {
if fetchedNode[0].bluetoothConfig == nil {
@ -214,15 +216,16 @@ func upsertBluetoothConfigPacket(config: Meshtastic.Config.BluetoothConfig, node
}
func upsertDeviceConfigPacket(config: Meshtastic.Config.DeviceConfig, nodeNum: Int64, context: NSManagedObjectContext) {
let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.device.config %@", comment: "Device config received: %@"), String(nodeNum))
MeshLogger.log("📟 \(logString)")
let fetchNodeInfoRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "NodeInfoEntity")
fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum))
do {
let fetchedNode = try context.fetch(fetchNodeInfoRequest) as! [NodeInfoEntity]
guard let fetchedNode = try context.fetch(fetchNodeInfoRequest) as? [NodeInfoEntity] else {
return
}
// Found a node, save Device Config
if !fetchedNode.isEmpty {
if fetchedNode[0].deviceConfig == nil {
@ -232,6 +235,8 @@ func upsertDeviceConfigPacket(config: Meshtastic.Config.DeviceConfig, nodeNum: I
newDeviceConfig.debugLogEnabled = config.debugLogEnabled
newDeviceConfig.buttonGpio = Int32(config.buttonGpio)
newDeviceConfig.buzzerGpio = Int32(config.buzzerGpio)
newDeviceConfig.rebroadcastMode = Int32(config.rebroadcastMode.rawValue)
newDeviceConfig.nodeInfoBroadcastSecs = Int32(config.nodeInfoBroadcastSecs)
fetchedNode[0].deviceConfig = newDeviceConfig
} else {
fetchedNode[0].deviceConfig?.role = Int32(config.role.rawValue)
@ -239,6 +244,8 @@ func upsertDeviceConfigPacket(config: Meshtastic.Config.DeviceConfig, nodeNum: I
fetchedNode[0].deviceConfig?.debugLogEnabled = config.debugLogEnabled
fetchedNode[0].deviceConfig?.buttonGpio = Int32(config.buttonGpio)
fetchedNode[0].deviceConfig?.buzzerGpio = Int32(config.buzzerGpio)
fetchedNode[0].deviceConfig?.rebroadcastMode = Int32(config.rebroadcastMode.rawValue)
fetchedNode[0].deviceConfig?.nodeInfoBroadcastSecs = Int32(config.nodeInfoBroadcastSecs)
}
do {
try context.save()
@ -256,22 +263,22 @@ func upsertDeviceConfigPacket(config: Meshtastic.Config.DeviceConfig, nodeNum: I
}
func upsertDisplayConfigPacket(config: Meshtastic.Config.DisplayConfig, nodeNum: Int64, context: NSManagedObjectContext) {
let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.display.config %@", comment: "Display config received: %@"), String(nodeNum))
MeshLogger.log("🖥️ \(logString)")
let fetchNodeInfoRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "NodeInfoEntity")
fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum))
do {
let fetchedNode = try context.fetch(fetchNodeInfoRequest) as! [NodeInfoEntity]
guard let fetchedNode = try context.fetch(fetchNodeInfoRequest) as? [NodeInfoEntity] else {
return
}
// Found a node, save Device Config
if !fetchedNode.isEmpty {
if fetchedNode[0].displayConfig == nil {
let newDisplayConfig = DisplayConfigEntity(context: context)
newDisplayConfig.gpsFormat = Int32(config.gpsFormat.rawValue)
newDisplayConfig.screenOnSeconds = Int32(config.screenOnSecs)
@ -280,10 +287,11 @@ func upsertDisplayConfigPacket(config: Meshtastic.Config.DisplayConfig, nodeNum:
newDisplayConfig.flipScreen = config.flipScreen
newDisplayConfig.oledType = Int32(config.oled.rawValue)
newDisplayConfig.displayMode = Int32(config.displaymode.rawValue)
newDisplayConfig.headingBold = config.headingBold
fetchedNode[0].displayConfig = newDisplayConfig
} else {
fetchedNode[0].displayConfig?.gpsFormat = Int32(config.gpsFormat.rawValue)
fetchedNode[0].displayConfig?.screenOnSeconds = Int32(config.screenOnSecs)
fetchedNode[0].displayConfig?.screenCarouselInterval = Int32(config.autoScreenCarouselSecs)
@ -291,43 +299,44 @@ func upsertDisplayConfigPacket(config: Meshtastic.Config.DisplayConfig, nodeNum:
fetchedNode[0].displayConfig?.flipScreen = config.flipScreen
fetchedNode[0].displayConfig?.oledType = Int32(config.oled.rawValue)
fetchedNode[0].displayConfig?.displayMode = Int32(config.displaymode.rawValue)
fetchedNode[0].displayConfig?.headingBold = config.headingBold
}
do {
try context.save()
print("💾 Updated Display Config for node number: \(String(nodeNum))")
} catch {
context.rollback()
let nsError = error as NSError
print("💥 Error Updating Core Data DisplayConfigEntity: \(nsError)")
}
} else {
print("💥 No Nodes found in local database matching node number \(nodeNum) unable to save Display Config")
}
} catch {
let nsError = error as NSError
print("💥 Fetching node for core data DisplayConfigEntity failed: \(nsError)")
}
}
func upsertLoRaConfigPacket(config: Meshtastic.Config.LoRaConfig, nodeNum: Int64, context: NSManagedObjectContext) {
let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.lora.config %@", comment: "LoRa config received: %@"), String(nodeNum))
MeshLogger.log("📻 \(logString)")
let fetchNodeInfoRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "NodeInfoEntity")
fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", nodeNum)
do {
let fetchedNode = try context.fetch(fetchNodeInfoRequest) as! [NodeInfoEntity]
guard let fetchedNode = try context.fetch(fetchNodeInfoRequest) as? [NodeInfoEntity] else {
return
}
// Found a node, save LoRa Config
if fetchedNode.count > 0 {
if fetchedNode[0].loRaConfig == nil {
@ -381,16 +390,18 @@ func upsertLoRaConfigPacket(config: Meshtastic.Config.LoRaConfig, nodeNum: Int64
}
func upsertNetworkConfigPacket(config: Meshtastic.Config.NetworkConfig, nodeNum: Int64, context: NSManagedObjectContext) {
let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.network.config %@", comment: "Network config received: %@"), String(nodeNum))
MeshLogger.log("🌐 \(logString)")
let fetchNodeInfoRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "NodeInfoEntity")
fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum))
do {
let fetchedNode = try context.fetch(fetchNodeInfoRequest) as! [NodeInfoEntity]
guard let fetchedNode = try context.fetch(fetchNodeInfoRequest) as? [NodeInfoEntity] else {
return
}
// Found a node, save WiFi Config
if !fetchedNode.isEmpty {
if fetchedNode[0].networkConfig == nil {
@ -406,11 +417,11 @@ func upsertNetworkConfigPacket(config: Meshtastic.Config.NetworkConfig, nodeNum:
fetchedNode[0].networkConfig?.wifiSsid = config.wifiSsid
fetchedNode[0].networkConfig?.wifiPsk = config.wifiPsk
}
do {
try context.save()
print("💾 Updated Network Config for node number: \(String(nodeNum))")
} catch {
context.rollback()
let nsError = error as NSError
@ -426,16 +437,18 @@ func upsertNetworkConfigPacket(config: Meshtastic.Config.NetworkConfig, nodeNum:
}
func upsertPositionConfigPacket(config: Meshtastic.Config.PositionConfig, nodeNum: Int64, context: NSManagedObjectContext) {
let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.position.config %@", comment: "Positon config received: %@"), String(nodeNum))
MeshLogger.log("🗺️ \(logString)")
let fetchNodeInfoRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "NodeInfoEntity")
fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum))
do {
let fetchedNode = try context.fetch(fetchNodeInfoRequest) as! [NodeInfoEntity]
guard let fetchedNode = try context.fetch(fetchNodeInfoRequest) as? [NodeInfoEntity] else {
return
}
// Found a node, save LoRa Config
if !fetchedNode.isEmpty {
if fetchedNode[0].positionConfig == nil {
@ -475,24 +488,25 @@ func upsertPositionConfigPacket(config: Meshtastic.Config.PositionConfig, nodeNu
}
func upsertCannedMessagesModuleConfigPacket(config: Meshtastic.ModuleConfig.CannedMessageConfig, nodeNum: Int64, context: NSManagedObjectContext) {
let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.cannedmessage.config %@", comment: "Canned Message module config received: %@"), String(nodeNum))
MeshLogger.log("🥫 \(logString)")
let fetchNodeInfoRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "NodeInfoEntity")
fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum))
do {
let fetchedNode = try context.fetch(fetchNodeInfoRequest) as! [NodeInfoEntity]
guard let fetchedNode = try context.fetch(fetchNodeInfoRequest) as? [NodeInfoEntity] else {
return
}
// Found a node, save Canned Message Config
if !fetchedNode.isEmpty {
if fetchedNode[0].cannedMessageConfig == nil {
let newCannedMessageConfig = CannedMessageConfigEntity(context: context)
newCannedMessageConfig.enabled = config.enabled
newCannedMessageConfig.sendBell = config.sendBell
newCannedMessageConfig.rotary1Enabled = config.rotary1Enabled
@ -503,11 +517,11 @@ func upsertCannedMessagesModuleConfigPacket(config: Meshtastic.ModuleConfig.Cann
newCannedMessageConfig.inputbrokerEventCw = Int32(config.inputbrokerEventCw.rawValue)
newCannedMessageConfig.inputbrokerEventCcw = Int32(config.inputbrokerEventCcw.rawValue)
newCannedMessageConfig.inputbrokerEventPress = Int32(config.inputbrokerEventPress.rawValue)
fetchedNode[0].cannedMessageConfig = newCannedMessageConfig
} else {
fetchedNode[0].cannedMessageConfig?.enabled = config.enabled
fetchedNode[0].cannedMessageConfig?.sendBell = config.sendBell
fetchedNode[0].cannedMessageConfig?.rotary1Enabled = config.rotary1Enabled
@ -519,7 +533,7 @@ func upsertCannedMessagesModuleConfigPacket(config: Meshtastic.ModuleConfig.Cann
fetchedNode[0].cannedMessageConfig?.inputbrokerEventCcw = Int32(config.inputbrokerEventCcw.rawValue)
fetchedNode[0].cannedMessageConfig?.inputbrokerEventPress = Int32(config.inputbrokerEventPress.rawValue)
}
do {
try context.save()
print("💾 Updated Canned Message Module Config for node number: \(String(nodeNum))")
@ -538,19 +552,21 @@ func upsertCannedMessagesModuleConfigPacket(config: Meshtastic.ModuleConfig.Cann
}
func upsertExternalNotificationModuleConfigPacket(config: Meshtastic.ModuleConfig.ExternalNotificationConfig, nodeNum: Int64, context: NSManagedObjectContext) {
let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.externalnotification.config %@", comment: "External Notifiation module config received: %@"), String(nodeNum))
MeshLogger.log("📣 \(logString)")
let fetchNodeInfoRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "NodeInfoEntity")
fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum))
do {
let fetchedNode = try context.fetch(fetchNodeInfoRequest) as! [NodeInfoEntity]
guard let fetchedNode = try context.fetch(fetchNodeInfoRequest) as? [NodeInfoEntity] else {
return
}
// Found a node, save External Notificaitone Config
if !fetchedNode.isEmpty {
if fetchedNode[0].externalNotificationConfig == nil {
let newExternalNotificationConfig = ExternalNotificationConfigEntity(context: context)
newExternalNotificationConfig.enabled = config.enabled
@ -568,7 +584,7 @@ func upsertExternalNotificationModuleConfigPacket(config: Meshtastic.ModuleConfi
newExternalNotificationConfig.outputMilliseconds = Int32(config.outputMs)
newExternalNotificationConfig.nagTimeout = Int32(config.nagTimeout)
fetchedNode[0].externalNotificationConfig = newExternalNotificationConfig
} else {
fetchedNode[0].externalNotificationConfig?.enabled = config.enabled
fetchedNode[0].externalNotificationConfig?.usePWM = config.usePwm
@ -585,7 +601,7 @@ func upsertExternalNotificationModuleConfigPacket(config: Meshtastic.ModuleConfi
fetchedNode[0].externalNotificationConfig?.outputMilliseconds = Int32(config.outputMs)
fetchedNode[0].externalNotificationConfig?.nagTimeout = Int32(config.nagTimeout)
}
do {
try context.save()
print("💾 Updated External Notification Module Config for node number: \(String(nodeNum))")
@ -604,19 +620,21 @@ func upsertExternalNotificationModuleConfigPacket(config: Meshtastic.ModuleConfi
}
func upsertMqttModuleConfigPacket(config: Meshtastic.ModuleConfig.MQTTConfig, nodeNum: Int64, context: NSManagedObjectContext) {
let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.mqtt.config %@", comment: "MQTT module config received: %@"), String(nodeNum))
MeshLogger.log("🌉 \(logString)")
let fetchNodeInfoRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "NodeInfoEntity")
fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum))
do {
let fetchedNode = try context.fetch(fetchNodeInfoRequest) as! [NodeInfoEntity]
guard let fetchedNode = try context.fetch(fetchNodeInfoRequest) as? [NodeInfoEntity] else {
return
}
// Found a node, save MQTT Config
if !fetchedNode.isEmpty {
if fetchedNode[0].mqttConfig == nil {
let newMQTTConfig = MQTTConfigEntity(context: context)
newMQTTConfig.enabled = config.enabled
@ -652,16 +670,18 @@ func upsertMqttModuleConfigPacket(config: Meshtastic.ModuleConfig.MQTTConfig, no
}
func upsertRangeTestModuleConfigPacket(config: Meshtastic.ModuleConfig.RangeTestConfig, nodeNum: Int64, context: NSManagedObjectContext) {
let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.rangetest.config %@", comment: "Range Test module config received: %@"), String(nodeNum))
MeshLogger.log("⛰️ \(logString)")
let fetchNodeInfoRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "NodeInfoEntity")
fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum))
do {
let fetchedNode = try context.fetch(fetchNodeInfoRequest) as! [NodeInfoEntity]
guard let fetchedNode = try context.fetch(fetchNodeInfoRequest) as? [NodeInfoEntity] else {
return
}
// Found a node, save Device Config
if !fetchedNode.isEmpty {
if fetchedNode[0].rangeTestConfig == nil {
@ -693,22 +713,24 @@ func upsertRangeTestModuleConfigPacket(config: Meshtastic.ModuleConfig.RangeTest
}
func upsertSerialModuleConfigPacket(config: Meshtastic.ModuleConfig.SerialConfig, nodeNum: Int64, context: NSManagedObjectContext) {
let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.serial.config %@", comment: "Serial module config received: %@"), String(nodeNum))
MeshLogger.log("🤖 \(logString)")
let fetchNodeInfoRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "NodeInfoEntity")
fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum))
do {
let fetchedNode = try context.fetch(fetchNodeInfoRequest) as! [NodeInfoEntity]
guard let fetchedNode = try context.fetch(fetchNodeInfoRequest) as? [NodeInfoEntity] else {
return
}
// Found a node, save Device Config
if !fetchedNode.isEmpty {
if fetchedNode[0].serialConfig == nil {
let newSerialConfig = SerialConfigEntity(context: context)
newSerialConfig.enabled = config.enabled
newSerialConfig.echo = config.echo
@ -718,7 +740,7 @@ func upsertSerialModuleConfigPacket(config: Meshtastic.ModuleConfig.SerialConfig
newSerialConfig.timeout = Int32(config.timeout)
newSerialConfig.mode = Int32(config.mode.rawValue)
fetchedNode[0].serialConfig = newSerialConfig
} else {
fetchedNode[0].serialConfig?.enabled = config.enabled
fetchedNode[0].serialConfig?.echo = config.echo
@ -728,26 +750,26 @@ func upsertSerialModuleConfigPacket(config: Meshtastic.ModuleConfig.SerialConfig
fetchedNode[0].serialConfig?.timeout = Int32(config.timeout)
fetchedNode[0].serialConfig?.mode = Int32(config.mode.rawValue)
}
do {
try context.save()
print("💾 Updated Serial Module Config for node number: \(String(nodeNum))")
} catch {
context.rollback()
let nsError = error as NSError
print("💥 Error Updating Core Data SerialConfigEntity: \(nsError)")
}
} else {
print("💥 No Nodes found in local database matching node number \(nodeNum) unable to save Serial Module Config")
}
} catch {
let nsError = error as NSError
print("💥 Fetching node for core data SerialConfigEntity failed: \(nsError)")
}
@ -757,51 +779,53 @@ func upsertTelemetryModuleConfigPacket(config: Meshtastic.ModuleConfig.Telemetry
let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.telemetry.config %@", comment: "Telemetry module config received: %@"), String(nodeNum))
MeshLogger.log("📈 \(logString)")
let fetchNodeInfoRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "NodeInfoEntity")
fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum))
do {
let fetchedNode = try context.fetch(fetchNodeInfoRequest) as! [NodeInfoEntity]
guard let fetchedNode = try context.fetch(fetchNodeInfoRequest) as? [NodeInfoEntity] else {
return
}
// Found a node, save Telemetry Config
if !fetchedNode.isEmpty {
if fetchedNode[0].telemetryConfig == nil {
let newTelemetryConfig = TelemetryConfigEntity(context: context)
newTelemetryConfig.deviceUpdateInterval = Int32(config.deviceUpdateInterval)
newTelemetryConfig.environmentUpdateInterval = Int32(config.environmentUpdateInterval)
newTelemetryConfig.environmentMeasurementEnabled = config.environmentMeasurementEnabled
newTelemetryConfig.environmentScreenEnabled = config.environmentScreenEnabled
newTelemetryConfig.environmentDisplayFahrenheit = config.environmentDisplayFahrenheit
fetchedNode[0].telemetryConfig = newTelemetryConfig
} else {
fetchedNode[0].telemetryConfig?.deviceUpdateInterval = Int32(config.deviceUpdateInterval)
fetchedNode[0].telemetryConfig?.environmentUpdateInterval = Int32(config.environmentUpdateInterval)
fetchedNode[0].telemetryConfig?.environmentMeasurementEnabled = config.environmentMeasurementEnabled
fetchedNode[0].telemetryConfig?.environmentScreenEnabled = config.environmentScreenEnabled
fetchedNode[0].telemetryConfig?.environmentDisplayFahrenheit = config.environmentDisplayFahrenheit
}
do {
try context.save()
print("💾 Updated Telemetry Module Config for node number: \(String(nodeNum))")
} catch {
context.rollback()
let nsError = error as NSError
print("💥 Error Updating Core Data TelemetryConfigEntity: \(nsError)")
}
} else {
print("💥 No Nodes found in local database matching node number \(nodeNum) unable to save Telemetry Module Config")
}
} catch {
let nsError = error as NSError
print("💥 Fetching node for core data TelemetryConfigEntity failed: \(nsError)")

View file

@ -8,14 +8,14 @@
import Foundation
extension UserEntity {
var messageList: [MessageEntity] {
self.value(forKey: "allMessages") as? [MessageEntity] ?? [MessageEntity]()
}
var adminMessageList: [MessageEntity] {
self.value(forKey: "adminMessages") as? [MessageEntity] ?? [MessageEntity]()
}
}

View file

@ -37,7 +37,7 @@ extension WaypointEntity {
return nil
}
}
var annotaton: MKPointAnnotation {
let pointAnn = MKPointAnnotation()
if waypointCoordinate != nil {

View file

@ -15,7 +15,7 @@ import SwiftProtobuf
// incompatible with the version of SwiftProtobuf to which you are linking.
// Please ensure that you are building against the same version of the API
// that was used to generate this file.
fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck {
private struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck {
struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {}
typealias Version = _2
}
@ -31,7 +31,7 @@ struct AdminMessage {
///
/// TODO: REPLACE
var payloadVariant: AdminMessage.OneOf_PayloadVariant? = nil
var payloadVariant: AdminMessage.OneOf_PayloadVariant?
///
/// Send the specified channel in the response to this message
@ -752,7 +752,7 @@ extension AdminMessage.ConfigType: CaseIterable {
.networkConfig,
.displayConfig,
.loraConfig,
.bluetoothConfig,
.bluetoothConfig
]
}
@ -767,7 +767,7 @@ extension AdminMessage.ModuleConfigType: CaseIterable {
.telemetryConfig,
.cannedmsgConfig,
.audioConfig,
.remotehardwareConfig,
.remotehardwareConfig
]
}
@ -813,7 +813,7 @@ extension HamParameters: @unchecked Sendable {}
// MARK: - Code below here is support for the SwiftProtobuf runtime.
fileprivate let _protobuf_package = "meshtastic"
private let _protobuf_package = "meshtastic"
extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
static let protoMessageName: String = _protobuf_package + ".AdminMessage"
@ -848,7 +848,7 @@ extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat
97: .standard(proto: "reboot_seconds"),
98: .standard(proto: "shutdown_seconds"),
99: .standard(proto: "factory_reset"),
100: .standard(proto: "nodedb_reset"),
100: .standard(proto: "nodedb_reset")
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
@ -1315,7 +1315,7 @@ extension AdminMessage.ConfigType: SwiftProtobuf._ProtoNameProviding {
3: .same(proto: "NETWORK_CONFIG"),
4: .same(proto: "DISPLAY_CONFIG"),
5: .same(proto: "LORA_CONFIG"),
6: .same(proto: "BLUETOOTH_CONFIG"),
6: .same(proto: "BLUETOOTH_CONFIG")
]
}
@ -1329,7 +1329,7 @@ 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"),
8: .same(proto: "REMOTEHARDWARE_CONFIG")
]
}
@ -1339,7 +1339,7 @@ extension HamParameters: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementa
1: .standard(proto: "call_sign"),
2: .standard(proto: "tx_power"),
3: .same(proto: "frequency"),
4: .standard(proto: "short_name"),
4: .standard(proto: "short_name")
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {

View file

@ -15,7 +15,7 @@ import SwiftProtobuf
// incompatible with the version of SwiftProtobuf to which you are linking.
// Please ensure that you are building against the same version of the API
// that was used to generate this file.
fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck {
private struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck {
struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {}
typealias Version = _2
}
@ -50,7 +50,7 @@ struct ChannelSet {
init() {}
fileprivate var _loraConfig: Config.LoRaConfig? = nil
fileprivate var _loraConfig: Config.LoRaConfig?
}
#if swift(>=5.5) && canImport(_Concurrency)
@ -59,13 +59,13 @@ extension ChannelSet: @unchecked Sendable {}
// MARK: - Code below here is support for the SwiftProtobuf runtime.
fileprivate let _protobuf_package = "meshtastic"
private let _protobuf_package = "meshtastic"
extension ChannelSet: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
static let protoMessageName: String = _protobuf_package + ".ChannelSet"
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .same(proto: "settings"),
2: .standard(proto: "lora_config"),
2: .standard(proto: "lora_config")
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {

View file

@ -15,7 +15,7 @@ import SwiftProtobuf
// incompatible with the version of SwiftProtobuf to which you are linking.
// Please ensure that you are building against the same version of the API
// that was used to generate this file.
fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck {
private struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck {
struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {}
typealias Version = _2
}
@ -42,12 +42,12 @@ extension CannedMessageModuleConfig: @unchecked Sendable {}
// MARK: - Code below here is support for the SwiftProtobuf runtime.
fileprivate let _protobuf_package = "meshtastic"
private let _protobuf_package = "meshtastic"
extension CannedMessageModuleConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
static let protoMessageName: String = _protobuf_package + ".CannedMessageModuleConfig"
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .same(proto: "messages"),
1: .same(proto: "messages")
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {

View file

@ -15,7 +15,7 @@ import SwiftProtobuf
// incompatible with the version of SwiftProtobuf to which you are linking.
// Please ensure that you are building against the same version of the API
// that was used to generate this file.
fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck {
private struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck {
struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {}
typealias Version = _2
}
@ -183,7 +183,7 @@ struct Channel {
init() {}
fileprivate var _settings: ChannelSettings? = nil
fileprivate var _settings: ChannelSettings?
}
#if swift(>=4.2)
@ -193,7 +193,7 @@ extension Channel.Role: CaseIterable {
static var allCases: [Channel.Role] = [
.disabled,
.primary,
.secondary,
.secondary
]
}
@ -207,7 +207,7 @@ extension Channel.Role: @unchecked Sendable {}
// MARK: - Code below here is support for the SwiftProtobuf runtime.
fileprivate let _protobuf_package = "meshtastic"
private let _protobuf_package = "meshtastic"
extension ChannelSettings: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
static let protoMessageName: String = _protobuf_package + ".ChannelSettings"
@ -217,7 +217,7 @@ extension ChannelSettings: SwiftProtobuf.Message, SwiftProtobuf._MessageImplemen
3: .same(proto: "name"),
4: .same(proto: "id"),
5: .standard(proto: "uplink_enabled"),
6: .standard(proto: "downlink_enabled"),
6: .standard(proto: "downlink_enabled")
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
@ -276,7 +276,7 @@ extension Channel: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBa
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .same(proto: "index"),
2: .same(proto: "settings"),
3: .same(proto: "role"),
3: .same(proto: "role")
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
@ -323,6 +323,6 @@ extension Channel.Role: SwiftProtobuf._ProtoNameProviding {
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
0: .same(proto: "DISABLED"),
1: .same(proto: "PRIMARY"),
2: .same(proto: "SECONDARY"),
2: .same(proto: "SECONDARY")
]
}

View file

@ -15,7 +15,7 @@ import SwiftProtobuf
// incompatible with the version of SwiftProtobuf to which you are linking.
// Please ensure that you are building against the same version of the API
// that was used to generate this file.
fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck {
private struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck {
struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {}
typealias Version = _2
}
@ -27,7 +27,7 @@ struct Config {
///
/// Payload Variant
var payloadVariant: Config.OneOf_PayloadVariant? = nil
var payloadVariant: Config.OneOf_PayloadVariant?
var device: Config.DeviceConfig {
get {
@ -632,7 +632,7 @@ struct Config {
init() {}
fileprivate var _ipv4Config: Config.NetworkConfig.IpV4Config? = nil
fileprivate var _ipv4Config: Config.NetworkConfig.IpV4Config?
}
///
@ -1252,7 +1252,7 @@ extension Config.DeviceConfig.Role: CaseIterable {
.routerClient,
.repeater,
.tracker,
.sensor,
.sensor
]
}
@ -1261,7 +1261,7 @@ extension Config.DeviceConfig.RebroadcastMode: CaseIterable {
static var allCases: [Config.DeviceConfig.RebroadcastMode] = [
.all,
.allSkipDecoding,
.localOnly,
.localOnly
]
}
@ -1278,7 +1278,7 @@ extension Config.PositionConfig.PositionFlags: CaseIterable {
.seqNo,
.timestamp,
.heading,
.speed,
.speed
]
}
@ -1286,7 +1286,7 @@ extension Config.NetworkConfig.AddressMode: CaseIterable {
// The compiler won't synthesize support with the UNRECOGNIZED case.
static var allCases: [Config.NetworkConfig.AddressMode] = [
.dhcp,
.static,
.static
]
}
@ -1298,7 +1298,7 @@ extension Config.DisplayConfig.GpsCoordinateFormat: CaseIterable {
.utm,
.mgrs,
.olc,
.osgr,
.osgr
]
}
@ -1306,7 +1306,7 @@ extension Config.DisplayConfig.DisplayUnits: CaseIterable {
// The compiler won't synthesize support with the UNRECOGNIZED case.
static var allCases: [Config.DisplayConfig.DisplayUnits] = [
.metric,
.imperial,
.imperial
]
}
@ -1316,7 +1316,7 @@ extension Config.DisplayConfig.OledType: CaseIterable {
.oledAuto,
.oledSsd1306,
.oledSh1106,
.oledSh1107,
.oledSh1107
]
}
@ -1326,7 +1326,7 @@ extension Config.DisplayConfig.DisplayMode: CaseIterable {
.default,
.twocolor,
.inverted,
.color,
.color
]
}
@ -1348,7 +1348,7 @@ extension Config.LoRaConfig.RegionCode: CaseIterable {
.th,
.lora24,
.ua433,
.ua868,
.ua868
]
}
@ -1362,7 +1362,7 @@ extension Config.LoRaConfig.ModemPreset: CaseIterable {
.mediumFast,
.shortSlow,
.shortFast,
.longModerate,
.longModerate
]
}
@ -1371,7 +1371,7 @@ extension Config.BluetoothConfig.PairingMode: CaseIterable {
static var allCases: [Config.BluetoothConfig.PairingMode] = [
.randomPin,
.fixedPin,
.noPin,
.noPin
]
}
@ -1403,7 +1403,7 @@ extension Config.BluetoothConfig.PairingMode: @unchecked Sendable {}
// MARK: - Code below here is support for the SwiftProtobuf runtime.
fileprivate let _protobuf_package = "meshtastic"
private let _protobuf_package = "meshtastic"
extension Config: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
static let protoMessageName: String = _protobuf_package + ".Config"
@ -1414,7 +1414,7 @@ extension Config: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBas
4: .same(proto: "network"),
5: .same(proto: "display"),
6: .same(proto: "lora"),
7: .same(proto: "bluetooth"),
7: .same(proto: "bluetooth")
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
@ -1574,7 +1574,7 @@ extension Config.DeviceConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImpl
4: .standard(proto: "button_gpio"),
5: .standard(proto: "buzzer_gpio"),
6: .standard(proto: "rebroadcast_mode"),
7: .standard(proto: "node_info_broadcast_secs"),
7: .standard(proto: "node_info_broadcast_secs")
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
@ -1641,7 +1641,7 @@ extension Config.DeviceConfig.Role: SwiftProtobuf._ProtoNameProviding {
3: .same(proto: "ROUTER_CLIENT"),
4: .same(proto: "REPEATER"),
5: .same(proto: "TRACKER"),
6: .same(proto: "SENSOR"),
6: .same(proto: "SENSOR")
]
}
@ -1649,7 +1649,7 @@ extension Config.DeviceConfig.RebroadcastMode: SwiftProtobuf._ProtoNameProviding
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
0: .same(proto: "ALL"),
1: .same(proto: "ALL_SKIP_DECODING"),
2: .same(proto: "LOCAL_ONLY"),
2: .same(proto: "LOCAL_ONLY")
]
}
@ -1664,7 +1664,7 @@ extension Config.PositionConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageIm
6: .standard(proto: "gps_attempt_time"),
7: .standard(proto: "position_flags"),
8: .standard(proto: "rx_gpio"),
9: .standard(proto: "tx_gpio"),
9: .standard(proto: "tx_gpio")
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
@ -1745,7 +1745,7 @@ extension Config.PositionConfig.PositionFlags: SwiftProtobuf._ProtoNameProviding
64: .same(proto: "SEQ_NO"),
128: .same(proto: "TIMESTAMP"),
256: .same(proto: "HEADING"),
512: .same(proto: "SPEED"),
512: .same(proto: "SPEED")
]
}
@ -1759,7 +1759,7 @@ extension Config.PowerConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImple
5: .standard(proto: "mesh_sds_timeout_secs"),
6: .standard(proto: "sds_secs"),
7: .standard(proto: "ls_secs"),
8: .standard(proto: "min_wake_secs"),
8: .standard(proto: "min_wake_secs")
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
@ -1833,7 +1833,7 @@ extension Config.NetworkConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImp
6: .standard(proto: "eth_enabled"),
7: .standard(proto: "address_mode"),
8: .standard(proto: "ipv4_config"),
9: .standard(proto: "rsyslog_server"),
9: .standard(proto: "rsyslog_server")
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
@ -1904,7 +1904,7 @@ extension Config.NetworkConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImp
extension Config.NetworkConfig.AddressMode: SwiftProtobuf._ProtoNameProviding {
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
0: .same(proto: "DHCP"),
1: .same(proto: "STATIC"),
1: .same(proto: "STATIC")
]
}
@ -1914,7 +1914,7 @@ extension Config.NetworkConfig.IpV4Config: SwiftProtobuf.Message, SwiftProtobuf.
1: .same(proto: "ip"),
2: .same(proto: "gateway"),
3: .same(proto: "subnet"),
4: .same(proto: "dns"),
4: .same(proto: "dns")
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
@ -1969,7 +1969,7 @@ extension Config.DisplayConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImp
6: .same(proto: "units"),
7: .same(proto: "oled"),
8: .same(proto: "displaymode"),
9: .standard(proto: "heading_bold"),
9: .standard(proto: "heading_bold")
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
@ -2045,14 +2045,14 @@ extension Config.DisplayConfig.GpsCoordinateFormat: SwiftProtobuf._ProtoNameProv
2: .same(proto: "UTM"),
3: .same(proto: "MGRS"),
4: .same(proto: "OLC"),
5: .same(proto: "OSGR"),
5: .same(proto: "OSGR")
]
}
extension Config.DisplayConfig.DisplayUnits: SwiftProtobuf._ProtoNameProviding {
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
0: .same(proto: "METRIC"),
1: .same(proto: "IMPERIAL"),
1: .same(proto: "IMPERIAL")
]
}
@ -2061,7 +2061,7 @@ extension Config.DisplayConfig.OledType: SwiftProtobuf._ProtoNameProviding {
0: .same(proto: "OLED_AUTO"),
1: .same(proto: "OLED_SSD1306"),
2: .same(proto: "OLED_SH1106"),
3: .same(proto: "OLED_SH1107"),
3: .same(proto: "OLED_SH1107")
]
}
@ -2070,7 +2070,7 @@ extension Config.DisplayConfig.DisplayMode: SwiftProtobuf._ProtoNameProviding {
0: .same(proto: "DEFAULT"),
1: .same(proto: "TWOCOLOR"),
2: .same(proto: "INVERTED"),
3: .same(proto: "COLOR"),
3: .same(proto: "COLOR")
]
}
@ -2091,7 +2091,7 @@ extension Config.LoRaConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem
12: .standard(proto: "override_duty_cycle"),
13: .standard(proto: "sx126x_rx_boosted_gain"),
14: .standard(proto: "override_frequency"),
103: .standard(proto: "ignore_incoming"),
103: .standard(proto: "ignore_incoming")
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
@ -2207,7 +2207,7 @@ extension Config.LoRaConfig.RegionCode: SwiftProtobuf._ProtoNameProviding {
12: .same(proto: "TH"),
13: .same(proto: "LORA_24"),
14: .same(proto: "UA_433"),
15: .same(proto: "UA_868"),
15: .same(proto: "UA_868")
]
}
@ -2220,7 +2220,7 @@ extension Config.LoRaConfig.ModemPreset: SwiftProtobuf._ProtoNameProviding {
4: .same(proto: "MEDIUM_FAST"),
5: .same(proto: "SHORT_SLOW"),
6: .same(proto: "SHORT_FAST"),
7: .same(proto: "LONG_MODERATE"),
7: .same(proto: "LONG_MODERATE")
]
}
@ -2229,7 +2229,7 @@ extension Config.BluetoothConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageI
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .same(proto: "enabled"),
2: .same(proto: "mode"),
3: .standard(proto: "fixed_pin"),
3: .standard(proto: "fixed_pin")
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
@ -2272,6 +2272,6 @@ extension Config.BluetoothConfig.PairingMode: SwiftProtobuf._ProtoNameProviding
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
0: .same(proto: "RANDOM_PIN"),
1: .same(proto: "FIXED_PIN"),
2: .same(proto: "NO_PIN"),
2: .same(proto: "NO_PIN")
]
}

View file

@ -15,7 +15,7 @@ import SwiftProtobuf
// incompatible with the version of SwiftProtobuf to which you are linking.
// Please ensure that you are building against the same version of the API
// that was used to generate this file.
fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck {
private struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck {
struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {}
typealias Version = _2
}
@ -73,10 +73,10 @@ struct DeviceConnectionStatus {
init() {}
fileprivate var _wifi: WifiConnectionStatus? = nil
fileprivate var _ethernet: EthernetConnectionStatus? = nil
fileprivate var _bluetooth: BluetoothConnectionStatus? = nil
fileprivate var _serial: SerialConnectionStatus? = nil
fileprivate var _wifi: WifiConnectionStatus?
fileprivate var _ethernet: EthernetConnectionStatus?
fileprivate var _bluetooth: BluetoothConnectionStatus?
fileprivate var _serial: SerialConnectionStatus?
}
///
@ -109,7 +109,7 @@ struct WifiConnectionStatus {
init() {}
fileprivate var _status: NetworkConnectionStatus? = nil
fileprivate var _status: NetworkConnectionStatus?
}
///
@ -134,7 +134,7 @@ struct EthernetConnectionStatus {
init() {}
fileprivate var _status: NetworkConnectionStatus? = nil
fileprivate var _status: NetworkConnectionStatus?
}
///
@ -220,7 +220,7 @@ extension SerialConnectionStatus: @unchecked Sendable {}
// MARK: - Code below here is support for the SwiftProtobuf runtime.
fileprivate let _protobuf_package = "meshtastic"
private let _protobuf_package = "meshtastic"
extension DeviceConnectionStatus: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
static let protoMessageName: String = _protobuf_package + ".DeviceConnectionStatus"
@ -228,7 +228,7 @@ extension DeviceConnectionStatus: SwiftProtobuf.Message, SwiftProtobuf._MessageI
1: .same(proto: "wifi"),
2: .same(proto: "ethernet"),
3: .same(proto: "bluetooth"),
4: .same(proto: "serial"),
4: .same(proto: "serial")
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
@ -281,7 +281,7 @@ extension WifiConnectionStatus: SwiftProtobuf.Message, SwiftProtobuf._MessageImp
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .same(proto: "status"),
2: .same(proto: "ssid"),
3: .same(proto: "rssi"),
3: .same(proto: "rssi")
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
@ -327,7 +327,7 @@ extension WifiConnectionStatus: SwiftProtobuf.Message, SwiftProtobuf._MessageImp
extension EthernetConnectionStatus: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
static let protoMessageName: String = _protobuf_package + ".EthernetConnectionStatus"
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .same(proto: "status"),
1: .same(proto: "status")
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
@ -366,7 +366,7 @@ extension NetworkConnectionStatus: SwiftProtobuf.Message, SwiftProtobuf._Message
1: .standard(proto: "ip_address"),
2: .standard(proto: "is_connected"),
3: .standard(proto: "is_mqtt_connected"),
4: .standard(proto: "is_syslog_connected"),
4: .standard(proto: "is_syslog_connected")
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
@ -415,7 +415,7 @@ extension BluetoothConnectionStatus: SwiftProtobuf.Message, SwiftProtobuf._Messa
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .same(proto: "pin"),
2: .same(proto: "rssi"),
3: .standard(proto: "is_connected"),
3: .standard(proto: "is_connected")
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
@ -458,7 +458,7 @@ extension SerialConnectionStatus: SwiftProtobuf.Message, SwiftProtobuf._MessageI
static let protoMessageName: String = _protobuf_package + ".SerialConnectionStatus"
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .same(proto: "baud"),
2: .standard(proto: "is_connected"),
2: .standard(proto: "is_connected")
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {

View file

@ -15,7 +15,7 @@ import SwiftProtobuf
// incompatible with the version of SwiftProtobuf to which you are linking.
// Please ensure that you are building against the same version of the API
// that was used to generate this file.
fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck {
private struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck {
struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {}
typealias Version = _2
}
@ -69,7 +69,7 @@ extension ScreenFonts: CaseIterable {
static var allCases: [ScreenFonts] = [
.fontSmall,
.fontMedium,
.fontLarge,
.fontLarge
]
}
@ -246,8 +246,8 @@ struct OEMStore {
init() {}
fileprivate var _oemLocalConfig: LocalConfig? = nil
fileprivate var _oemLocalModuleConfig: LocalModuleConfig? = nil
fileprivate var _oemLocalConfig: LocalConfig?
fileprivate var _oemLocalModuleConfig: LocalModuleConfig?
}
#if swift(>=5.5) && canImport(_Concurrency)
@ -259,13 +259,13 @@ extension OEMStore: @unchecked Sendable {}
// MARK: - Code below here is support for the SwiftProtobuf runtime.
fileprivate let _protobuf_package = "meshtastic"
private let _protobuf_package = "meshtastic"
extension ScreenFonts: SwiftProtobuf._ProtoNameProviding {
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
0: .same(proto: "FONT_SMALL"),
1: .same(proto: "FONT_MEDIUM"),
2: .same(proto: "FONT_LARGE"),
2: .same(proto: "FONT_LARGE")
]
}
@ -279,16 +279,16 @@ extension DeviceState: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementati
8: .same(proto: "version"),
7: .standard(proto: "rx_text_message"),
9: .standard(proto: "no_save"),
11: .standard(proto: "did_gps_reset"),
11: .standard(proto: "did_gps_reset")
]
fileprivate class _StorageClass {
var _myNode: MyNodeInfo? = nil
var _owner: User? = nil
var _myNode: MyNodeInfo?
var _owner: User?
var _nodeDb: [NodeInfo] = []
var _receiveQueue: [MeshPacket] = []
var _version: UInt32 = 0
var _rxTextMessage: MeshPacket? = nil
var _rxTextMessage: MeshPacket?
var _noSave: Bool = false
var _didGpsReset: Bool = false
@ -397,7 +397,7 @@ extension ChannelFile: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementati
static let protoMessageName: String = _protobuf_package + ".ChannelFile"
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .same(proto: "channels"),
2: .same(proto: "version"),
2: .same(proto: "version")
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
@ -441,7 +441,7 @@ extension OEMStore: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB
5: .standard(proto: "oem_text"),
6: .standard(proto: "oem_aes_key"),
7: .standard(proto: "oem_local_config"),
8: .standard(proto: "oem_local_module_config"),
8: .standard(proto: "oem_local_module_config")
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {

View file

@ -15,7 +15,7 @@ import SwiftProtobuf
// incompatible with the version of SwiftProtobuf to which you are linking.
// Please ensure that you are building against the same version of the API
// that was used to generate this file.
fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck {
private struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck {
struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {}
typealias Version = _2
}
@ -245,7 +245,7 @@ extension LocalModuleConfig: @unchecked Sendable {}
// MARK: - Code below here is support for the SwiftProtobuf runtime.
fileprivate let _protobuf_package = "meshtastic"
private let _protobuf_package = "meshtastic"
extension LocalConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
static let protoMessageName: String = _protobuf_package + ".LocalConfig"
@ -257,17 +257,17 @@ extension LocalConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementati
5: .same(proto: "display"),
6: .same(proto: "lora"),
7: .same(proto: "bluetooth"),
8: .same(proto: "version"),
8: .same(proto: "version")
]
fileprivate class _StorageClass {
var _device: Config.DeviceConfig? = nil
var _position: Config.PositionConfig? = nil
var _power: Config.PowerConfig? = nil
var _network: Config.NetworkConfig? = nil
var _display: Config.DisplayConfig? = nil
var _lora: Config.LoRaConfig? = nil
var _bluetooth: Config.BluetoothConfig? = nil
var _device: Config.DeviceConfig?
var _position: Config.PositionConfig?
var _power: Config.PowerConfig?
var _network: Config.NetworkConfig?
var _display: Config.DisplayConfig?
var _lora: Config.LoRaConfig?
var _bluetooth: Config.BluetoothConfig?
var _version: UInt32 = 0
static let defaultInstance = _StorageClass()
@ -383,19 +383,19 @@ extension LocalModuleConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem
7: .standard(proto: "canned_message"),
9: .same(proto: "audio"),
10: .standard(proto: "remote_hardware"),
8: .same(proto: "version"),
8: .same(proto: "version")
]
fileprivate class _StorageClass {
var _mqtt: ModuleConfig.MQTTConfig? = nil
var _serial: ModuleConfig.SerialConfig? = nil
var _externalNotification: ModuleConfig.ExternalNotificationConfig? = nil
var _storeForward: ModuleConfig.StoreForwardConfig? = nil
var _rangeTest: ModuleConfig.RangeTestConfig? = nil
var _telemetry: ModuleConfig.TelemetryConfig? = nil
var _cannedMessage: ModuleConfig.CannedMessageConfig? = nil
var _audio: ModuleConfig.AudioConfig? = nil
var _remoteHardware: ModuleConfig.RemoteHardwareConfig? = nil
var _mqtt: ModuleConfig.MQTTConfig?
var _serial: ModuleConfig.SerialConfig?
var _externalNotification: ModuleConfig.ExternalNotificationConfig?
var _storeForward: ModuleConfig.StoreForwardConfig?
var _rangeTest: ModuleConfig.RangeTestConfig?
var _telemetry: ModuleConfig.TelemetryConfig?
var _cannedMessage: ModuleConfig.CannedMessageConfig?
var _audio: ModuleConfig.AudioConfig?
var _remoteHardware: ModuleConfig.RemoteHardwareConfig?
var _version: UInt32 = 0
static let defaultInstance = _StorageClass()

View file

@ -15,7 +15,7 @@ import SwiftProtobuf
// incompatible with the version of SwiftProtobuf to which you are linking.
// Please ensure that you are building against the same version of the API
// that was used to generate this file.
fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck {
private struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck {
struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {}
typealias Version = _2
}
@ -302,7 +302,7 @@ extension HardwareModel: CaseIterable {
.heltecWslV3,
.betafpv2400Tx,
.betafpv900NanoTx,
.privateHw,
.privateHw
]
}
@ -353,7 +353,7 @@ extension Constants: CaseIterable {
// The compiler won't synthesize support with the UNRECOGNIZED case.
static var allCases: [Constants] = [
.zero,
.dataPayloadLen,
.dataPayloadLen
]
}
@ -476,7 +476,7 @@ extension CriticalErrorCode: CaseIterable {
.transmitFailed,
.brownout,
.sx1262Failure,
.radioSpiBug,
.radioSpiBug
]
}
@ -781,7 +781,7 @@ extension Position.LocSource: CaseIterable {
.locUnset,
.locManual,
.locInternal,
.locExternal,
.locExternal
]
}
@ -792,7 +792,7 @@ extension Position.AltSource: CaseIterable {
.altManual,
.altInternal,
.altExternal,
.altBarometric,
.altBarometric
]
}
@ -886,7 +886,7 @@ struct Routing {
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
var variant: Routing.OneOf_Variant? = nil
var variant: Routing.OneOf_Variant?
///
/// A route request going from the requester
@ -1075,7 +1075,7 @@ extension Routing.Error: CaseIterable {
.noResponse,
.dutyCycleLimit,
.badRequest,
.notAuthorized,
.notAuthorized
]
}
@ -1503,7 +1503,7 @@ extension MeshPacket.Priority: CaseIterable {
.default,
.reliable,
.ack,
.max,
.max
]
}
@ -1512,7 +1512,7 @@ extension MeshPacket.Delayed: CaseIterable {
static var allCases: [MeshPacket.Delayed] = [
.noDelay,
.broadcast,
.direct,
.direct
]
}
@ -1591,9 +1591,9 @@ struct NodeInfo {
init() {}
fileprivate var _user: User? = nil
fileprivate var _position: Position? = nil
fileprivate var _deviceMetrics: DeviceMetrics? = nil
fileprivate var _user: User?
fileprivate var _position: Position?
fileprivate var _deviceMetrics: DeviceMetrics?
}
///
@ -1796,7 +1796,7 @@ extension LogRecord.Level: CaseIterable {
.warning,
.info,
.debug,
.trace,
.trace
]
}
@ -2102,7 +2102,7 @@ struct ToRadio {
///
/// Log levels, chosen to match python logging conventions.
var payloadVariant: ToRadio.OneOf_PayloadVariant? = nil
var payloadVariant: ToRadio.OneOf_PayloadVariant?
///
/// Send this packet on the mesh
@ -2308,7 +2308,7 @@ extension DeviceMetadata: @unchecked Sendable {}
// MARK: - Code below here is support for the SwiftProtobuf runtime.
fileprivate let _protobuf_package = "meshtastic"
private let _protobuf_package = "meshtastic"
extension HardwareModel: SwiftProtobuf._ProtoNameProviding {
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
@ -2346,14 +2346,14 @@ extension HardwareModel: SwiftProtobuf._ProtoNameProviding {
44: .same(proto: "HELTEC_WSL_V3"),
45: .same(proto: "BETAFPV_2400_TX"),
46: .same(proto: "BETAFPV_900_NANO_TX"),
255: .same(proto: "PRIVATE_HW"),
255: .same(proto: "PRIVATE_HW")
]
}
extension Constants: SwiftProtobuf._ProtoNameProviding {
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
0: .same(proto: "ZERO"),
237: .same(proto: "DATA_PAYLOAD_LEN"),
237: .same(proto: "DATA_PAYLOAD_LEN")
]
}
@ -2370,7 +2370,7 @@ extension CriticalErrorCode: SwiftProtobuf._ProtoNameProviding {
8: .same(proto: "TRANSMIT_FAILED"),
9: .same(proto: "BROWNOUT"),
10: .same(proto: "SX1262_FAILURE"),
11: .same(proto: "RADIO_SPI_BUG"),
11: .same(proto: "RADIO_SPI_BUG")
]
}
@ -2398,7 +2398,7 @@ extension Position: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB
19: .standard(proto: "sats_in_view"),
20: .standard(proto: "sensor_id"),
21: .standard(proto: "next_update"),
22: .standard(proto: "seq_number"),
22: .standard(proto: "seq_number")
]
fileprivate class _StorageClass {
@ -2611,7 +2611,7 @@ extension Position.LocSource: SwiftProtobuf._ProtoNameProviding {
0: .same(proto: "LOC_UNSET"),
1: .same(proto: "LOC_MANUAL"),
2: .same(proto: "LOC_INTERNAL"),
3: .same(proto: "LOC_EXTERNAL"),
3: .same(proto: "LOC_EXTERNAL")
]
}
@ -2621,7 +2621,7 @@ extension Position.AltSource: SwiftProtobuf._ProtoNameProviding {
1: .same(proto: "ALT_MANUAL"),
2: .same(proto: "ALT_INTERNAL"),
3: .same(proto: "ALT_EXTERNAL"),
4: .same(proto: "ALT_BAROMETRIC"),
4: .same(proto: "ALT_BAROMETRIC")
]
}
@ -2633,7 +2633,7 @@ extension User: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase,
3: .standard(proto: "short_name"),
4: .same(proto: "macaddr"),
5: .standard(proto: "hw_model"),
6: .standard(proto: "is_licensed"),
6: .standard(proto: "is_licensed")
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
@ -2690,7 +2690,7 @@ extension User: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase,
extension RouteDiscovery: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
static let protoMessageName: String = _protobuf_package + ".RouteDiscovery"
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .same(proto: "route"),
1: .same(proto: "route")
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
@ -2724,7 +2724,7 @@ extension Routing: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBa
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .standard(proto: "route_request"),
2: .standard(proto: "route_reply"),
3: .standard(proto: "error_reason"),
3: .standard(proto: "error_reason")
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
@ -2815,7 +2815,7 @@ extension Routing.Error: SwiftProtobuf._ProtoNameProviding {
8: .same(proto: "NO_RESPONSE"),
9: .same(proto: "DUTY_CYCLE_LIMIT"),
32: .same(proto: "BAD_REQUEST"),
33: .same(proto: "NOT_AUTHORIZED"),
33: .same(proto: "NOT_AUTHORIZED")
]
}
@ -2829,7 +2829,7 @@ extension DataMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementati
5: .same(proto: "source"),
6: .standard(proto: "request_id"),
7: .standard(proto: "reply_id"),
8: .same(proto: "emoji"),
8: .same(proto: "emoji")
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
@ -2903,7 +2903,7 @@ extension Waypoint: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB
5: .standard(proto: "locked_to"),
6: .same(proto: "name"),
7: .same(proto: "description"),
8: .same(proto: "icon"),
8: .same(proto: "icon")
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
@ -2982,7 +2982,7 @@ extension MeshPacket: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio
10: .standard(proto: "want_ack"),
11: .same(proto: "priority"),
12: .standard(proto: "rx_rssi"),
13: .same(proto: "delayed"),
13: .same(proto: "delayed")
]
fileprivate class _StorageClass {
@ -3160,7 +3160,7 @@ extension MeshPacket.Priority: SwiftProtobuf._ProtoNameProviding {
64: .same(proto: "DEFAULT"),
70: .same(proto: "RELIABLE"),
120: .same(proto: "ACK"),
127: .same(proto: "MAX"),
127: .same(proto: "MAX")
]
}
@ -3168,7 +3168,7 @@ extension MeshPacket.Delayed: SwiftProtobuf._ProtoNameProviding {
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
0: .same(proto: "NO_DELAY"),
1: .same(proto: "DELAYED_BROADCAST"),
2: .same(proto: "DELAYED_DIRECT"),
2: .same(proto: "DELAYED_DIRECT")
]
}
@ -3180,7 +3180,7 @@ extension NodeInfo: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB
3: .same(proto: "position"),
4: .same(proto: "snr"),
5: .standard(proto: "last_heard"),
6: .standard(proto: "device_metrics"),
6: .standard(proto: "device_metrics")
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
@ -3256,7 +3256,7 @@ extension MyNodeInfo: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio
13: .standard(proto: "air_period_rx"),
14: .standard(proto: "has_wifi"),
15: .standard(proto: "channel_utilization"),
16: .standard(proto: "air_util_tx"),
16: .standard(proto: "air_util_tx")
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
@ -3366,7 +3366,7 @@ extension LogRecord: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementation
1: .same(proto: "message"),
2: .same(proto: "time"),
3: .same(proto: "source"),
4: .same(proto: "level"),
4: .same(proto: "level")
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
@ -3418,7 +3418,7 @@ extension LogRecord.Level: SwiftProtobuf._ProtoNameProviding {
20: .same(proto: "INFO"),
30: .same(proto: "WARNING"),
40: .same(proto: "ERROR"),
50: .same(proto: "CRITICAL"),
50: .same(proto: "CRITICAL")
]
}
@ -3428,7 +3428,7 @@ extension QueueStatus: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementati
1: .same(proto: "res"),
2: .same(proto: "free"),
3: .same(proto: "maxlen"),
4: .standard(proto: "mesh_packet_id"),
4: .standard(proto: "mesh_packet_id")
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
@ -3487,7 +3487,7 @@ extension FromRadio: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementation
10: .same(proto: "channel"),
11: .same(proto: "queueStatus"),
12: .same(proto: "xmodemPacket"),
13: .same(proto: "metadata"),
13: .same(proto: "metadata")
]
fileprivate class _StorageClass {
@ -3758,7 +3758,7 @@ extension ToRadio: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBa
1: .same(proto: "packet"),
3: .standard(proto: "want_config_id"),
4: .same(proto: "disconnect"),
5: .same(proto: "xmodemPacket"),
5: .same(proto: "xmodemPacket")
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
@ -3852,7 +3852,7 @@ extension Compressed: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio
static let protoMessageName: String = _protobuf_package + ".Compressed"
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .same(proto: "portnum"),
2: .same(proto: "data"),
2: .same(proto: "data")
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
@ -3897,7 +3897,7 @@ extension DeviceMetadata: SwiftProtobuf.Message, SwiftProtobuf._MessageImplement
6: .same(proto: "hasEthernet"),
7: .same(proto: "role"),
8: .standard(proto: "position_flags"),
9: .standard(proto: "hw_model"),
9: .standard(proto: "hw_model")
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {

View file

@ -15,7 +15,7 @@ import SwiftProtobuf
// incompatible with the version of SwiftProtobuf to which you are linking.
// Please ensure that you are building against the same version of the API
// that was used to generate this file.
fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck {
private struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck {
struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {}
typealias Version = _2
}
@ -29,7 +29,7 @@ struct ModuleConfig {
///
/// TODO: REPLACE
var payloadVariant: ModuleConfig.OneOf_PayloadVariant? = nil
var payloadVariant: ModuleConfig.OneOf_PayloadVariant?
///
/// TODO: REPLACE
@ -829,7 +829,7 @@ extension ModuleConfig.AudioConfig.Audio_Baud: CaseIterable {
.codec21300,
.codec21200,
.codec2700,
.codec2700B,
.codec2700B
]
}
@ -851,7 +851,7 @@ extension ModuleConfig.SerialConfig.Serial_Baud: CaseIterable {
.baud230400,
.baud460800,
.baud576000,
.baud921600,
.baud921600
]
}
@ -862,7 +862,7 @@ extension ModuleConfig.SerialConfig.Serial_Mode: CaseIterable {
.simple,
.proto,
.textmsg,
.nmea,
.nmea
]
}
@ -876,7 +876,7 @@ extension ModuleConfig.CannedMessageConfig.InputEventChar: CaseIterable {
.right,
.select,
.back,
.cancel,
.cancel
]
}
@ -902,7 +902,7 @@ extension ModuleConfig.CannedMessageConfig.InputEventChar: @unchecked Sendable {
// MARK: - Code below here is support for the SwiftProtobuf runtime.
fileprivate let _protobuf_package = "meshtastic"
private let _protobuf_package = "meshtastic"
extension ModuleConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
static let protoMessageName: String = _protobuf_package + ".ModuleConfig"
@ -915,7 +915,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"),
9: .standard(proto: "remote_hardware")
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
@ -1108,7 +1108,7 @@ extension ModuleConfig.MQTTConfig: SwiftProtobuf.Message, SwiftProtobuf._Message
3: .same(proto: "username"),
4: .same(proto: "password"),
5: .standard(proto: "encryption_enabled"),
6: .standard(proto: "json_enabled"),
6: .standard(proto: "json_enabled")
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
@ -1165,7 +1165,7 @@ 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"),
1: .same(proto: "enabled")
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
@ -1203,7 +1203,7 @@ extension ModuleConfig.AudioConfig: SwiftProtobuf.Message, SwiftProtobuf._Messag
4: .standard(proto: "i2s_ws"),
5: .standard(proto: "i2s_sd"),
6: .standard(proto: "i2s_din"),
7: .standard(proto: "i2s_sck"),
7: .standard(proto: "i2s_sck")
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
@ -1272,7 +1272,7 @@ extension ModuleConfig.AudioConfig.Audio_Baud: SwiftProtobuf._ProtoNameProviding
5: .same(proto: "CODEC2_1300"),
6: .same(proto: "CODEC2_1200"),
7: .same(proto: "CODEC2_700"),
8: .same(proto: "CODEC2_700B"),
8: .same(proto: "CODEC2_700B")
]
}
@ -1285,7 +1285,7 @@ extension ModuleConfig.SerialConfig: SwiftProtobuf.Message, SwiftProtobuf._Messa
4: .same(proto: "txd"),
5: .same(proto: "baud"),
6: .same(proto: "timeout"),
7: .same(proto: "mode"),
7: .same(proto: "mode")
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
@ -1361,7 +1361,7 @@ extension ModuleConfig.SerialConfig.Serial_Baud: SwiftProtobuf._ProtoNameProvidi
12: .same(proto: "BAUD_230400"),
13: .same(proto: "BAUD_460800"),
14: .same(proto: "BAUD_576000"),
15: .same(proto: "BAUD_921600"),
15: .same(proto: "BAUD_921600")
]
}
@ -1371,7 +1371,7 @@ extension ModuleConfig.SerialConfig.Serial_Mode: SwiftProtobuf._ProtoNameProvidi
1: .same(proto: "SIMPLE"),
2: .same(proto: "PROTO"),
3: .same(proto: "TEXTMSG"),
4: .same(proto: "NMEA"),
4: .same(proto: "NMEA")
]
}
@ -1391,7 +1391,7 @@ extension ModuleConfig.ExternalNotificationConfig: SwiftProtobuf.Message, SwiftP
12: .standard(proto: "alert_bell_vibra"),
13: .standard(proto: "alert_bell_buzzer"),
7: .standard(proto: "use_pwm"),
14: .standard(proto: "nag_timeout"),
14: .standard(proto: "nag_timeout")
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
@ -1492,7 +1492,7 @@ extension ModuleConfig.StoreForwardConfig: SwiftProtobuf.Message, SwiftProtobuf.
2: .same(proto: "heartbeat"),
3: .same(proto: "records"),
4: .standard(proto: "history_return_max"),
5: .standard(proto: "history_return_window"),
5: .standard(proto: "history_return_window")
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
@ -1546,7 +1546,7 @@ extension ModuleConfig.RangeTestConfig: SwiftProtobuf.Message, SwiftProtobuf._Me
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .same(proto: "enabled"),
2: .same(proto: "sender"),
3: .same(proto: "save"),
3: .same(proto: "save")
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
@ -1594,7 +1594,7 @@ extension ModuleConfig.TelemetryConfig: SwiftProtobuf.Message, SwiftProtobuf._Me
4: .standard(proto: "environment_screen_enabled"),
5: .standard(proto: "environment_display_fahrenheit"),
6: .standard(proto: "air_quality_enabled"),
7: .standard(proto: "air_quality_interval"),
7: .standard(proto: "air_quality_interval")
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
@ -1666,7 +1666,7 @@ extension ModuleConfig.CannedMessageConfig: SwiftProtobuf.Message, SwiftProtobuf
8: .standard(proto: "updown1_enabled"),
9: .same(proto: "enabled"),
10: .standard(proto: "allow_input_source"),
11: .standard(proto: "send_bell"),
11: .standard(proto: "send_bell")
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
@ -1754,6 +1754,6 @@ extension ModuleConfig.CannedMessageConfig.InputEventChar: SwiftProtobuf._ProtoN
19: .same(proto: "LEFT"),
20: .same(proto: "RIGHT"),
24: .same(proto: "CANCEL"),
27: .same(proto: "BACK"),
27: .same(proto: "BACK")
]
}

View file

@ -15,7 +15,7 @@ import SwiftProtobuf
// incompatible with the version of SwiftProtobuf to which you are linking.
// Please ensure that you are building against the same version of the API
// that was used to generate this file.
fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck {
private struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck {
struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {}
typealias Version = _2
}
@ -52,7 +52,7 @@ struct ServiceEnvelope {
init() {}
fileprivate var _packet: MeshPacket? = nil
fileprivate var _packet: MeshPacket?
}
#if swift(>=5.5) && canImport(_Concurrency)
@ -61,14 +61,14 @@ extension ServiceEnvelope: @unchecked Sendable {}
// MARK: - Code below here is support for the SwiftProtobuf runtime.
fileprivate let _protobuf_package = "meshtastic"
private let _protobuf_package = "meshtastic"
extension ServiceEnvelope: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
static let protoMessageName: String = _protobuf_package + ".ServiceEnvelope"
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .same(proto: "packet"),
2: .standard(proto: "channel_id"),
3: .standard(proto: "gateway_id"),
3: .standard(proto: "gateway_id")
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {

View file

@ -15,7 +15,7 @@ import SwiftProtobuf
// incompatible with the version of SwiftProtobuf to which you are linking.
// Please ensure that you are building against the same version of the API
// that was used to generate this file.
fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck {
private struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck {
struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {}
typealias Version = _2
}
@ -238,7 +238,7 @@ extension PortNum: CaseIterable {
.tracerouteApp,
.privateApp,
.atakForwarder,
.max,
.max
]
}
@ -273,6 +273,6 @@ extension PortNum: SwiftProtobuf._ProtoNameProviding {
70: .same(proto: "TRACEROUTE_APP"),
256: .same(proto: "PRIVATE_APP"),
257: .same(proto: "ATAK_FORWARDER"),
511: .same(proto: "MAX"),
511: .same(proto: "MAX")
]
}

View file

@ -15,7 +15,7 @@ import SwiftProtobuf
// incompatible with the version of SwiftProtobuf to which you are linking.
// Please ensure that you are building against the same version of the API
// that was used to generate this file.
fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck {
private struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck {
struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {}
typealias Version = _2
}
@ -125,7 +125,7 @@ extension HardwareMessage.TypeEnum: CaseIterable {
.watchGpios,
.gpiosChanged,
.readGpios,
.readGpiosReply,
.readGpiosReply
]
}
@ -138,14 +138,14 @@ extension HardwareMessage.TypeEnum: @unchecked Sendable {}
// MARK: - Code below here is support for the SwiftProtobuf runtime.
fileprivate let _protobuf_package = "meshtastic"
private let _protobuf_package = "meshtastic"
extension HardwareMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
static let protoMessageName: String = _protobuf_package + ".HardwareMessage"
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .same(proto: "type"),
2: .standard(proto: "gpio_mask"),
3: .standard(proto: "gpio_value"),
3: .standard(proto: "gpio_value")
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
@ -191,6 +191,6 @@ extension HardwareMessage.TypeEnum: SwiftProtobuf._ProtoNameProviding {
2: .same(proto: "WATCH_GPIOS"),
3: .same(proto: "GPIOS_CHANGED"),
4: .same(proto: "READ_GPIOS"),
5: .same(proto: "READ_GPIOS_REPLY"),
5: .same(proto: "READ_GPIOS_REPLY")
]
}

View file

@ -15,7 +15,7 @@ import SwiftProtobuf
// incompatible with the version of SwiftProtobuf to which you are linking.
// Please ensure that you are building against the same version of the API
// that was used to generate this file.
fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck {
private struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck {
struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {}
typealias Version = _2
}
@ -42,12 +42,12 @@ extension RTTTLConfig: @unchecked Sendable {}
// MARK: - Code below here is support for the SwiftProtobuf runtime.
fileprivate let _protobuf_package = "meshtastic"
private let _protobuf_package = "meshtastic"
extension RTTTLConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
static let protoMessageName: String = _protobuf_package + ".RTTTLConfig"
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .same(proto: "ringtone"),
1: .same(proto: "ringtone")
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {

View file

@ -15,7 +15,7 @@ import SwiftProtobuf
// incompatible with the version of SwiftProtobuf to which you are linking.
// Please ensure that you are building against the same version of the API
// that was used to generate this file.
fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck {
private struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck {
struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {}
typealias Version = _2
}
@ -33,7 +33,7 @@ struct StoreAndForward {
///
/// TODO: REPLACE
var variant: StoreAndForward.OneOf_Variant? = nil
var variant: StoreAndForward.OneOf_Variant?
///
/// TODO: REPLACE
@ -345,7 +345,7 @@ extension StoreAndForward.RequestResponse: CaseIterable {
.clientStats,
.clientPing,
.clientPong,
.clientAbort,
.clientAbort
]
}
@ -362,7 +362,7 @@ extension StoreAndForward.Heartbeat: @unchecked Sendable {}
// MARK: - Code below here is support for the SwiftProtobuf runtime.
fileprivate let _protobuf_package = "meshtastic"
private let _protobuf_package = "meshtastic"
extension StoreAndForward: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
static let protoMessageName: String = _protobuf_package + ".StoreAndForward"
@ -371,7 +371,7 @@ extension StoreAndForward: SwiftProtobuf.Message, SwiftProtobuf._MessageImplemen
2: .same(proto: "stats"),
3: .same(proto: "history"),
4: .same(proto: "heartbeat"),
5: .same(proto: "empty"),
5: .same(proto: "empty")
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
@ -486,7 +486,7 @@ extension StoreAndForward.RequestResponse: SwiftProtobuf._ProtoNameProviding {
66: .same(proto: "CLIENT_STATS"),
67: .same(proto: "CLIENT_PING"),
68: .same(proto: "CLIENT_PONG"),
106: .same(proto: "CLIENT_ABORT"),
106: .same(proto: "CLIENT_ABORT")
]
}
@ -501,7 +501,7 @@ extension StoreAndForward.Statistics: SwiftProtobuf.Message, SwiftProtobuf._Mess
6: .standard(proto: "requests_history"),
7: .same(proto: "heartbeat"),
8: .standard(proto: "return_max"),
9: .standard(proto: "return_window"),
9: .standard(proto: "return_window")
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
@ -575,7 +575,7 @@ extension StoreAndForward.History: SwiftProtobuf.Message, SwiftProtobuf._Message
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .standard(proto: "history_messages"),
2: .same(proto: "window"),
3: .standard(proto: "last_request"),
3: .standard(proto: "last_request")
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
@ -618,7 +618,7 @@ extension StoreAndForward.Heartbeat: SwiftProtobuf.Message, SwiftProtobuf._Messa
static let protoMessageName: String = StoreAndForward.protoMessageName + ".Heartbeat"
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .same(proto: "period"),
2: .same(proto: "secondary"),
2: .same(proto: "secondary")
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {

View file

@ -15,7 +15,7 @@ import SwiftProtobuf
// incompatible with the version of SwiftProtobuf to which you are linking.
// Please ensure that you are building against the same version of the API
// that was used to generate this file.
fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck {
private struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck {
struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {}
typealias Version = _2
}
@ -146,7 +146,7 @@ extension TelemetrySensorType: CaseIterable {
.qmi8658,
.qmc5883L,
.sht31,
.pmsa003I,
.pmsa003I
]
}
@ -291,7 +291,7 @@ struct Telemetry {
/// seconds since 1970
var time: UInt32 = 0
var variant: Telemetry.OneOf_Variant? = nil
var variant: Telemetry.OneOf_Variant?
///
/// Key native device metrics such as battery level
@ -374,7 +374,7 @@ extension Telemetry.OneOf_Variant: @unchecked Sendable {}
// MARK: - Code below here is support for the SwiftProtobuf runtime.
fileprivate let _protobuf_package = "meshtastic"
private let _protobuf_package = "meshtastic"
extension TelemetrySensorType: SwiftProtobuf._ProtoNameProviding {
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
@ -391,7 +391,7 @@ extension TelemetrySensorType: SwiftProtobuf._ProtoNameProviding {
10: .same(proto: "QMI8658"),
11: .same(proto: "QMC5883L"),
12: .same(proto: "SHT31"),
13: .same(proto: "PMSA003I"),
13: .same(proto: "PMSA003I")
]
}
@ -401,7 +401,7 @@ extension DeviceMetrics: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementa
1: .standard(proto: "battery_level"),
2: .same(proto: "voltage"),
3: .standard(proto: "channel_utilization"),
4: .standard(proto: "air_util_tx"),
4: .standard(proto: "air_util_tx")
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
@ -453,7 +453,7 @@ extension EnvironmentMetrics: SwiftProtobuf.Message, SwiftProtobuf._MessageImple
3: .standard(proto: "barometric_pressure"),
4: .standard(proto: "gas_resistance"),
5: .same(proto: "voltage"),
6: .same(proto: "current"),
6: .same(proto: "current")
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
@ -521,7 +521,7 @@ extension AirQualityMetrics: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem
9: .standard(proto: "particles_10um"),
10: .standard(proto: "particles_25um"),
11: .standard(proto: "particles_50um"),
12: .standard(proto: "particles_100um"),
12: .standard(proto: "particles_100um")
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
@ -611,7 +611,7 @@ extension Telemetry: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementation
1: .same(proto: "time"),
2: .standard(proto: "device_metrics"),
3: .standard(proto: "environment_metrics"),
4: .standard(proto: "air_quality_metrics"),
4: .standard(proto: "air_quality_metrics")
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {

View file

@ -15,7 +15,7 @@ import SwiftProtobuf
// incompatible with the version of SwiftProtobuf to which you are linking.
// Please ensure that you are building against the same version of the API
// that was used to generate this file.
fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck {
private struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck {
struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {}
typealias Version = _2
}
@ -96,7 +96,7 @@ extension XModem.Control: CaseIterable {
.ack,
.nak,
.can,
.ctrlz,
.ctrlz
]
}
@ -109,7 +109,7 @@ extension XModem.Control: @unchecked Sendable {}
// MARK: - Code below here is support for the SwiftProtobuf runtime.
fileprivate let _protobuf_package = "meshtastic"
private let _protobuf_package = "meshtastic"
extension XModem: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
static let protoMessageName: String = _protobuf_package + ".XModem"
@ -117,7 +117,7 @@ extension XModem: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBas
1: .same(proto: "control"),
2: .same(proto: "seq"),
3: .same(proto: "crc16"),
4: .same(proto: "buffer"),
4: .same(proto: "buffer")
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
@ -170,6 +170,6 @@ extension XModem.Control: SwiftProtobuf._ProtoNameProviding {
6: .same(proto: "ACK"),
21: .same(proto: "NAK"),
24: .same(proto: "CAN"),
26: .same(proto: "CTRLZ"),
26: .same(proto: "CTRLZ")
]
}

View file

@ -15,19 +15,19 @@ import ActivityKit
#endif
struct Connect: View {
@Environment(\.managedObjectContext) var context
@EnvironmentObject var bleManager: BLEManager
@EnvironmentObject var userSettings: UserSettings
@State var node: NodeInfoEntity? = nil
@State var node: NodeInfoEntity?
@State var isUnsetRegion = false
@State var invalidFirmwareVersion = false
@State var liveActivityStarted = false
@State var presentingSwitchPreferredPeripheral = false
@State var selectedPeripherialId = ""
var body: some View {
NavigationStack {
VStack {
List {
@ -63,7 +63,7 @@ struct Connect: View {
.font(.caption).foregroundColor(Color.gray)
.padding([.top, .bottom])
.swipeActions {
Button(role: .destructive) {
if bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral.peripheral.state == CBPeripheralState.connected {
bleManager.disconnectPeripheral(reconnect: false)
@ -72,8 +72,8 @@ struct Connect: View {
Label("disconnect", systemImage: "antenna.radiowaves.left.and.right.slash")
}
}
.contextMenu{
.contextMenu {
if node != nil {
#if !targetEnvironment(macCatalyst)
if #available(iOS 16.2, *) {
@ -114,7 +114,7 @@ struct Connect: View {
}
}
} else {
if bleManager.isConnecting {
HStack {
Image(systemName: "antenna.radiowaves.left.and.right")
@ -129,7 +129,7 @@ struct Connect: View {
.foregroundColor(.orange)
} else {
VStack {
Text("Connection Attempt \(bleManager.timeoutTimerCount) of 10")
.font(.callout)
.foregroundColor(.orange)
@ -137,9 +137,9 @@ struct Connect: View {
}
}
.padding()
} else {
if bleManager.lastConnectionError.count > 0 {
Text(bleManager.lastConnectionError).font(.callout).foregroundColor(.red)
}
@ -157,7 +157,7 @@ struct Connect: View {
}
}
.textCase(nil)
if !self.bleManager.isConnected {
Section(header: Text("available.radios").font(.title)) {
ForEach(bleManager.peripherals.filter({ $0.peripheral.state == CBPeripheralState.disconnected }).sorted(by: { $0.name > $1.name })) { peripheral in
@ -183,7 +183,7 @@ struct Connect: View {
}
}
.confirmationDialog("Connecting to a new radio will clear all local app data on the phone.", isPresented: $presentingSwitchPreferredPeripheral, titleVisibility: .visible) {
Button("Connect to new radio?", role: .destructive) {
bleManager.stopScanning()
bleManager.connectedPeripheral = nil
@ -191,9 +191,9 @@ struct Connect: View {
if bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral.peripheral.state == CBPeripheralState.connected {
bleManager.disconnectPeripheral()
}
clearCoreDataDatabase(context: context)
let radio = bleManager.peripherals.first(where: { $0.peripheral.identifier.uuidString == selectedPeripherialId} )
let radio = bleManager.peripherals.first(where: { $0.peripheral.identifier.uuidString == selectedPeripherialId})
bleManager.connectTo(peripheral: radio!.peripheral)
presentingSwitchPreferredPeripheral = false
selectedPeripherialId = ""
@ -201,14 +201,14 @@ struct Connect: View {
}
.textCase(nil)
}
} else {
Text("bluetooth.off")
.foregroundColor(.red)
.font(.title)
}
}
HStack(alignment: .center) {
Spacer()
#if targetEnvironment(macCatalyst)
@ -236,24 +236,25 @@ struct Connect: View {
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????")
})
}
.sheet(isPresented: $invalidFirmwareVersion, onDismiss: didDismissSheet) {
.sheet(isPresented: $invalidFirmwareVersion, onDismiss: didDismissSheet) {
InvalidVersion(minimumVersion: self.bleManager.minimumVersion, version: self.bleManager.connectedVersion)
.presentationDetents([.large])
.presentationDragIndicator(.automatic)
}
.onChange(of: (self.bleManager.invalidVersion)) { cv in
.onChange(of: (self.bleManager.invalidVersion)) { _ in
invalidFirmwareVersion = self.bleManager.invalidVersion
}
.onChange(of: (self.bleManager.isSubscribed)) { sub in
if userSettings.preferredPeripheralId.count > 0 && sub {
let fetchNodeInfoRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "NodeInfoEntity")
fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(bleManager.connectedPeripheral?.num ?? -1))
do {
let fetchedNode = try context.fetch(fetchNodeInfoRequest) as! [NodeInfoEntity]
guard let fetchedNode = try context.fetch(fetchNodeInfoRequest) as? [NodeInfoEntity] else {
return
}
// Found a node, check it for a region
if !fetchedNode.isEmpty {
node = fetchedNode[0]
@ -264,14 +265,14 @@ struct Connect: View {
}
}
} catch {
}
}
}
.onAppear(perform: {
self.bleManager.context = context
self.bleManager.userSettings = userSettings
// Ask for notification permission
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { success, error in
if success {
@ -287,17 +288,17 @@ struct Connect: View {
if #available(iOS 16.2, *) {
liveActivityStarted = true
let timerSeconds = 60
let mostRecent = node?.telemetries?.lastObject as! TelemetryEntity
let activityAttributes = MeshActivityAttributes(nodeNum: Int(node?.num ?? 0), name: node?.user?.longName ?? "unknown")
let future = Date(timeIntervalSinceNow: Double(timerSeconds))
let initialContentState = MeshActivityAttributes.ContentState(timerRange: Date.now...future, connected: true, channelUtilization: mostRecent.channelUtilization, airtime: mostRecent.airUtilTx, batteryLevel: UInt32(mostRecent.batteryLevel))
let activityContent = ActivityContent(state: initialContentState, staleDate: Calendar.current.date(byAdding: .minute, value: 2, to: Date())!)
do {
let myActivity = try Activity<MeshActivityAttributes>.request(attributes: activityAttributes, content: activityContent,
pushType: nil)
@ -307,7 +308,7 @@ struct Connect: View {
}
}
}
func endActivity() {
liveActivityStarted = false
Task {
@ -322,7 +323,7 @@ struct Connect: View {
}
}
#endif
#if os(iOS)
func postNotification() {
let timerSeconds = 60
@ -344,7 +345,7 @@ struct Connect: View {
}
}
#endif
func didDismissSheet() {
bleManager.disconnectPeripheral(reconnect: false)
}

View file

@ -7,20 +7,20 @@
import SwiftUI
struct InvalidVersion: View {
@Environment(\.dismiss) private var dismiss
@State var minimumVersion = ""
@State var version = ""
var body: some View {
VStack {
Text("update.firmware")
.font(.largeTitle)
.foregroundColor(.orange)
Divider()
VStack {
Text("The Meshtastic Apple apps support firmware version \(minimumVersion) and above.")
@ -36,7 +36,7 @@ struct InvalidVersion: View {
.padding()
Divider()
.padding(.top)
VStack{
VStack {
Text("🦕 End of life Version 🦖 ☄️")
.font(.title3)
.foregroundColor(.orange)
@ -46,20 +46,20 @@ struct InvalidVersion: View {
.padding([.leading, .trailing, .bottom])
Link("Version 1.2 End of life (EOL) Info", destination: URL(string: "https://meshtastic.org/docs/1.2-End-of-life/")!)
.font(.callout)
#if targetEnvironment(macCatalyst)
Button {
dismiss()
} label: {
Label("close", systemImage: "xmark")
}
.buttonStyle(.bordered)
.buttonBorderShape(.capsule)
.controlSize(.large)
.padding()
#endif
}.padding()
}
}

View file

@ -5,11 +5,11 @@
import SwiftUI
struct ContentView: View {
@EnvironmentObject var userSettings: UserSettings
@State private var selection: Tab = .ble
enum Tab {
case contacts
case messages
@ -18,11 +18,11 @@ struct ContentView: View {
case nodes
case settings
}
var body: some View {
TabView(selection: $selection) {
Contacts()
.tabItem {
Label("messages", systemImage: "message")

View file

@ -9,9 +9,7 @@ import SwiftUI
import Charts
struct BatteryGauge: View {
@State var batteryLevel = 0.0
private let minValue = 1.0
private let maxValue = 100.00
@ -24,7 +22,6 @@ struct BatteryGauge: View {
.foregroundColor(.accentColor)
.symbolRenderingMode(.hierarchical)
} else {
let gradient = Gradient(colors: [.red, .orange, .green])
Gauge(value: batteryLevel, in: minValue...maxValue) {
if batteryLevel > 1.0 && batteryLevel < 10 {
@ -52,7 +49,6 @@ struct BatteryGauge: View {
struct BatteryGauge_Previews: PreviewProvider {
static var previews: some View {
VStack {
BatteryGauge(batteryLevel: 0.0)
BatteryGauge(batteryLevel: 9.0)

View file

@ -13,9 +13,9 @@ struct CircleText: View {
var brightness: Double? = 0
var body: some View {
let font = Font.system(size: fontSize!)
ZStack {
Circle()
.fill(color)

View file

@ -14,16 +14,16 @@ import SwiftUI
//
struct DateTimeText: View {
var dateTime: Date?
let sixMonthsAgo = Calendar.current.date(byAdding: .month, value: -6, to: Date())
var body: some View {
if (dateTime != nil && dateTime! >= sixMonthsAgo!){
if dateTime != nil && dateTime! >= sixMonthsAgo! {
Text("\(dateTime!, style: .date) \(dateTime!, style: .time)")
} else {
Text("unknown.age")
}
}

View file

@ -10,18 +10,18 @@ import CoreLocation
import MapKit
struct DistanceText: View {
var meters: CLLocationDistance
var body: some View {
let distanceFormatter = MKDistanceFormatter()
Text("distance")+Text(": \(distanceFormatter.string(fromDistance: Double(meters)))")
}
}
struct DistanceText_Previews: PreviewProvider {
static var previews: some View {
VStack {
DistanceText(meters: 100)
DistanceText(meters: 1000)

View file

@ -9,7 +9,7 @@ struct LastHeardText: View {
var lastHeard: Date?
let sixMonthsAgo = Calendar.current.date(byAdding: .month, value: -6, to: Date())
var body: some View {
if (lastHeard != nil && lastHeard! >= sixMonthsAgo!){
if lastHeard != nil && lastHeard! >= sixMonthsAgo! {
Text("heard")+Text(": \(lastHeard!, style: .relative) ")+Text("ago")
} else {
Text("unknown.age")

View file

@ -7,11 +7,11 @@
import SwiftUI
struct MeshtasticLogo: View {
@Environment(\.colorScheme) var colorScheme
var body: some View {
#if targetEnvironment(macCatalyst)
VStack {
Image("logo-white")

View file

@ -7,16 +7,16 @@
import SwiftUI
struct MessageTemplate: View {
var user: UserEntity
var message: MessageEntity
var messageReply: MessageEntity?
var body: some View {
// Display the message being replied to and the arrow
if message.replyID > 0 {
HStack {
Text(messageReply?.messagePayload ?? "EMPTY MESSAGE").foregroundColor(.blue).font(.caption2)
@ -31,8 +31,8 @@ struct MessageTemplate: View {
.padding(.trailing)
}
}
// Message
}
}

View file

@ -3,13 +3,13 @@ import SwiftUI
struct NodeAnnotation: View {
let time: Date
let sixMonthsAgo = Calendar.current.date(byAdding: .month, value: -6, to: Date())
var body: some View {
if (time >= sixMonthsAgo!) {
if time >= sixMonthsAgo! {
VStack(spacing: 0) {
Text(time, style: .offset)
.font(.caption2).foregroundColor(.accentColor)
@ -17,9 +17,9 @@ struct NodeAnnotation: View {
.background(Color(.white))
.cornerRadius(10)
}
} else {
VStack(spacing: 0) {
Text("unknown.age")
.font(.caption2).foregroundColor(.accentColor)

View file

@ -33,7 +33,7 @@ import SwiftUI
struct SignalStrengthIndicator: View {
let signalStrength: SignalStrength
var body: some View {
HStack {
ForEach(0..<3) { bar in
@ -44,7 +44,7 @@ struct SignalStrengthIndicator: View {
}
}
}
private func getColor() -> Color {
switch signalStrength {
case .weak:
@ -71,7 +71,7 @@ extension Shape {
}
}
enum SignalStrength : Int {
enum SignalStrength: Int {
case weak = 0
case normal = 1
case strong = 2

View file

@ -10,14 +10,13 @@ struct AirQualityIndexCompact: View {
var aqi: Int
var body: some View {
HStack (spacing: 0.5) {
HStack(spacing: 0.5) {
Text("AQI \(aqi)")
.foregroundColor(.gray)
.padding(.trailing, 0)
.font(.caption)
if aqi > 0 && aqi < 51 {
// Good
Circle()
@ -38,7 +37,7 @@ struct AirQualityIndexCompact: View {
Circle()
.fill(.orange)
.frame(width: 10, height: 10)
} else if aqi > 300 && aqi < 401 {
// Very Poor
Circle()
@ -55,7 +54,7 @@ struct AirQualityIndexCompact: View {
}
struct AQICircleDisplay_Previews: PreviewProvider {
static var previews: some View {
VStack {
AirQualityIndexCompact(aqi: 5)
AirQualityIndexCompact(aqi: 51)

View file

@ -19,7 +19,7 @@ struct CurrentConditionsCompact: View {
}
struct CurrentConditionsCompact_Previews: PreviewProvider {
static var previews: some View {
VStack {
CurrentConditionsCompact(temp: 22, condition: WeatherConditions.clear)
CurrentConditionsCompact(temp: 17, condition: WeatherConditions.cloudy)

View file

@ -12,15 +12,15 @@ import WeatherKit
struct NodeWeatherForecastView: View {
var location: CLLocation
@State private var forecast: NodeWeatherForecast = placeholderForecast
var body: some View {
VStack {
chart
.frame(width: 400)
}
//.frame(width: 350, height: 200)
// .frame(width: 350, height: 200)
.padding(10)
.background()
.task {
@ -38,12 +38,12 @@ struct NodeWeatherForecastView: View {
}
}
}
var chart: some View {
Chart {
areaMarks(seriesKey: "Temperature", value: 0)
.foregroundStyle(.linearGradient(colors: [.teal, .yellow], startPoint: .bottom, endPoint: .top))
ForEach(forecast.nightTimeRanges, id: \.lowerBound) { range in
RectangleMark(
xStart: .value("Hour", range.lowerBound),
@ -53,7 +53,7 @@ struct NodeWeatherForecastView: View {
.mask {
areaMarks(seriesKey: "Mask", value: range.lowerBound.timeIntervalSince1970)
}
if range.lowerBound != forecast.entries.first!.date {
let date = range.lowerBound
RectangleMark(
@ -71,7 +71,7 @@ struct NodeWeatherForecastView: View {
.foregroundStyle(.white, .indigo)
}
}
if range.upperBound != forecast.entries.last!.date {
let date = range.upperBound
RectangleMark(
@ -107,7 +107,7 @@ struct NodeWeatherForecastView: View {
}
}
}
@ChartContentBuilder
func areaMarks(seriesKey: String, value: Double) -> some ChartContent {
ForEach(forecast.entries) { entry in
@ -120,14 +120,14 @@ struct NodeWeatherForecastView: View {
.interpolationMethod(.catmullRom)
}
}
static var placeholderForecast: NodeWeatherForecast {
func entry(hourOffset: Int, degrees: Double, isDaylight: Bool) -> NodeWeatherForecast.WeatherEntry {
let startDate = Calendar.current.date(from: DateComponents(year: 2022, month: 5, day: 6, hour: 9))!
let date = Calendar.current.date(byAdding: DateComponents(hour: hourOffset), to: startDate)!
return NodeWeatherForecast.WeatherEntry(date: date, degrees: degrees, isDaylight: isDaylight)
}
return NodeWeatherForecast(entries: [
entry(hourOffset: 0, degrees: 63, isDaylight: true),
entry(hourOffset: 1, degrees: 68, isDaylight: true),
@ -165,17 +165,17 @@ struct NodeWeatherForecast {
var degrees: Double
var isDaylight: Bool
}
var entries: [WeatherEntry]
var low: Double {
return entries.map(\.degrees).min()! - 2
}
var hottestEntry: WeatherEntry {
return entries.sorted { $0.degrees > $1.degrees }.first!
}
var nightTimeRanges: [Range<Date>] {
var currentLowerBound: Date?
var results: [Range<Date>] = []
@ -192,7 +192,7 @@ struct NodeWeatherForecast {
}
return results
}
var binRange: ClosedRange<Date> {
let startDate: Date = entries.map(\.date).first(where: {
Calendar.current.component(.hour, from: $0).isMultiple(of: 3)
@ -202,7 +202,7 @@ struct NodeWeatherForecast {
})!
return startDate ... endDate
}
func temperature(at date: Date) -> Double {
entries.first(where: { $0.date == date })!.degrees
}

View file

@ -25,10 +25,10 @@ extension MKMapRect {
left = min(left, coordinate.longitude)
right = max(right, coordinate.longitude)
}
let topLeft = MKMapPoint(CLLocationCoordinate2D(latitude:top, longitude:left))
let bottomRight = MKMapPoint(CLLocationCoordinate2D(latitude:bottom, longitude:right))
self = MKMapRect(x:topLeft.x, y:topLeft.y,
width:bottomRight.x - topLeft.x, height:bottomRight.y - topLeft.y)
let topLeft = MKMapPoint(CLLocationCoordinate2D(latitude: top, longitude: left))
let bottomRight = MKMapPoint(CLLocationCoordinate2D(latitude: bottom, longitude: right))
self = MKMapRect(x: topLeft.x, y: topLeft.y,
width: bottomRight.x - topLeft.x, height: bottomRight.y - topLeft.y)
}
}
}
@ -44,36 +44,34 @@ class LocalMBTileOverlay: MKTileOverlay {
var mb: Connection!
private var _boundingMapRect: MKMapRect!
override var boundingMapRect: MKMapRect {
get {
return _boundingMapRect
}
return _boundingMapRect
}
init?(mbTilePath path: String) {
super.init(urlTemplate: nil)
self.path = path
do {
self.mb = try Connection(self.path, readonly: true)
let metadata = Table("metadata")
let name = Expression<String>("name")
let value = Expression<String>("value")
//make sure it's raster
// make sure it's raster
let formatQuery = try mb.pluck(metadata.select(value).filter(name == "format"))
if formatQuery?[value] == nil || (formatQuery![value] != "jpg" && formatQuery![value] != "png") {
throw MapTileError.invalidFormat
}
let minZQuery = try mb.pluck(metadata.select(value).filter(name == "minzoom"))
self.minimumZ = Int(minZQuery![value])!
let maxZQuery = try mb.pluck(metadata.select(value).filter(name == "maxzoom"))
self.maximumZ = Int(maxZQuery![value])!
self.isGeometryFlipped = true
let boundingBoxString = try mb.pluck(metadata.select(value).filter(name == "bounds"))
let boundCoords = boundingBoxString![value].split(separator: ",")
let coords = [
@ -83,15 +81,15 @@ class LocalMBTileOverlay: MKTileOverlay {
longitude: Double(boundCoords[2]) ?? 0)
]
self._boundingMapRect = MKMapRect(coordinates: coords)
} catch {
print("💥 Map tile error: \(error)")
return nil
}
}
override func loadTile(at path: MKTileOverlayPath, result: @escaping (Data?, Error?) -> Void) {
let tileX = Int64(path.x)
let tileY = Int64(path.y)
let tileZ = Int64(path.z)
@ -99,9 +97,9 @@ class LocalMBTileOverlay: MKTileOverlay {
let zoomLevel = Expression<Int64>("zoom_level")
let tileColumn = Expression<Int64>("tile_column")
let tileRow = Expression<Int64>("tile_row")
if let dataQuery = try? self.mb.pluck(Table("tiles").select(tileData).filter(zoomLevel == tileZ).filter(tileColumn == tileX).filter(tileRow == tileY)) {
let data = Data(bytes: dataQuery[tileData].bytes, count: dataQuery[tileData].bytes.count)//dataQuery![tileData].bytes
let data = Data(bytes: dataQuery[tileData].bytes, count: dataQuery[tileData].bytes.count)// dataQuery![tileData].bytes
result(data, nil)
} else {
print("💥 No tile here: x:\(tileX) y:\(tileY) z:\(tileZ)")

View file

@ -8,7 +8,7 @@
import MapKit
extension MKMapView {
func fitAllAnnotations(with padding: UIEdgeInsets = UIEdgeInsets(top: 100, left: 100, bottom: 100, right: 100)) {
var zoomRect: MKMapRect = .null
annotations.forEach({
@ -16,10 +16,10 @@ extension MKMapView {
let pointRect = MKMapRect(x: annotationPoint.x, y: annotationPoint.y, width: 0.01, height: 0.01)
zoomRect = zoomRect.union(pointRect)
})
setVisibleMapRect(zoomRect, edgePadding: padding, animated: true)
}
func fit(annotations: [MKAnnotation], andShow show: Bool, with padding: UIEdgeInsets = UIEdgeInsets(top: 100, left: 100, bottom: 100, right: 100)) {
var zoomRect: MKMapRect = .null
annotations.forEach({
@ -27,11 +27,11 @@ extension MKMapView {
let rect = MKMapRect(x: aPoint.x, y: aPoint.y, width: 0.1, height: 0.1)
zoomRect = zoomRect.isNull ? rect : zoomRect.union(rect)
})
if show {
addAnnotations(annotations)
}
setVisibleMapRect(zoomRect, edgePadding: padding, animated: true)
}
}

View file

@ -1,469 +0,0 @@
////
//// MapView.swift
//// MapViewTest
////
//// Created by Cem Yilmaz on 05.07.21.
////
//import SwiftUI
//import MapKit
//import CoreData
//
//#if canImport(MapKit) && canImport(UIKit)
//public struct MapView: UIViewRepresentable {
//
// @Environment(\.managedObjectContext) var context
//
// //var context: NSManagedObjectContext?
//
// //@Binding private var region: MKCoordinateRegion
//
// //make this view dependent on the UserDefault that is updated when importing a new map file
// @AppStorage("lastUpdatedLocalMapFile") private var lastUpdatedLocalMapFile = 0
// @State private var loadedLastUpdatedLocalMapFile = 0
//
// private var customMapOverlay: CustomMapOverlay?
// @State private var presentCustomMapOverlayHash: CustomMapOverlay?
//
// private var mapType: MKMapType
//
// private var showZoomScale: Bool
// private var zoomEnabled: Bool
// private var zoomRange: (minHeight: CLLocationDistance?, maxHeight: CLLocationDistance?)
//
// private var scrollEnabled: Bool
// private var scrollBoundaries: MKCoordinateRegion?
//
// private var rotationEnabled: Bool
// private var showCompassWhenRotated: Bool
//
// private var showUserLocation: Bool
// private var userTrackingMode: MKUserTrackingMode
// @Binding private var userLocation: CLLocationCoordinate2D?
//
// private var overlays: [Overlay]
//
// @FetchRequest(sortDescriptors: [NSSortDescriptor(key: "time", ascending: false)], animation: .default)
// private var positions: FetchedResults<PositionEntity>
//
// public init(
// customMapOverlay: CustomMapOverlay? = nil,
// mapType: String = "hybrid",
// zoomEnabled: Bool = true,
// showZoomScale: Bool = false,
// zoomRange: (minHeight: CLLocationDistance?, maxHeight: CLLocationDistance?) = (nil, nil),
// scrollEnabled: Bool = true,
// scrollBoundaries: MKCoordinateRegion? = nil,
// rotationEnabled: Bool = true,
// showCompassWhenRotated: Bool = true,
// showUserLocation: Bool = true,
// userTrackingMode: MKUserTrackingMode = MKUserTrackingMode.none,
// userLocation: Binding<CLLocationCoordinate2D?> = .constant(nil),
// overlays: [Overlay] = []
// ) {
// self.customMapOverlay = customMapOverlay
//
// switch mapType {
// case "satellite":
// self.mapType = .satellite
// break
// case "standard":
// self.mapType = .standard
// break
// case "hybrid":
// self.mapType = .hybrid
// break
// default:
// self.mapType = .hybrid
// }
//
// self.showZoomScale = showZoomScale
// self.zoomEnabled = zoomEnabled
// self.zoomRange = zoomRange
//
// self.scrollEnabled = scrollEnabled
// self.scrollBoundaries = scrollBoundaries
//
// self.rotationEnabled = rotationEnabled
// self.showCompassWhenRotated = showCompassWhenRotated
//
// self.showUserLocation = showUserLocation
// self.userTrackingMode = userTrackingMode
// self._userLocation = userLocation
//
// self.overlays = overlays
//
// }
//
// public func makeUIView(context: Context) -> MKMapView {
// let mapView = MKMapView()
// mapView.delegate = context.coordinator
// mapView.register(PositionAnnotationView.self, forAnnotationViewWithReuseIdentifier: NSStringFromClass(PositionAnnotationView.self))
//
// return mapView
// }
//
//
// public func updateUIView(_ mapView: MKMapView, context: Context) {
//
// if self.customMapOverlay != self.presentCustomMapOverlayHash || self.loadedLastUpdatedLocalMapFile != self.lastUpdatedLocalMapFile {
// mapView.removeOverlays(mapView.overlays)
// if let customMapOverlay = self.customMapOverlay {
//
// let fileManager = FileManager.default
// let documentsDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!
// let tilePath = documentsDirectory.appendingPathComponent("offline_map.mbtiles", isDirectory: false).path
// if fileManager.fileExists(atPath: tilePath) {
// //if let tilePath = Bundle.main.path(forResource: "offline_map", ofType: "mbtiles") {
//
// print("Loading local map file")
//
// if let overlay = LocalMBTileOverlay(mbTilePath: tilePath) {
//
// overlay.canReplaceMapContent = false//customMapOverlay.canReplaceMapContent
//
// mapView.addOverlay(overlay)
// }
// } else {
// print("Couldn't find a local map file to load")
// }
// }
// DispatchQueue.main.async {
// self.presentCustomMapOverlayHash = self.customMapOverlay
// self.loadedLastUpdatedLocalMapFile = self.lastUpdatedLocalMapFile
// }
// }
//
// if mapView.mapType != self.mapType {
// mapView.mapType = self.mapType
// }
//
// mapView.showsScale = self.zoomEnabled ? self.showZoomScale : false
//
// if mapView.isZoomEnabled != self.zoomEnabled {
// mapView.isZoomEnabled = self.zoomEnabled
// }
//
// if mapView.cameraZoomRange.minCenterCoordinateDistance != self.zoomRange.minHeight ?? 0 ||
// mapView.cameraZoomRange.maxCenterCoordinateDistance != self.zoomRange.maxHeight ?? .infinity {
// mapView.cameraZoomRange = MKMapView.CameraZoomRange(
// minCenterCoordinateDistance: self.zoomRange.minHeight ?? 0,
// maxCenterCoordinateDistance: self.zoomRange.maxHeight ?? .infinity
// )
// }
//
// mapView.isScrollEnabled = self.userTrackingMode == MKUserTrackingMode.none ? self.scrollEnabled : false
//
// if let scrollBoundary = self.scrollBoundaries, (mapView.cameraBoundary?.region.center.latitude != scrollBoundary.center.latitude || mapView.cameraBoundary?.region.center.longitude != scrollBoundary.center.longitude || mapView.camera Boundary?.region.span.latitudeDelta != scrollBoundary.span.latitudeDelta || mapView.cameraBoundary?.region.span.longitudeDelta != scrollBoundary.span.longitudeDelta) {
// mapView.cameraBoundary = MKMapView.CameraBoundary(coordinateRegion: scrollBoundary)
// } else if self.scrollBoundaries == nil && mapView.cameraBoundary != nil {
// mapView.cameraBoundary = nil
// }
//
// mapView.isRotateEnabled = self.userTrackingMode != .followWithHeading ? self.rotationEnabled : false
// mapView.showsCompass = self.userTrackingMode != .followWithHeading ? self.showCompassWhenRotated : false
//
// if mapView.showsUserLocation != self.showUserLocation {
// mapView.showsUserLocation = self.showUserLocation
// }
//
// if mapView.userTrackingMode != self.userTrackingMode {
// mapView.userTrackingMode = self.userTrackingMode
// }
//
// // clear any existing annotations
// var shouldMoveRegion = false
// if !mapView.annotations.isEmpty {
// mapView.removeAnnotations(mapView.annotations)
// } else {
// shouldMoveRegion = true
// }
//
// var displayedNodes: [Int64] = []
// for position in self.positions {
// if position.nodePosition == nil || displayedNodes.contains(position.nodePosition!.num) || position.coordinate == nil {
// continue
// }
//
// let annotation = PositionAnnotation()
// annotation.coordinate = position.nodeCoordinate!
// annotation.title = position.nodePosition!.user?.longName ?? NSLocalizedString("unknown", comment: "Unknown")
// annotation.shortName = position.nodePosition!.user?.shortName?.uppercased() ?? "???"
//
// mapView.addAnnotation(annotation)
//
// displayedNodes.append(position.nodePosition!.num)
// }
//
// if shouldMoveRegion {
// self.moveToMeshRegion(mapView)
// }
//
//
// }
//
// func moveToMeshRegion(_ mapView: MKMapView) {
// //go through the annotations and create a bounding box that encloses them
//
// var minLat: CLLocationDegrees = 90.0
// var maxLat: CLLocationDegrees = -90.0
// var minLon: CLLocationDegrees = 180.0
// var maxLon: CLLocationDegrees = -180.0
//
// for annotation in mapView.annotations {
// if annotation.isKind(of: PositionAnnotation.self) {
// minLat = min(minLat, annotation.coordinate.latitude)
// maxLat = max(maxLat, annotation.coordinate.latitude)
// minLon = min(minLon, annotation.coordinate.longitude)
// maxLon = max(maxLon, annotation.coordinate.longitude)
// }
// }
//
// //check if the mesh region looks sensible before we move to it. Otherwise we won't move the map (leave it at the current location)
// if maxLat < minLat || (maxLat-minLat) > 5 || maxLon < minLon || (maxLon-minLon) > 5 {
// return
// } else if minLat == maxLat && minLon == maxLon {
// //then we are focussed on a single point (probably because there is only one node with a position)
// //widen that out a little (don't zoom way in to that point)
//
// //0.001 degrees latitude is about 100m
// //the mapView.regionThatFits call below will expand this out to a rectangle
// minLat = minLat - 0.001
// maxLat = maxLat + 0.001
// }
//
// let centerCoord = CLLocationCoordinate2D(latitude: (minLat+maxLat)/2, longitude: (minLon+maxLon)/2)
//
// let span = MKCoordinateSpan(latitudeDelta: (maxLat-minLat)*1.5, longitudeDelta: (maxLon-minLon)*1.5)
//
// let region = mapView.regionThatFits(MKCoordinateRegion(center: centerCoord, span: span))
//
// mapView.setRegion(region, animated: true)
// }
//
// public func makeCoordinator() -> Coordinator {
// Coordinator(parent: self)
// }
//
// public class Coordinator: NSObject, MKMapViewDelegate {
//
// private var parent: MapView
// public var overlays: [Overlay] = []
//
// init(parent: MapView) {
// self.parent = parent
// }
//
// public func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
//
// guard !annotation.isKind(of: MKUserLocation.self) else {
// // Make a fast exit if the annotation is the `MKUserLocation`, as it's not an annotation view we wish to customize.
// return nil
// }
//
// if let annotation = annotation as? PositionAnnotation {
//
// let annotationView = PositionAnnotationView(annotation: annotation, reuseIdentifier: "PositionAnnotation")
// annotationView.name = annotation.shortName ?? "????"
// annotationView.canShowCallout = true
//
// return annotationView
// }
//
// return nil
// }
//
// public func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
//
// if let index = self.overlays.firstIndex(where: { overlay_ in overlay_.shape.hash == overlay.hash }) {
//
// let unwrappedOverlay = self.overlays[index]
//
// if let circleOverlay = unwrappedOverlay.shape as? MKCircle {
//
// let renderer = MKCircleRenderer(circle: circleOverlay)
// renderer.fillColor = unwrappedOverlay.fillColor
// renderer.strokeColor = unwrappedOverlay.strokeColor
// renderer.lineWidth = unwrappedOverlay.lineWidth
// return renderer
//
// } else if let polygonOverlay = unwrappedOverlay.shape as? MKPolygon {
//
// let renderer = MKPolygonRenderer(polygon: polygonOverlay)
// renderer.fillColor = unwrappedOverlay.fillColor
// renderer.strokeColor = unwrappedOverlay.strokeColor
// renderer.lineWidth = unwrappedOverlay.lineWidth
// return renderer
//
// } else if let multiPolygonOverlay = unwrappedOverlay.shape as? MKMultiPolygon {
//
// let renderer = MKMultiPolygonRenderer(multiPolygon: multiPolygonOverlay)
// renderer.fillColor = unwrappedOverlay.fillColor
// renderer.strokeColor = unwrappedOverlay.strokeColor
// renderer.lineWidth = unwrappedOverlay.lineWidth
// return renderer
//
// } else if let polyLineOverlay = unwrappedOverlay.shape as? MKPolyline {
//
// let renderer = MKPolylineRenderer(polyline: polyLineOverlay)
// renderer.fillColor = unwrappedOverlay.fillColor
// renderer.strokeColor = unwrappedOverlay.strokeColor
// renderer.lineWidth = unwrappedOverlay.lineWidth
// return renderer
//
// } else if let multiPolylineOverlay = unwrappedOverlay.shape as? MKMultiPolyline {
//
// let renderer = MKMultiPolylineRenderer(multiPolyline: multiPolylineOverlay)
// renderer.fillColor = unwrappedOverlay.fillColor
// renderer.strokeColor = unwrappedOverlay.strokeColor
// renderer.lineWidth = unwrappedOverlay.lineWidth
// return renderer
//
// } else {
//
// return MKOverlayRenderer()
//
// }
//
// } else if let tileOverlay = overlay as? MKTileOverlay {
//
// return MKTileOverlayRenderer(tileOverlay: tileOverlay)
//
// } else {
// return MKOverlayRenderer()
// }
// }
// }
//
// /// is supposed to be located in the folder with the map name
// public struct DefaultTile: Hashable {
// let tileName: String
// let tileType: String
//
// public init(tileName: String, tileType: String) {
// self.tileName = tileName
// self.tileType = tileType
// }
// }
//
// public struct CustomMapOverlay: Equatable, Hashable {
// let mapName: String
// let tileType: String
// var canReplaceMapContent: Bool
// var minimumZoomLevel: Int?
// var maximumZoomLevel: Int?
// let defaultTile: DefaultTile?
//
// public init(
// mapName: String,
// tileType: String,
// canReplaceMapContent: Bool = true, // false for transparent tiles
// minimumZoomLevel: Int? = nil,
// maximumZoomLevel: Int? = nil,
// defaultTile: DefaultTile? = nil
// ) {
//
// self.mapName = mapName
// self.tileType = tileType
// self.canReplaceMapContent = canReplaceMapContent
// self.minimumZoomLevel = minimumZoomLevel
// self.maximumZoomLevel = maximumZoomLevel
// self.defaultTile = defaultTile
// }
//
// public init?(
// mapName: String?,
// tileType: String,
// canReplaceMapContent: Bool = true, // false for transparent tiles
// minimumZoomLevel: Int? = nil,
// maximumZoomLevel: Int? = nil,
// defaultTile: DefaultTile? = nil
// ) {
// if (mapName == nil || mapName! == "") {
// return nil
// }
// self.mapName = mapName!
// self.tileType = tileType
// self.canReplaceMapContent = canReplaceMapContent
// self.minimumZoomLevel = minimumZoomLevel
// self.maximumZoomLevel = maximumZoomLevel
// self.defaultTile = defaultTile
// }
// }
//
// public class CustomMapOverlaySource: MKTileOverlay {
//
// // requires folder: tiles/{mapName}/z/y/y,{tileType}
// private var parent: MapView
// private let mapName: String
// private let tileType: String
// private let defaultTile: DefaultTile?
//
// public init(
// parent: MapView,
// mapName: String,
// tileType: String,
// defaultTile: DefaultTile?
// ) {
// self.parent = parent
// self.mapName = mapName
// self.tileType = tileType
// self.defaultTile = defaultTile
// super.init(urlTemplate: "")
// }
//
// public override func url(forTilePath path: MKTileOverlayPath) -> URL {
// if let tileUrl = Bundle.main.url(
// forResource: "\(path.y)",
// withExtension: self.tileType,
// subdirectory: "tiles/\(self.mapName)/\(path.z)/\(path.x)",
// localization: nil
// ) {
// return tileUrl
// } else if let defaultTile = self.defaultTile, let defaultTileUrl = Bundle.main.url(
// forResource: defaultTile.tileName,
// withExtension: defaultTile.tileType,
// subdirectory: "tiles/\(self.mapName)",
// localization: nil
// ) {
// return defaultTileUrl
// } else {
// let urlstring = self.mapName+"\(path.z)/\(path.x)/\(path.y).png"
// return URL(string: urlstring)!
// // Bundle.main.url(forResource: "surrounding", withExtension: "png", subdirectory: "tiles")!
// }
//
// }
//
// }
//
// public struct Overlay {
//
// public static func == (lhs: MapView.Overlay, rhs: MapView.Overlay) -> Bool {
// // maybe to use in the future for comparison of full array
// lhs.shape.coordinate.latitude == rhs.shape.coordinate.latitude &&
// lhs.shape.coordinate.longitude == rhs.shape.coordinate.longitude &&
// lhs.fillColor == rhs.fillColor
// }
//
// var shape: MKOverlay
// var fillColor: UIColor?
// var strokeColor: UIColor?
// var lineWidth: CGFloat
//
// public init(
// shape: MKOverlay,
// fillColor: UIColor? = nil,
// strokeColor: UIColor? = nil,
// lineWidth: CGFloat = 0
// ) {
// self.shape = shape
// self.fillColor = fillColor
// self.strokeColor = strokeColor
// self.lineWidth = lineWidth
// }
// }
//
//}
//
//// MARK: End of implementation
//#endif

View file

@ -12,7 +12,7 @@ func degreesToRadians(_ number: Double) -> Double {
}
struct MapViewSwiftUI: UIViewRepresentable {
var onLongPress: (_ waypointCoordinate: CLLocationCoordinate2D) -> Void
var onWaypointEdit: (_ waypointId: Int ) -> Void
let mapView = MKMapView()
@ -21,28 +21,30 @@ struct MapViewSwiftUI: UIViewRepresentable {
let mapViewType: MKMapType
let userTrackingMode: MKUserTrackingMode
let centeringMode: CenteringMode
let centerOnPositionsOnly: Bool
@AppStorage("meshMapRecentering") private var recenter: Bool = false
// Offline Maps
//make this view dependent on the UserDefault that is updated when importing a new map file
// make this view dependent on the UserDefault that is updated when importing a new map file
@AppStorage("lastUpdatedLocalMapFile") private var lastUpdatedLocalMapFile = 0
@State private var loadedLastUpdatedLocalMapFile = 0
var customMapOverlay: CustomMapOverlay?
@State private var presentCustomMapOverlayHash: CustomMapOverlay?
var overlays: [Overlay] = []
let dynamicRegion: Bool = true
func makeUIView(context: Context) -> MKMapView {
// Map View Parameters
mapView.mapType = mapViewType
mapView.addAnnotations(waypoints)
mapView.setUserTrackingMode(userTrackingMode, animated: true)
// Do the initial map centering
let span = MKCoordinateSpan(latitudeDelta: 0.003, longitudeDelta: 0.003)
let center = LocationHelper.currentLocation
let region = MKCoordinateRegion(center: center, span: span)
mapView.setRegion(region, animated: true)
// Set user (phone gps) tracking options
mapView.setUserTrackingMode(userTrackingMode, animated: true)
if userTrackingMode != MKUserTrackingMode.none {
mapView.showsUserLocation = true
} else {
@ -55,13 +57,13 @@ struct MapViewSwiftUI: UIViewRepresentable {
mapView.fitAllAnnotations()
}
case .allPositions:
if userTrackingMode != MKUserTrackingMode.none {
if userTrackingMode == MKUserTrackingMode.none {
mapView.fit(annotations: positions, andShow: true)
} else {
mapView.addAnnotations(positions)
}
}
// Other MKMapView Settings
mapView.preferredConfiguration.elevationStyle = .realistic// .flat
mapView.isPitchEnabled = true
@ -71,14 +73,14 @@ struct MapViewSwiftUI: UIViewRepresentable {
mapView.showsBuildings = true
mapView.showsScale = true
mapView.showsTraffic = true
#if targetEnvironment(macCatalyst)
// Show the default always visible compass and the mac only controls
mapView.showsCompass = true
mapView.showsZoomControls = true
mapView.showsPitchControl = true
#else
#if os(iOS)
// Hide the default compass that only appears when you are not going north and instead always show the compass in the bottom right corner of the map
mapView.showsCompass = false
@ -89,26 +91,26 @@ struct MapViewSwiftUI: UIViewRepresentable {
compassButton.trailingAnchor.constraint(equalTo: mapView.trailingAnchor, constant: -5).isActive = true
compassButton.bottomAnchor.constraint(equalTo: mapView.bottomAnchor, constant: -25).isActive = true
#endif
#endif
mapView.delegate = context.coordinator
return mapView
}
func updateUIView(_ mapView: MKMapView, context: Context) {
mapView.mapType = mapViewType
if self.customMapOverlay != self.presentCustomMapOverlayHash || self.loadedLastUpdatedLocalMapFile != self.lastUpdatedLocalMapFile {
mapView.removeOverlays(mapView.overlays)
if self.customMapOverlay != nil {
let fileManager = FileManager.default
let documentsDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!
let tilePath = documentsDirectory.appendingPathComponent("offline_map.mbtiles", isDirectory: false).path
if fileManager.fileExists(atPath: tilePath) {
print("Loading local map file")
if let overlay = LocalMBTileOverlay(mbTilePath: tilePath) {
overlay.canReplaceMapContent = false//customMapOverlay.canReplaceMapContent
overlay.canReplaceMapContent = false// customMapOverlay.canReplaceMapContent
mapView.addOverlay(overlay)
}
} else {
@ -120,9 +122,9 @@ struct MapViewSwiftUI: UIViewRepresentable {
self.loadedLastUpdatedLocalMapFile = self.lastUpdatedLocalMapFile
}
}
DispatchQueue.main.async {
let annotationCount = waypoints.count + positions.count
if annotationCount != mapView.annotations.count {
mapView.removeAnnotations(mapView.annotations)
@ -149,17 +151,17 @@ struct MapViewSwiftUI: UIViewRepresentable {
}
}
}
func makeCoordinator() -> MapCoordinator {
return Coordinator(self)
}
final class MapCoordinator: NSObject, MKMapViewDelegate, UIGestureRecognizerDelegate {
var parent: MapViewSwiftUI
var longPressRecognizer = UILongPressGestureRecognizer()
var overlays: [Overlay] = []
init(_ parent: MapViewSwiftUI) {
self.parent = parent
super.init()
@ -170,22 +172,21 @@ struct MapViewSwiftUI: UIViewRepresentable {
self.parent.mapView.addGestureRecognizer(longPressRecognizer)
self.overlays = []
}
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
switch annotation {
case let positionAnnotation as PositionEntity:
let reuseID = String(positionAnnotation.nodePosition?.num ?? 0) + "-" + String(positionAnnotation.time?.timeIntervalSince1970 ?? 0)
let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "node") as? MKMarkerAnnotationView ?? MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: reuseID )
annotationView.tag = -1
annotationView.canShowCallout = true
if positionAnnotation.latest {
annotationView.markerTintColor = .systemRed
annotationView.displayPriority = .required
annotationView.titleVisibility = .visible
}
else {
} else {
annotationView.markerTintColor = UIColor(.indigo)
annotationView.displayPriority = .defaultHigh
annotationView.titleVisibility = .adaptive
@ -203,10 +204,10 @@ struct MapViewSwiftUI: UIViewRepresentable {
let distanceFormatter = MKDistanceFormatter()
subtitle.text! += "Altitude: \(distanceFormatter.string(fromDistance: Double(positionAnnotation.altitude))) \n"
if positionAnnotation.nodePosition?.metadata != nil {
if DeviceRoles(rawValue: Int(positionAnnotation.nodePosition!.metadata?.role ?? 0)) == DeviceRoles.client ||
DeviceRoles(rawValue: Int(positionAnnotation.nodePosition!.metadata?.role ?? 0)) == DeviceRoles.clientMute ||
DeviceRoles(rawValue: Int(positionAnnotation.nodePosition!.metadata?.role ?? 0)) == DeviceRoles.routerClient{
DeviceRoles(rawValue: Int(positionAnnotation.nodePosition!.metadata?.role ?? 0)) == DeviceRoles.routerClient {
annotationView.glyphImage = UIImage(systemName: "flipphone")
} else if DeviceRoles(rawValue: Int(positionAnnotation.nodePosition!.metadata?.role ?? 0)) == DeviceRoles.repeater {
annotationView.glyphImage = UIImage(systemName: "repeat")
@ -217,7 +218,7 @@ struct MapViewSwiftUI: UIViewRepresentable {
} else if DeviceRoles(rawValue: Int(positionAnnotation.nodePosition!.metadata?.role ?? 0)) == DeviceRoles.sensor {
annotationView.glyphImage = UIImage(systemName: "sensor")
}
let pf = PositionFlags(rawValue: Int(positionAnnotation.nodePosition?.metadata?.positionFlags ?? 3))
if pf.contains(.Satsinview) {
subtitle.text! += "Sats in view: \(String(positionAnnotation.satsInView)) \n"
@ -225,13 +226,8 @@ struct MapViewSwiftUI: UIViewRepresentable {
if pf.contains(.SeqNo) {
subtitle.text! += "Sequence: \(String(positionAnnotation.seqNo)) \n"
}
if pf.contains(.Speed) {
let formatter = MeasurementFormatter()
formatter.locale = Locale.current
subtitle.text! += "Speed: \(formatter.string(from: Measurement(value: Double(positionAnnotation.speed), unit: UnitSpeed.kilometersPerHour))) \n"
}
if pf.contains(.Heading){
if pf.contains(.Heading) {
if parent.userTrackingMode != MKUserTrackingMode.followWithHeading {
annotationView.glyphImage = UIImage(systemName: "location.north.fill")?.rotate(radians: Float(degreesToRadians(Double(positionAnnotation.heading))))
subtitle.text! += "Heading: \(String(positionAnnotation.heading)) \n"
@ -239,9 +235,23 @@ struct MapViewSwiftUI: UIViewRepresentable {
annotationView.glyphImage = UIImage(systemName: "flipphone")
}
}
if pf.contains(.Speed) {
let formatter = MeasurementFormatter()
formatter.locale = Locale.current
if positionAnnotation.speed <= 1 {
annotationView.glyphImage = UIImage(systemName: "hexagon")
}
subtitle.text! += "Speed: \(formatter.string(from: Measurement(value: Double(positionAnnotation.speed), unit: UnitSpeed.kilometersPerHour))) \n"
}
} else {
// node metadata is nil
annotationView.glyphImage = UIImage(systemName: "flipphone")
}
if LocationHelper.currentLocation.distance(from: LocationHelper.DefaultLocation) > 0.0 {
let metersAway = positionAnnotation.coordinate.distance(from: LocationHelper.currentLocation)
subtitle.text! += NSLocalizedString("distance", comment: "") + ": \(distanceFormatter.string(fromDistance: Double(metersAway))) \n"
}
subtitle.text! += positionAnnotation.time?.formatted() ?? "Unknown \n"
subtitle.numberOfLines = 0
annotationView.detailCalloutAccessoryView = subtitle
@ -268,10 +278,14 @@ struct MapViewSwiftUI: UIViewRepresentable {
let subtitle = UILabel()
if waypointAnnotation.longDescription?.count ?? 0 > 0 {
subtitle.text = (waypointAnnotation.longDescription ?? "") + "\n"
}
else {
} else {
subtitle.text = ""
}
if LocationHelper.currentLocation.distance(from: LocationHelper.DefaultLocation) > 0.0 {
let metersAway = waypointAnnotation.coordinate.distance(from: LocationHelper.currentLocation)
let distanceFormatter = MKDistanceFormatter()
subtitle.text! += NSLocalizedString("distance", comment: "") + ": \(distanceFormatter.string(fromDistance: Double(metersAway))) \n"
}
if waypointAnnotation.created != nil {
subtitle.text! += "Created: \(waypointAnnotation.created?.formatted() ?? "Unknown") \n"
}
@ -290,23 +304,23 @@ struct MapViewSwiftUI: UIViewRepresentable {
default: return nil
}
}
func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
// Only Allow Edit for waypoint annotations with a id
if view.tag > 0 {
parent.onWaypointEdit(view.tag)
}
}
@objc func longPressHandler(_ gesture: UILongPressGestureRecognizer) {
if gesture.state != UIGestureRecognizer.State.ended {
return
} else if gesture.state != UIGestureRecognizer.State.began {
// Screen Position - CGPoint
let location = longPressRecognizer.location(in: self.parent.mapView)
// Map Coordinate - CLLocationCoordinate2D
let coordinate = self.parent.mapView.convert(location, toCoordinateFrom: self.parent.mapView)
let annotation = MKPointAnnotation()
@ -317,11 +331,11 @@ struct MapViewSwiftUI: UIViewRepresentable {
parent.onLongPress(coordinate)
}
}
public func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
if let index = self.overlays.firstIndex(where: { overlay_ in overlay_.shape.hash == overlay.hash }) {
let unwrappedOverlay = self.overlays[index]
if let circleOverlay = unwrappedOverlay.shape as? MKCircle {
let renderer = MKCircleRenderer(circle: circleOverlay)
@ -363,18 +377,18 @@ struct MapViewSwiftUI: UIViewRepresentable {
}
}
}
/// is supposed to be located in the folder with the map name
public struct DefaultTile: Hashable {
let tileName: String
let tileType: String
public init(tileName: String, tileType: String) {
self.tileName = tileName
self.tileType = tileType
}
}
public struct CustomMapOverlay: Equatable, Hashable {
let mapName: String
let tileType: String
@ -382,7 +396,7 @@ struct MapViewSwiftUI: UIViewRepresentable {
var minimumZoomLevel: Int?
var maximumZoomLevel: Int?
let defaultTile: DefaultTile?
public init(
mapName: String,
tileType: String,
@ -398,7 +412,7 @@ struct MapViewSwiftUI: UIViewRepresentable {
self.maximumZoomLevel = maximumZoomLevel
self.defaultTile = defaultTile
}
public init?(
mapName: String?,
tileType: String,
@ -407,7 +421,7 @@ struct MapViewSwiftUI: UIViewRepresentable {
maximumZoomLevel: Int? = nil,
defaultTile: DefaultTile? = nil
) {
if (mapName == nil || mapName! == "") {
if mapName == nil || mapName! == "" {
return nil
}
self.mapName = mapName!
@ -418,15 +432,15 @@ struct MapViewSwiftUI: UIViewRepresentable {
self.defaultTile = defaultTile
}
}
public class CustomMapOverlaySource: MKTileOverlay {
// requires folder: tiles/{mapName}/z/y/y,{tileType}
private var parent: MapViewSwiftUI
private let mapName: String
private let tileType: String
private let defaultTile: DefaultTile?
public init(
parent: MapViewSwiftUI,
mapName: String,
@ -439,7 +453,7 @@ struct MapViewSwiftUI: UIViewRepresentable {
self.defaultTile = defaultTile
super.init(urlTemplate: "")
}
public override func url(forTilePath path: MKTileOverlayPath) -> URL {
if let tileUrl = Bundle.main.url(
forResource: "\(path.y)",
@ -461,21 +475,21 @@ struct MapViewSwiftUI: UIViewRepresentable {
}
}
}
public struct Overlay {
public static func == (lhs: MapViewSwiftUI.Overlay, rhs: MapViewSwiftUI.Overlay) -> Bool {
// maybe to use in the future for comparison of full array
lhs.shape.coordinate.latitude == rhs.shape.coordinate.latitude &&
lhs.shape.coordinate.longitude == rhs.shape.coordinate.longitude &&
lhs.fillColor == rhs.fillColor
}
var shape: MKOverlay
var fillColor: UIColor?
var strokeColor: UIColor?
var lineWidth: CGFloat
public init(
shape: MKOverlay,
fillColor: UIColor? = nil,

View file

@ -1,60 +0,0 @@
////
//// PositionAnnotationView.swift
//// MeshtasticApple
////
//// Created by Joshua Pirihi on 24/12/21.
////
//
//import UIKit
//import MapKit
//import SwiftUI
//
//// a simple circle annotation, with a string in it
//class PositionAnnotation: NSObject, MKAnnotation {
//
// // This property must be key-value observable, which the `@objc dynamic` attributes provide.
// @objc dynamic var coordinate = CLLocationCoordinate2D(latitude: 0, longitude: 0)
//
// // Required if you set the annotation view's `canShowCallout` property to `true`
// // this string fills the callout label when you tap an annotation
// var title: String?
//
// // the text to appear inside the little circle
// var shortName: String?
//
//}
//
//class PositionAnnotationView: MKAnnotationView {
//
// private let annotationFrame = CGRect(x: 0, y: 0, width: 40, height: 40)
// private let label: UILabel
//
// override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
// self.label = UILabel(frame: annotationFrame.offsetBy(dx: 0, dy: 0))
// super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
// self.frame = annotationFrame
// self.label.font = UIFont.preferredFont(forTextStyle: .caption2)
// self.label.textColor = .white
// self.label.textAlignment = .center
// self.backgroundColor = .clear
// self.addSubview(label)
// }
//
// required init?(coder aDecoder: NSCoder) {
// fatalError("init(coder:) not implemented!")
// }
//
// public var name: String = "" {
// didSet {
// self.label.text = name
// }
// }
//
// override func draw(_ rect: CGRect) {
// guard let context = UIGraphicsGetCurrentContext() else { return }
//
// let circleRect = CGRect(x: 1, y: 1, width: 38, height: 38)
// context.setFillColor(Color.accentColor.cgColor ?? CGColor(red: 0, green: 0.5, blue: 1.0, alpha: 1.0))
// context.fillEllipse(in: circleRect)
// }
//}

View file

@ -9,14 +9,14 @@ import SwiftUI
import CoreLocation
struct WaypointFormView: View {
@EnvironmentObject var bleManager: BLEManager
@Environment(\.dismiss) private var dismiss
@State var coordinate: CLLocationCoordinate2D
@State var waypointId : Int = 0
@State var waypointId: Int = 0
@FocusState private var iconIsFocused: Bool
@State private var name: String = ""
@State private var description: String = ""
@State private var icon: String = "📍"
@ -26,9 +26,9 @@ struct WaypointFormView: View {
@State private var expire: Date = Date() // = Date.now.addingTimeInterval(60 * 120) // 1 minute * 120 = 2 Hours
@State private var locked: Bool = false
@State private var lockedTo: Int64 = 0
var body: some View {
Form {
let distance = CLLocation(latitude: LocationHelper.currentLocation.latitude, longitude: LocationHelper.currentLocation.longitude).distance(from: CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude))
Section(header: Text((waypointId > 0) ? "Editing Waypoint" : "Create Waypoint")) {
@ -52,7 +52,7 @@ struct WaypointFormView: View {
axis: .vertical
)
.foregroundColor(Color.gray)
.onChange(of: name, perform: { value in
.onChange(of: name, perform: { _ in
let totalBytes = name.utf8.count
// Only mess with the value if it is too big
if totalBytes > 30 {
@ -73,7 +73,7 @@ struct WaypointFormView: View {
axis: .vertical
)
.foregroundColor(Color.gray)
.onChange(of: description, perform: { value in
.onChange(of: description, perform: { _ in
let totalBytes = description.utf8.count
// Only mess with the value if it is too big
if totalBytes > 100 {
@ -92,14 +92,14 @@ struct WaypointFormView: View {
.font(.title)
.focused($iconIsFocused)
.onChange(of: icon) { value in
// If you have anything other than emojis in your string make it empty
if !value.onlyEmojis() {
icon = ""
}
// If a second emoji is entered delete the first one
if value.count >= 1 {
if value.count > 1 {
let index = value.index(value.startIndex, offsetBy: 1)
icon = String(value[index])
@ -107,7 +107,7 @@ struct WaypointFormView: View {
iconIsFocused = false
}
}
}
Toggle(isOn: $expires) {
Label("Expires", systemImage: "clock.badge.xmark")
@ -126,9 +126,9 @@ struct WaypointFormView: View {
}
HStack {
Button {
var newWaypoint = Waypoint()
if waypointId > 0 {
newWaypoint.id = UInt32(waypointId)
} else {
@ -144,7 +144,7 @@ struct WaypointFormView: View {
let unicode = unicodeScalers[unicodeScalers.startIndex].value
newWaypoint.icon = unicode
if locked {
if lockedTo == 0 {
newWaypoint.lockedTo = UInt32(bleManager.connectedPeripheral!.num)
} else {
@ -172,8 +172,8 @@ struct WaypointFormView: View {
.controlSize(.large)
.disabled(bleManager.connectedPeripheral == nil)
.padding(.bottom)
Button(role:.cancel) {
Button(role: .cancel) {
dismiss()
} label: {
Label("cancel", systemImage: "x.circle")
@ -184,7 +184,7 @@ struct WaypointFormView: View {
.padding(.bottom)
if waypointId > 0 {
Menu {
Button("For me", action: {
let waypoint = getWaypoint(id: Int64(waypointId), context: bleManager.context!)
@ -197,10 +197,10 @@ struct WaypointFormView: View {
dismiss() })
Button("For everyone", action: {
var newWaypoint = Waypoint()
if waypointId > 0 {
newWaypoint.id = UInt32(waypointId)
}
}
newWaypoint.name = name.count > 0 ? name : "Dropped Pin"
newWaypoint.description_p = description
newWaypoint.latitudeI = Int32(coordinate.latitude * 1e7)
@ -211,7 +211,7 @@ struct WaypointFormView: View {
let unicode = unicodeScalers[unicodeScalers.startIndex].value
newWaypoint.icon = unicode
if locked {
if lockedTo == 0 {
newWaypoint.lockedTo = UInt32(bleManager.connectedPeripheral!.num)
} else {
@ -241,7 +241,7 @@ struct WaypointFormView: View {
}
.onChange(of: waypointId) { newId in
print(newId)
}
.onAppear {
if waypointId > 0 {

View file

@ -9,27 +9,27 @@ import SwiftUI
import CoreData
struct ChannelMessageList: View {
@Environment(\.managedObjectContext) var context
@EnvironmentObject var bleManager: BLEManager
@EnvironmentObject var userSettings: UserSettings
enum Field: Hashable {
case messageText
}
// Keyboard State
@State var typingMessage: String = ""
@State private var totalBytes = 0
var maxbytes = 228
@FocusState var focusedField: Field?
@ObservedObject var channel: ChannelEntity
@State var showDeleteMessageAlert = false
@State private var deleteMessageId: Int64 = 0
@State private var replyMessageId: Int64 = 0
@State private var sendPositionWithMessage: Bool = false
var body: some View {
NavigationStack {
let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMddjmmssa", options: 0, locale: Locale.current)
@ -54,8 +54,8 @@ struct ChannelMessageList: View {
.padding(.trailing)
}
}
HStack (alignment: .top) {
if currentUser { Spacer(minLength:50) }
HStack(alignment: .top) {
if currentUser { Spacer(minLength: 50) }
if !currentUser {
CircleText(text: message.fromUser?.shortName ?? "????", color: currentUser ? .accentColor : Color(.gray), circleSize: 44, fontSize: 14)
.padding(.all, 5)
@ -71,7 +71,7 @@ struct ChannelMessageList: View {
.background(currentUser ? .accentColor : Color(.gray))
.cornerRadius(15)
.contextMenu {
VStack{
VStack {
Text("channel")+Text(": \(message.channel)")
}
Menu("tapback") {
@ -81,7 +81,7 @@ struct ChannelMessageList: View {
print("Sent \(tb.emojiString) Tapback")
self.context.refresh(channel, mergeChanges: true)
} else { print("\(tb.emojiString) Tapback Failed") }
}) {
Text(tb.description)
let image = tb.emojiString.image()
@ -152,11 +152,10 @@ struct ChannelMessageList: View {
Image(systemName: "trash")
}
}
let tapbacks = message.value(forKey: "tapbacks") as! [MessageEntity]
let tapbacks = message.value(forKey: "tapbacks") as? [MessageEntity] ?? []
if tapbacks.count > 0 {
VStack (alignment: .trailing) {
HStack {
VStack(alignment: .trailing) {
HStack {
ForEach( tapbacks ) { (tapback: MessageEntity) in
VStack {
let image = tapback.messagePayload!.image(fontSize: 20)
@ -193,7 +192,7 @@ struct ChannelMessageList: View {
.padding(.bottom)
.id(channel.allPrivateMessages.firstIndex(of: message))
if !currentUser {
Spacer(minLength:50)
Spacer(minLength: 50)
}
}
.padding([.leading, .trailing])
@ -225,7 +224,7 @@ struct ChannelMessageList: View {
scrollView.scrollTo(channel.allPrivateMessages.last!.messageId)
}
})
.onChange(of: channel.allPrivateMessages, perform: { messages in
.onChange(of: channel.allPrivateMessages, perform: { _ in
if channel.allPrivateMessages.count > 0 {
scrollView.scrollTo(channel.allPrivateMessages.last!.messageId)
}
@ -238,14 +237,14 @@ struct ChannelMessageList: View {
let userLongName = bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown"
sendPositionWithMessage = true
if userSettings.meshtasticUsername.count > 0 {
typingMessage = "📍 " + userSettings.meshtasticUsername + " has shared their position with you from node " + userLongName
} else {
typingMessage = "📍 " + userLongName + " has shared their position with you."
}
} label: {
Text("share.position")
Image(systemName: "mappin.and.ellipse")
@ -261,7 +260,7 @@ struct ChannelMessageList: View {
}
#endif
HStack(alignment: .top) {
ZStack {
let kbType = UIKeyboardType(rawValue: UserDefaults.standard.object(forKey: "keyboardType") as? Int ?? 0)
TextField("message", text: $typingMessage, axis: .vertical)
@ -290,20 +289,20 @@ struct ChannelMessageList: View {
let userLongName = bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown"
sendPositionWithMessage = true
if userSettings.meshtasticUsername.count > 0 {
typingMessage = "📍 " + userSettings.meshtasticUsername + " has shared their position with you from node " + userLongName
} else {
typingMessage = "📍 " + userLongName + " has shared their position with you."
}
} label: {
Image(systemName: "mappin.and.ellipse")
.symbolRenderingMode(.hierarchical)
.imageScale(.large).foregroundColor(.accentColor)
}
ProgressView("\(NSLocalizedString("bytes", comment: "")): \(totalBytes) / \(maxbytes)", value: Double(totalBytes), total: Double(maxbytes))
.frame(width: 130)
.padding(5)

View file

@ -13,15 +13,15 @@ struct Contacts: View {
@Environment(\.managedObjectContext) var context
@EnvironmentObject var bleManager: BLEManager
@ObservedObject private var userSettings: UserSettings = UserSettings()
@FetchRequest(
sortDescriptors: [NSSortDescriptor(key: "longName", ascending: true)],
animation: .default)
private var users: FetchedResults<UserEntity>
@State var node: NodeInfoEntity? = nil
@State private var userSelection: UserEntity? = nil // Nothing selected by default.
@State private var channelSelection: ChannelEntity? = nil // Nothing selected by default.
@State var node: NodeInfoEntity?
@State private var userSelection: UserEntity? // Nothing selected by default.
@State private var channelSelection: ChannelEntity? // Nothing selected by default.
@State private var isPresentingDeleteChannelMessagesConfirm: Bool = false
@State private var isPresentingDeleteUserMessagesConfirm: Bool = false
@State private var isPresentingTraceRouteSentAlert = false
@ -38,7 +38,7 @@ struct Contacts: View {
ForEach(node!.myInfo!.channels!.array as! [ChannelEntity], id: \.self) { (channel: ChannelEntity) in
if channel.name?.lowercased() ?? "" != "admin" && channel.name?.lowercased() ?? "" != "gpio" && channel.name?.lowercased() ?? "" != "serial" {
NavigationLink(destination: ChannelMessageList(channel: channel)) {
let mostRecent = channel.allPrivateMessages.last(where: { $0.channel == channel.index })
let lastMessageTime = Date(timeIntervalSince1970: TimeInterval(Int64((mostRecent?.messageTimestamp ?? 0 ))))
let lastMessageDay = Calendar.current.dateComponents([.day], from: lastMessageTime).day ?? 0
@ -60,7 +60,7 @@ struct Contacts: View {
}
Spacer()
if channel.allPrivateMessages.count > 0 {
VStack (alignment: .trailing) {
VStack(alignment: .trailing) {
if lastMessageDay == currentDay {
Text(lastMessageTime, style: .time )
.font(.subheadline)
@ -102,7 +102,7 @@ struct Contacts: View {
// Would rather not do this but the merge changes on
// A single object is only working on mac GVH
context.refreshAllObjects()
//context.refresh(channel, mergeChanges: true)
// context.refresh(channel, mergeChanges: true)
} catch {
context.rollback()
print("💥 Save Channel Mute Error")
@ -136,7 +136,7 @@ struct Contacts: View {
}
}
.padding([.top, .bottom])
}
}
Section(header: Text("direct.messages")) {
@ -157,7 +157,7 @@ struct Contacts: View {
Text(user.longName ?? NSLocalizedString("unknown", comment: "Unknown")).font(.headline)
Spacer()
if user.messageList.count > 0 {
VStack (alignment: .trailing) {
VStack(alignment: .trailing) {
if lastMessageDay == currentDay {
Text(lastMessageTime, style: .time )
.font(.subheadline)
@ -218,8 +218,7 @@ struct Contacts: View {
.alert(
"Trace Route Sent",
isPresented: $isPresentingTraceRouteSentAlert
)
{
) {
Button("OK", role: .cancel) { }
}
message: {
@ -258,21 +257,23 @@ struct Contacts: View {
let fetchNodeInfoRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "NodeInfoEntity")
fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(bleManager.connectedPeripheral?.num ?? -1))
do {
let fetchedNode = try context.fetch(fetchNodeInfoRequest) as! [NodeInfoEntity]
guard let fetchedNode = try context.fetch(fetchNodeInfoRequest) as? [NodeInfoEntity] else {
return
}
// Found a node, check it for a region
if !fetchedNode.isEmpty {
node = fetchedNode[0]
}
} catch {
}
}
}
}
detail: {
if let user = userSelection {
UserMessageList(user:user)
UserMessageList(user: user)
} else {
Text("select.contact")
}

View file

@ -9,11 +9,11 @@ import SwiftUI
import CoreData
struct UserMessageList: View {
@Environment(\.managedObjectContext) var context
@EnvironmentObject var bleManager: BLEManager
@EnvironmentObject var userSettings: UserSettings
enum Field: Hashable {
case messageText
}
@ -28,7 +28,7 @@ struct UserMessageList: View {
@State private var deleteMessageId: Int64 = 0
@State private var replyMessageId: Int64 = 0
@State private var sendPositionWithMessage: Bool = false
var body: some View {
NavigationStack {
let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMddjmmss", options: 0, locale: Locale.current)
@ -39,7 +39,7 @@ struct UserMessageList: View {
ForEach( user.messageList ) { (message: MessageEntity) in
if user.num != bleManager.connectedPeripheral?.num ?? -1 {
let currentUser: Bool = (bleManager.connectedPeripheral?.num ?? 0 == message.fromUser?.num ?? -1 ? true : false)
if message.replyID > 0 {
let messageReply = user.messageList.first(where: { $0.messageId == message.replyID })
HStack {
@ -55,8 +55,8 @@ struct UserMessageList: View {
.padding(.trailing)
}
}
HStack (alignment: .top) {
if currentUser { Spacer(minLength:50) }
HStack(alignment: .top) {
if currentUser { Spacer(minLength: 50) }
if !currentUser {
CircleText(text: message.fromUser?.shortName ?? "????", color: currentUser ? .accentColor : Color(.gray), circleSize: 44, fontSize: 14)
.padding(.all, 5)
@ -64,7 +64,7 @@ struct UserMessageList: View {
}
VStack(alignment: currentUser ? .trailing : .leading) {
let markdownText: LocalizedStringKey = LocalizedStringKey.init(message.messagePayloadMarkdown ?? (message.messagePayload ?? "EMPTY MESSAGE"))
let linkBlue = Color(red: 0.4627, green: 0.8392, blue: 1) /* #76d6ff */
Text(markdownText)
.tint(linkBlue)
@ -73,7 +73,7 @@ struct UserMessageList: View {
.background(currentUser ? .accentColor : Color(.gray))
.cornerRadius(15)
.contextMenu {
VStack{
VStack {
Text("channel")+Text(": \(message.channel)")
}
Menu("tapback") {
@ -83,7 +83,7 @@ struct UserMessageList: View {
print("Sent \(tb.emojiString) Tapback")
self.context.refresh(user, mergeChanges: true)
} else { print("\(tb.emojiString) Tapback Failed") }
}) {
Text(tb.description)
let image = tb.emojiString.image()
@ -157,11 +157,11 @@ struct UserMessageList: View {
Image(systemName: "trash")
}
}
let tapbacks = message.value(forKey: "tapbacks") as! [MessageEntity]
let tapbacks = message.value(forKey: "tapbacks") as? [MessageEntity] ?? []
if tapbacks.count > 0 {
VStack (alignment: .trailing) {
HStack {
VStack(alignment: .trailing) {
HStack {
ForEach( tapbacks ) { (tapback: MessageEntity) in
VStack {
let image = tapback.messagePayload!.image(fontSize: 20)
@ -198,7 +198,7 @@ struct UserMessageList: View {
.padding(.bottom)
.id(user.messageList.firstIndex(of: message))
if !currentUser {
Spacer(minLength:50)
Spacer(minLength: 50)
}
}
.padding([.leading, .trailing])
@ -231,7 +231,7 @@ struct UserMessageList: View {
scrollView.scrollTo(user.messageList.last!.messageId)
}
})
.onChange(of: user.messageList, perform: { messages in
.onChange(of: user.messageList, perform: { _ in
if user.messageList.count > 0 {
scrollView.scrollTo(user.messageList.last!.messageId)
}
@ -243,13 +243,13 @@ struct UserMessageList: View {
Button {
let userLongName = bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown"
sendPositionWithMessage = true
if userSettings.meshtasticUsername.count > 0 {
typingMessage = "📍 " + userSettings.meshtasticUsername + " has shared their position with you from node " + userLongName + " and requested a response with your position."
} else {
typingMessage = "📍 " + userLongName + " has shared their position and requested a response with your position."
}
} label: {
Text("share.position")
Image(systemName: "mappin.and.ellipse")
@ -264,7 +264,7 @@ struct UserMessageList: View {
.padding(.trailing)
}
#endif
HStack(alignment: .top) {
ZStack {
let kbType = UIKeyboardType(rawValue: UserDefaults.standard.object(forKey: "keyboardType") as? Int ?? 0)
@ -293,13 +293,13 @@ struct UserMessageList: View {
Button {
let userLongName = bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown"
sendPositionWithMessage = true
if userSettings.meshtasticUsername.count > 0 {
typingMessage = "📍 " + userSettings.meshtasticUsername + " has shared their position with you from node " + userLongName + " and requested a response with your position."
} else {
typingMessage = "📍 " + userLongName + " has shared their position and requested a response with your position."
}
} label: {
Image(systemName: "mappin.and.ellipse")
.symbolRenderingMode(.hierarchical)

View file

@ -8,21 +8,21 @@ import SwiftUI
import Charts
struct DeviceMetricsLog: View {
@Environment(\.managedObjectContext) var context
@EnvironmentObject var bleManager: BLEManager
@State private var isPresentingClearLogConfirm: Bool = false
@State var isExporting = false
@State var exportString = ""
var node: NodeInfoEntity
var body: some View {
NavigationStack {
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")),
@ -39,15 +39,15 @@ struct DeviceMetricsLog: View {
let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMddjmma", options: 0, locale: Locale.current)
let dateFormatString = (localeDateFormat ?? "MM/dd/YY j:mma").replacingOccurrences(of: ",", with: "")
if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac {
//Add a table for mac and ipad
// Add a table for mac and ipad
Table(node.telemetries!.reversed() as! [TelemetryEntity]) {
TableColumn("battery.level") { dm in
if dm.metricsType == 0 {
if dm.batteryLevel == 0 {
Text("Powered")
} else {
Text("\(String(dm.batteryLevel))%")
}
}
@ -73,10 +73,10 @@ struct DeviceMetricsLog: View {
}
}
}
} else {
ScrollView {
let columns = [
GridItem(.flexible(minimum: 30, maximum: 60), spacing: 0.1),
GridItem(.flexible(minimum: 30, maximum: 60), spacing: 0.1),
@ -118,7 +118,7 @@ struct DeviceMetricsLog: View {
.font(.caption)
Text("\(String(format: "%.2f", dm.airUtilTx))%")
.font(.caption)
Text(dm.time?.formattedDate(format: dateFormatString) ?? "Unknown time")
.font(.caption2)
}
@ -154,7 +154,7 @@ struct DeviceMetricsLog: View {
}
}
Button {
exportString = TelemetryToCsvFile(telemetry: node.telemetries!.array as! [TelemetryEntity], metricsType: 0)
exportString = telemetryToCsvFile(telemetry: node.telemetries!.array as? [TelemetryEntity] ?? [], metricsType: 0)
isExporting = true
} label: {
Label("save", systemImage: "square.and.arrow.down")
@ -182,7 +182,7 @@ struct DeviceMetricsLog: View {
if case .success = result {
print("Device metrics log download succeeded.")
self.isExporting = false
} else {
print("Device metrics log download failed: \(result).")
}

View file

@ -7,24 +7,24 @@
import SwiftUI
struct EnvironmentMetricsLog: View {
@Environment(\.managedObjectContext) var context
@EnvironmentObject var bleManager: BLEManager
@State private var isPresentingClearLogConfirm: Bool = false
@State var isExporting = false
@State var exportString = ""
var node: NodeInfoEntity
var body: some View {
NavigationStack {
let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMddjmma", options: 0, locale: Locale.current)
let dateFormatString = (localeDateFormat ?? "MM/dd/YY j:mma").replacingOccurrences(of: ",", with: "")
if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac {
//Add a table for mac and ipad
// Add a table for mac and ipad
Table(node.telemetries!.reversed() as! [TelemetryEntity]) {
TableColumn("Temperature") { em in
if em.metricsType == 1 {
@ -72,7 +72,7 @@ struct EnvironmentMetricsLog: View {
GridItem(spacing: 0)
]
LazyVGrid(columns: columns, alignment: .leading, spacing: 1) {
GridRow {
Text("Temp")
.font(.caption)
@ -91,11 +91,11 @@ struct EnvironmentMetricsLog: View {
.fontWeight(.bold)
}
ForEach(node.telemetries!.reversed() as! [TelemetryEntity], id: \.self) { (em: TelemetryEntity) in
if em.metricsType == 1 {
GridRow {
Text(em.temperature.formattedTemperature())
.font(.caption)
Text("\(String(format: "%.2f", em.relativeHumidity))")
@ -116,7 +116,7 @@ struct EnvironmentMetricsLog: View {
}
}
HStack {
Button(role: .destructive) {
isPresentingClearLogConfirm = true
} label: {
@ -134,11 +134,11 @@ struct EnvironmentMetricsLog: View {
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)
exportString = telemetryToCsvFile(telemetry: node.telemetries!.array as? [TelemetryEntity] ?? [], metricsType: 1)
isExporting = true
} label: {
Label("save", systemImage: "square.and.arrow.down")

View file

@ -9,7 +9,7 @@ import MapKit
import CoreLocation
struct NodeDetail: View {
@Environment(\.managedObjectContext) var context
@EnvironmentObject var bleManager: BLEManager
@Environment(\.colorScheme) var colorScheme: ColorScheme
@ -29,35 +29,34 @@ struct NodeDetail: View {
tileType: "png",
canReplaceMapContent: true
)
var node: NodeInfoEntity
@FetchRequest(sortDescriptors: [NSSortDescriptor(key: "name", ascending: false)],
predicate: NSPredicate(
format: "expire == nil || expire >= %@", Date() as NSDate
), animation: .none)
private var waypoints: FetchedResults<WaypointEntity>
/// The current weather condition for the city.
@State private var condition: WeatherCondition?
@State private var temperature: Measurement<UnitTemperature>?
@State private var humidity: Int?
@State private var symbolName: String = "cloud.fill"
@State private var attributionLink: URL?
@State private var attributionLogo: URL?
var body: some View {
let hwModelString = node.user?.hwModel ?? "UNSET"
NavigationStack {
GeometryReader { bounds in
VStack {
if node.positions?.count ?? 0 > 0 {
// let mostRecent = node.positions?.lastObject as! PositionEntity
ZStack {
let annotations = node.positions?.array as! [PositionEntity]
let annotations = node.positions?.array as? [PositionEntity] ?? []
ZStack {
MapViewSwiftUI(onLongPress: { coord in
waypointCoordinate = coord
@ -75,12 +74,12 @@ struct NodeDetail: View {
centerOnPositionsOnly: true,
customMapOverlay: self.customMapOverlay,
overlays: self.overlays
)
VStack (alignment: .leading) {
VStack(alignment: .leading) {
Spacer()
HStack (alignment: .bottom, spacing: 1) {
HStack(alignment: .bottom, spacing: 1) {
Picker("Map Type", selection: $mapType) {
ForEach(MeshMapType.allCases) { map in
Text(map.description).tag(map.MKMapTypeValue())
@ -92,8 +91,7 @@ struct NodeDetail: View {
VStack {
Label(temperature?.formatted(.measurement(width: .narrow)) ?? "??", systemImage: symbolName)
.font(.caption)
Label("\(humidity ?? 0)%", systemImage: "humidity")
.font(.caption2)
}
@ -122,7 +120,7 @@ struct NodeDetail: View {
#endif
.gesture(
LongPressGesture(minimumDuration: 0.5)
.onEnded { value in
.onEnded { _ in
showingForecast = true
}
)
@ -137,7 +135,7 @@ struct NodeDetail: View {
}
.padding([.top], 20)
}
ScrollView {
Divider()
if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac {
@ -153,17 +151,17 @@ struct NodeDetail: View {
.aspectRatio(contentMode: .fill)
.frame(width: 100, height: 100)
.cornerRadius(5)
Text(String(hwModelString))
.foregroundColor(.gray)
.font(.largeTitle).fixedSize()
}
}
if node.snr > 0 {
Divider()
VStack(alignment: .center) {
Image(systemName: "waveform.path")
.font(.title)
.foregroundColor(.accentColor)
@ -176,15 +174,15 @@ struct NodeDetail: View {
.fixedSize()
}
}
if node.telemetries?.count ?? 0 >= 1 {
let mostRecent = node.telemetries?.lastObject as! TelemetryEntity
Divider()
VStack(alignment: .center) {
BatteryGauge(batteryLevel: Double(mostRecent.batteryLevel))
if mostRecent.voltage > 0 {
Text(String(format: "%.2f", mostRecent.voltage) + " V")
.font(.title)
.foregroundColor(.gray)
@ -195,10 +193,10 @@ struct NodeDetail: View {
}
}
.padding()
Divider()
HStack(alignment: .center) {
VStack {
HStack {
Image(systemName: "person")
@ -207,7 +205,7 @@ struct NodeDetail: View {
.symbolRenderingMode(.hierarchical)
Text("user").font(.title)+Text(":").font(.title)
}
Text("!\(String(format:"%02x", node.num))")
Text("!\(String(format: "%02x", node.num))")
.font(.title).foregroundColor(.gray)
}
Divider()
@ -222,28 +220,28 @@ struct NodeDetail: View {
Text(String(node.num)).font(.title).foregroundColor(.gray)
}
Divider()
VStack{
VStack {
HStack {
Image(systemName: "globe")
.font(.title)
.foregroundColor(.accentColor)
.symbolRenderingMode(.hierarchical)
Text("MAC Address: ").font(.title)
}
Text(String(node.user?.macaddr?.macAddressString ?? "not a valid mac address"))
.font(.title)
.foregroundColor(.gray)
}
Divider()
VStack{
VStack {
HStack {
Image(systemName: "clock.badge.checkmark.fill")
.font(.title)
.foregroundColor(.accentColor)
.symbolRenderingMode(.hierarchical)
Text("heard.last").font(.title)+Text(":").font(.title)
}
DateTimeText(dateTime: node.lastHeard)
.font(.title3)
@ -251,11 +249,11 @@ struct NodeDetail: View {
}
}
Divider()
} else {
HStack {
VStack(alignment: .center) {
CircleText(text: node.user?.shortName ?? "???", color: .accentColor)
}
@ -270,11 +268,11 @@ struct NodeDetail: View {
.font(.callout).fixedSize()
}
}
if node.snr > 0 {
Divider()
VStack(alignment: .center) {
Image(systemName: "waveform.path")
.font(.title)
.foregroundColor(.accentColor)
@ -286,14 +284,14 @@ struct NodeDetail: View {
.fixedSize()
}
}
if node.telemetries?.count ?? 0 >= 1 {
let mostRecent = node.telemetries?.lastObject as! TelemetryEntity
Divider()
VStack(alignment: .center) {
BatteryGauge(batteryLevel: Double(mostRecent.batteryLevel))
if mostRecent.voltage > 0 {
Text(String(format: "%.2f", mostRecent.voltage) + " V")
.font(.callout)
.foregroundColor(.gray)
@ -338,36 +336,36 @@ struct NodeDetail: View {
.padding([.bottom], 10)
Divider()
}
VStack {
if (node.positions?.count ?? 0) > 0 {
NavigationLink {
PositionLog(node: node)
} label: {
Image(systemName: "building.columns")
.symbolRenderingMode(.hierarchical)
.font(.title)
Text("Position Log")
.font(.title3)
}
.fixedSize(horizontal: false, vertical: true)
Divider()
}
if (node.telemetries?.count ?? 0) > 0 {
NavigationLink {
DeviceMetricsLog(node: node)
} label: {
Image(systemName: "flipphone")
.symbolRenderingMode(.hierarchical)
.font(.title)
Text("Device Metrics Log")
.font(.title3)
}
@ -375,28 +373,28 @@ struct NodeDetail: View {
NavigationLink {
EnvironmentMetricsLog(node: node)
} label: {
Image(systemName: "chart.xyaxis.line")
.symbolRenderingMode(.hierarchical)
.font(.title)
Text("Environment Metrics Log")
.font(.title3)
}
Divider()
}
}
if (self.bleManager.connectedPeripheral != nil && node.metadata != nil) {
if self.bleManager.connectedPeripheral != nil && node.metadata != nil {
HStack {
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1, context: context)
if node.metadata?.canShutdown ?? false || hwModelString == "RAK4631" {//node.metadata?.hwModel ?? "UNSET" == "RAK4631" {
if node.metadata?.canShutdown ?? false || hwModelString == "RAK4631" {// node.metadata?.hwModel ?? "UNSET" == "RAK4631" {
Button(action: {
showingShutdownConfirm = true
}) {
Label("Power Off", systemImage: "power")
}
.buttonStyle(.bordered)
@ -414,7 +412,7 @@ struct NodeDetail: View {
}
}
}
Button(action: {
showingRebootConfirm = true
}) {
@ -448,7 +446,7 @@ struct NodeDetail: View {
.controlSize(.mini)
}
.frame(height: 15)
Link("Other data sources", destination: attributionLink ?? URL(string: "https://weather-data.apple.com/legal-attribution.html")!)
}
.font(.footnote)
@ -456,7 +454,7 @@ struct NodeDetail: View {
}
}
.edgesIgnoringSafeArea([.leading, .trailing])
.sheet(isPresented: $presentingWaypointForm ) {//, onDismiss: didDismissSheet) {
.sheet(isPresented: $presentingWaypointForm ) {// , onDismiss: didDismissSheet) {
WaypointFormView(coordinate: waypointCoordinate ?? LocationHelper.DefaultLocation, waypointId: editingWaypoint)
.presentationDetents([.medium, .large])
.presentationDragIndicator(.automatic)
@ -472,41 +470,35 @@ struct NodeDetail: View {
.onAppear {
self.bleManager.context = context
switch meshMapType {
case "standard":
mapType = .standard
break
case "mutedStandard":
mapType = .mutedStandard
break
case "hybrid":
mapType = .hybrid
break
case "hybridFlyover":
mapType = .hybridFlyover
break
case "satellite":
mapType = .satellite
break
case "satelliteFlyover":
mapType = .satelliteFlyover
break
default:
mapType = .hybridFlyover
case "standard":
mapType = .standard
case "mutedStandard":
mapType = .mutedStandard
case "hybrid":
mapType = .hybrid
case "hybridFlyover":
mapType = .hybridFlyover
case "satellite":
mapType = .satellite
case "satelliteFlyover":
mapType = .satelliteFlyover
default:
mapType = .hybridFlyover
}
}
.task(id: node.num) {
do {
if node.positions?.count ?? 0 > 0 {
let mostRecent = node.positions?.lastObject as! PositionEntity
let weather = try await WeatherService.shared.weather(for: mostRecent.nodeLocation!)
condition = weather.currentWeather.condition
temperature = weather.currentWeather.temperature
humidity = Int(weather.currentWeather.humidity * 100)
symbolName = weather.currentWeather.symbolName
let attribution = try await WeatherService.shared.attribution
attributionLink = attribution.legalPageURL
attributionLogo = colorScheme == .light ? attribution.combinedMarkLightURL : attribution.combinedMarkDarkURL

View file

@ -23,12 +23,12 @@ struct NodeList: View {
private var nodes: FetchedResults<NodeInfoEntity>
@State private var selection: NodeInfoEntity? = nil // Nothing selected by default.
@State private var selection: NodeInfoEntity? // Nothing selected by default.
var body: some View {
NavigationSplitView {
List (nodes, id: \.self, selection: $selection) { node in
List(nodes, id: \.self, selection: $selection) { node in
if nodes.count == 0 {
Text("no.nodes").font(.title)
} else {
@ -53,13 +53,13 @@ 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.nodeCoordinate != nil && myCoord.coordinate.longitude != LocationHelper.DefaultLocation.longitude && myCoord.coordinate.latitude != LocationHelper.DefaultLocation.latitude {
if lastPostion.nodeCoordinate != nil && myCoord.coordinate.longitude != LocationHelper.DefaultLocation.longitude && myCoord.coordinate.latitude != LocationHelper.DefaultLocation.latitude {
let nodeCoord = CLLocation(latitude: lastPostion.nodeCoordinate!.latitude, longitude: lastPostion.nodeCoordinate!.longitude)
let metersAway = nodeCoord.distance(from: myCoord)
Image(systemName: "lines.measurement.horizontal")
.font(.title3)
.symbolRenderingMode(.hierarchical)
DistanceText(meters: metersAway).font(.subheadline)
}
}
@ -89,7 +89,7 @@ struct NodeList: View {
}
} detail: {
if let node = selection {
NodeDetail(node:node)
NodeDetail(node: node)
} else {
Text("select.node")
}

View file

@ -15,7 +15,7 @@ struct NodeMap: View {
@Environment(\.managedObjectContext) var context
@EnvironmentObject var bleManager: BLEManager
@EnvironmentObject var userSettings: UserSettings
@AppStorage("meshMapCustomTileServer") var customTileServer: String = "" {
didSet {
if customTileServer == "" {
@ -32,18 +32,18 @@ struct NodeMap: View {
@AppStorage("meshMapType") private var meshMapType = "hybridFlyover"
@AppStorage("meshMapUserTrackingMode") private var meshMapUserTrackingMode = 0
@AppStorage("meshMapCenteringMode") private var meshMapCenteringMode = 0
//&& nodePosition != nil
// && nodePosition != nil
@FetchRequest(sortDescriptors: [NSSortDescriptor(key: "time", ascending: true)],
predicate: NSPredicate(format: "time >= %@ && nodePosition != nil", Calendar.current.startOfDay(for: Date()) as NSDate), animation: .none)
private var positions: FetchedResults<PositionEntity>
@FetchRequest(sortDescriptors: [NSSortDescriptor(key: "name", ascending: false)],
predicate: NSPredicate(
format: "expire == nil || expire >= %@", Date() as NSDate
), animation: .none)
private var waypoints: FetchedResults<WaypointEntity>
@State private var mapType: MKMapType = .standard
@State private var userTrackingMode: MKUserTrackingMode = .none
@State private var mapCenteringMode: CenteringMode = .allAnnotations
@ -56,12 +56,12 @@ struct NodeMap: View {
canReplaceMapContent: true
)
@State private var overlays: [MapViewSwiftUI.Overlay] = []
var body: some View {
NavigationStack {
ZStack {
MapViewSwiftUI(onLongPress: { coord in
waypointCoordinate = coord
editingWaypoint = 0
@ -98,11 +98,11 @@ struct NodeMap: View {
}
.ignoresSafeArea(.all, edges: [.top, .leading, .trailing])
.frame(maxHeight: .infinity)
.sheet(isPresented: $presentingWaypointForm ) {//, onDismiss: didDismissSheet) {
.sheet(isPresented: $presentingWaypointForm ) {// , onDismiss: didDismissSheet) {
WaypointFormView(coordinate: waypointCoordinate, waypointId: editingWaypoint)
.presentationDetents([.medium, .large])
.presentationDragIndicator(.automatic)
}
}
.navigationBarItems(leading:
@ -123,27 +123,21 @@ struct NodeMap: View {
switch meshMapType {
case "standard":
mapType = .standard
break
case "mutedStandard":
mapType = .mutedStandard
break
case "hybrid":
mapType = .hybrid
break
case "hybridFlyover":
mapType = .hybridFlyover
break
case "satellite":
mapType = .satellite
break
case "satelliteFlyover":
mapType = .satelliteFlyover
break
default:
mapType = .hybridFlyover
}
})
.onDisappear (perform: {
.onDisappear(perform: {
UIApplication.shared.isIdleTimerDisabled = false
})
}

View file

@ -7,25 +7,25 @@
import SwiftUI
struct PositionLog: View {
@Environment(\.managedObjectContext) var context
@EnvironmentObject var bleManager: BLEManager
@State var isExporting = false
@State var exportString = ""
var node: NodeInfoEntity
@State private var isPresentingClearLogConfirm = false
var body: some View {
NavigationStack {
let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMddjmma", options: 0, locale: Locale.current)
let dateFormatString = (localeDateFormat ?? "MM/dd/YY j:mma").replacingOccurrences(of: ",", with: "")
if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac {
//Add a table for mac and ipad
// Add a table for mac and ipad
Table(node.positions!.reversed() as! [PositionEntity]) {
TableColumn("SeqNo") { position in
Text(String(position.seqNo))
@ -55,9 +55,9 @@ struct PositionLog: View {
Text(position.time?.formattedDate(format: dateFormatString) ?? NSLocalizedString("unknown.age", comment: ""))
}
}
} else {
ScrollView {
// Use a grid on iOS as a table only shows a single column
let columns = [
@ -68,9 +68,9 @@ struct PositionLog: View {
GridItem(spacing: 0)
]
LazyVGrid(columns: columns, alignment: .leading, spacing: 1) {
GridRow {
Text("Latitude")
.font(.caption2)
.fontWeight(.bold)
@ -125,20 +125,20 @@ struct PositionLog: View {
Button("Delete all positions?", role: .destructive) {
if clearPositions(destNum: node.num, context: context) {
print("Successfully Cleared Position Log")
} else {
print("Clear Position Log Failed")
}
}
}
Button {
exportString = PositionToCsvFile(positions: node.positions!.array as! [PositionEntity])
exportString = positionToCsvFile(positions: node.positions!.array as? [PositionEntity] ?? [])
isExporting = true
} label: {
Label("save", systemImage: "square.and.arrow.down")
}
.buttonStyle(.bordered)
@ -154,12 +154,12 @@ struct PositionLog: View {
onCompletion: { result in
if case .success = result {
print("Position log download succeeded.")
self.isExporting = false
} else {
print("Position log download failed: \(result).")
}
}

View file

@ -8,18 +8,18 @@ import SwiftUI
import StoreKit
struct AboutMeshtastic: View {
let locale = Locale.current
var body: some View {
VStack{
VStack {
List {
Section(header: Text("What is Meshtastic?")) {
Text("An open source, off-grid, decentralized, mesh network built to run on affordable, low-power devices.")
.font(.title3)
}
Section(header: Text("Apple Apps")) {
Button("Review the app") {

View file

@ -14,38 +14,38 @@ import MapKit
import CoreLocation
struct AdminMessageList: View {
@Environment(\.managedObjectContext) var context
@EnvironmentObject var bleManager: BLEManager
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 {
ForEach ( user!.adminMessageList.reversed() ) { am in
VStack (alignment: .leading) {
ForEach( user!.adminMessageList.reversed() ) { am in
VStack(alignment: .leading) {
Text("\(am.adminDescription ?? NSLocalizedString("unknown", comment: "Unknown"))")
.font(.caption)
Text("Sent \(Date(timeIntervalSince1970: TimeInterval(am.messageTimestamp)).formattedDate(format: dateFormatString))")
.foregroundColor(.gray)
.font(.caption2)
HStack (spacing: 0) {
HStack(spacing: 0) {
let ackErrorVal = RoutingError(rawValue: Int(am.ackError))
if am.ackTimestamp > 0 {
Text(ackErrorVal?.display ?? "Empty Ack Error")
.foregroundColor(am.receivedACK ? .gray : .red)
.font(.caption2)
}
if am.receivedACK && am.ackTimestamp > 0 {
Text(" \(Date(timeIntervalSince1970: TimeInterval(am.ackTimestamp)).formattedDate(format: "h:mm:ss a"))")
.foregroundColor(.gray)

View file

@ -9,7 +9,7 @@ struct AppSettings: View {
@Environment(\.managedObjectContext) var context
@EnvironmentObject var bleManager: BLEManager
@EnvironmentObject var userSettings: UserSettings
@State private var isPresentingCoreDataResetConfirm = false
@State private var preferredDeviceConnected = false
@ -28,32 +28,32 @@ struct AppSettings: View {
.listRowSeparator(.visible)
}
Section(header: Text("options")) {
Picker("keyboard.type", selection: $userSettings.keyboardType) {
ForEach(KeyboardType.allCases) { kb in
Text(kb.description)
}
}
.pickerStyle(DefaultPickerStyle())
}
Section(header: Text("phone.gps")) {
Toggle(isOn: $userSettings.provideLocation) {
Label("provide.location", systemImage: "location.circle.fill")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
if userSettings.provideLocation {
Picker("update.interval", selection: $userSettings.provideLocationInterval) {
ForEach(LocationUpdateInterval.allCases) { lu in
Text(lu.description)
}
}
.pickerStyle(DefaultPickerStyle())
Text("phone.gps.interval.description")
.font(.caption)
.foregroundColor(.gray)
@ -68,27 +68,27 @@ struct AppSettings: View {
.font(.caption)
.foregroundColor(.gray)
}
Section(header: Text("map options")) {
Picker("map.type", selection: $userSettings.meshMapType) {
ForEach(MeshMapType.allCases) { map in
Text(map.description)
}
}
.pickerStyle(DefaultPickerStyle())
if userSettings.meshMapUserTrackingMode == 0 {
Picker("map.centering", selection: $userSettings.meshMapCenteringMode) {
ForEach(CenteringMode.allCases) { cm in
Text(cm.description)
}
}
.pickerStyle(DefaultPickerStyle())
Toggle(isOn: $userSettings.meshMapRecentering) {
Label("map.recentering", systemImage: "camera.metering.center.weighted")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
@ -126,8 +126,8 @@ struct AppSettings: View {
.onAppear {
self.bleManager.context = context
}
.onChange(of: userSettings.provideLocation) { newProvideLocation in
.onChange(of: userSettings.provideLocation) { _ in
if bleManager.connectedPeripheral != nil {
self.bleManager.sendWantConfig()
}

View file

@ -16,15 +16,14 @@ func generateChannelKey(size: Int) -> String {
}
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
@ -35,14 +34,14 @@ struct Channels: View {
@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: {
Button(action: {
channelIndex = channel.index
channelRole = Int(channel.role)
channelKey = channel.psk?.base64EncodedString() ?? ""
@ -87,7 +86,7 @@ struct Channels: View {
}
}
if node?.myInfo?.channels?.array.count ?? 0 < 8 && node != nil {
Button {
let key = generateChannelKey(size: 32)
channelName = ""
@ -98,7 +97,7 @@ struct Channels: View {
downlink = false
hasChanges = false
isPresentingEditView = true
} label: {
Label("Add Channel", systemImage: "plus.square")
}
@ -107,7 +106,7 @@ struct Channels: View {
.controlSize(.large)
.padding()
.sheet(isPresented: $isPresentingEditView) {
#if targetEnvironment(macCatalyst)
Text("channel")
.font(.largeTitle)
@ -125,7 +124,7 @@ struct Channels: View {
.keyboardType(.alphabet)
.foregroundColor(Color.gray)
.disabled(channelRole == 1 && channelName.count > 0)
.onChange(of: channelName, perform: { value in
.onChange(of: channelName, perform: { _ in
channelName = channelName.replacing(" ", with: "")
let totalBytes = channelName.utf8.count
// Only mess with the value if it is too big
@ -165,23 +164,23 @@ struct Channels: View {
.buttonBorderShape(.capsule)
.controlSize(.small)
}
HStack (alignment: .top) {
HStack(alignment: .top) {
Text("Key")
Spacer()
TextField (
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{
} else {
Text("Disabled").tag(0)
Text("Secondary").tag(2)
}
@ -193,13 +192,13 @@ struct Channels: View {
Toggle("Downlink Enabled", isOn: $downlink)
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
}
//.onSubmit {
//validate(name: channelName)
//}
.onChange(of: channelName) { newName in
// .onSubmit {
// validate(name: channelName)
// }
.onChange(of: channelName) { _ in
hasChanges = true
}
.onChange(of: channelKeySize) { newKeySize in
.onChange(of: channelKeySize) { _ in
if channelKeySize == -1 {
channelKey = "AQ=="
} else {
@ -208,16 +207,16 @@ struct Channels: View {
}
hasChanges = true
}
.onChange(of: channelKey) { newKey in
.onChange(of: channelKey) { _ in
hasChanges = true
}
.onChange(of: channelRole) { newRole in
.onChange(of: channelRole) { _ in
hasChanges = true
}
.onChange(of: uplink) { newUplink in
.onChange(of: uplink) { _ in
hasChanges = true
}
.onChange(of: downlink) { newDownlink in
.onChange(of: downlink) { _ in
hasChanges = true
}
HStack {
@ -231,10 +230,11 @@ struct Channels: View {
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
guard let channelEntity = node!.myInfo!.channels?[Int(channelIndex)] as? ChannelEntity else {
return
}
context.delete(channelEntity)
do {
try context.save()
@ -246,14 +246,14 @@ struct Channels: View {
}
}
}
let adminMessageId = bleManager.saveChannel(channel: channel, fromUser: node!.user!, toUser: node!.user!)
if adminMessageId > 0 {
self.isPresentingEditView = false
channelName = ""
hasChanges = false
bleManager.getChannel(channel: channel, fromUser: node!.user!, toUser: node!.user!)
_ = bleManager.getChannel(channel: channel, fromUser: node!.user!, toUser: node!.user!)
}
} label: {
Label("save", systemImage: "square.and.arrow.down")

View file

@ -8,13 +8,10 @@
import SwiftUI
struct BluetoothConfig: View {
@Environment(\.managedObjectContext) var context
@EnvironmentObject var bleManager: BLEManager
@Environment(\.dismiss) private var goBack
var node: NodeInfoEntity?
@State private var isPresentingSaveConfirm: Bool = false
@State var hasChanges = false
@State var enabled = true
@ -22,43 +19,35 @@ struct BluetoothConfig: View {
@State var fixedPin = "123456"
@State var shortPin = false
var pinLength: Int = 6
let numberFormatter: NumberFormatter = {
let formatter = NumberFormatter()
formatter.numberStyle = .none
return formatter
}()
var body: some View {
Form {
Section(header: Text("options")) {
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
.onChange(of: fixedPin, perform: { _ 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
// 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 {
@ -80,7 +69,6 @@ struct BluetoothConfig: View {
}
}
.disabled(self.bleManager.connectedPeripheral == nil || node?.bluetoothConfig == nil)
Button {
isPresentingSaveConfirm = true
} label: {
@ -128,12 +116,11 @@ struct BluetoothConfig: View {
self.mode = Int(node?.bluetoothConfig?.mode ?? 0)
self.fixedPin = String(node?.bluetoothConfig?.fixedPin ?? 123456)
self.hasChanges = false
// Need to request a BluetoothConfig from the remote node before allowing changes
if bleManager.connectedPeripheral != nil && node?.bluetoothConfig == nil {
print("empty bluetooth config")
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context)
if connectedNode != nil && connectedNode!.num > 0 {
if node != nil && connectedNode != nil {
_ = bleManager.requestBluetoothConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0)
}
}

View file

@ -7,32 +7,33 @@
import SwiftUI
struct DeviceConfig: View {
@Environment(\.managedObjectContext) var context
@EnvironmentObject var bleManager: BLEManager
@Environment(\.dismiss) private var goBack
var node: NodeInfoEntity?
@State private var isPresentingNodeDBResetConfirm = false
@State private var isPresentingFactoryResetConfirm = false
@State private var isPresentingSaveConfirm = false
@State var hasChanges = false
@State var deviceRole = 0
@State var buzzerGPIO = 0
@State var buttonGPIO = 0
@State var serialEnabled = true
@State var debugLogEnabled = false
@State var rebroadcastMode = 0
var body: some View {
VStack {
Form {
Section(header: Text("options")) {
Picker("Device Role", selection: $deviceRole ) {
ForEach(DeviceRoles.allCases) { dr in
Text(dr.name)
@ -43,25 +44,36 @@ struct DeviceConfig: View {
Text(DeviceRoles(rawValue: deviceRole)?.description ?? "")
.foregroundColor(.gray)
.font(.caption)
Picker("Rebroadcast Mode", selection: $rebroadcastMode ) {
ForEach(RebroadcastModes.allCases) { rm in
Text(rm.name)
}
}
.pickerStyle(DefaultPickerStyle())
.padding(.top, 10)
Text(RebroadcastModes(rawValue: rebroadcastMode)?.description ?? "")
.foregroundColor(.gray)
.font(.caption)
}
Section(header: Text("Debug")) {
Toggle(isOn: $serialEnabled) {
Label("Serial Console", systemImage: "terminal")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
Toggle(isOn: $debugLogEnabled) {
Label("Debug Log", systemImage: "ant.fill")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
}
Section(header: Text("GPIO")) {
Picker("Button GPIO", selection: $buttonGPIO) {
ForEach(0..<40) {
if $0 == 0 {
@ -83,14 +95,14 @@ struct DeviceConfig: View {
}
.pickerStyle(DefaultPickerStyle())
}
}
.disabled(self.bleManager.connectedPeripheral == nil || node?.deviceConfig == nil)
// Only show these buttons for the BLE connected node
if bleManager.connectedPeripheral != nil && node?.num ?? -1 == bleManager.connectedPeripheral.num {
HStack {
Button("Reset NodeDB", role: .destructive) {
isPresentingNodeDBResetConfirm = true
}
@ -105,7 +117,7 @@ struct DeviceConfig: View {
titleVisibility: .visible
) {
Button("Erase all device and app data?", role: .destructive) {
if bleManager.sendNodeDBReset(fromUser: node!.user!, toUser: node!.user!) {
bleManager.disconnectPeripheral()
clearCoreDataDatabase(context: context)
@ -128,23 +140,23 @@ struct DeviceConfig: View {
titleVisibility: .visible
) {
Button("Factory reset your device and app? ", role: .destructive) {
if bleManager.sendFactoryReset(fromUser: node!.user!, toUser: node!.user!) {
bleManager.disconnectPeripheral()
clearCoreDataDatabase(context: context)
} else {
print("Factory Reset Failed")
}
}
}
}
}
HStack {
Button {
isPresentingSaveConfirm = true
} label: {
Label("save", systemImage: "square.and.arrow.down")
}
@ -154,7 +166,7 @@ struct DeviceConfig: View {
.controlSize(.large)
.padding()
.confirmationDialog(
"are.you.sure",
isPresented: $isPresentingSaveConfirm,
titleVisibility: .visible
@ -170,7 +182,7 @@ struct DeviceConfig: View {
dc.debugLogEnabled = debugLogEnabled
dc.buttonGpio = UInt32(buttonGPIO)
dc.buzzerGpio = UInt32(buzzerGPIO)
let adminMessageId = bleManager.saveDeviceConfig(config: dc, fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: node?.myInfo?.adminIndex ?? 0)
if adminMessageId > 0 {
// Should show a saved successfully alert once I know that to be true
@ -200,48 +212,48 @@ struct DeviceConfig: View {
self.buttonGPIO = Int(node?.deviceConfig?.buttonGpio ?? 0)
self.buzzerGPIO = Int(node?.deviceConfig?.buzzerGpio ?? 0)
self.hasChanges = false
// Need to request a LoRaConfig from the remote node before allowing changes
if bleManager.connectedPeripheral != nil && node?.deviceConfig == nil {
print("empty device config")
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1, context: context)
if connectedNode != nil && connectedNode!.num > 0 {
if node != nil && connectedNode != nil {
_ = bleManager.requestDeviceConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0)
}
}
}
.onChange(of: deviceRole) { newRole in
if node != nil && node!.deviceConfig != nil {
if newRole != node!.deviceConfig!.role { hasChanges = true }
}
}
.onChange(of: serialEnabled) { newSerial in
if node != nil && node!.deviceConfig != nil {
if newSerial != node!.deviceConfig!.serialEnabled { hasChanges = true }
}
}
.onChange(of: debugLogEnabled) { newDebugLog in
if node != nil && node!.deviceConfig != nil {
if newDebugLog != node!.deviceConfig!.debugLogEnabled { hasChanges = true }
}
}
.onChange(of: buttonGPIO) { newButtonGPIO in
if node != nil && node!.deviceConfig != nil {
if newButtonGPIO != node!.deviceConfig!.buttonGpio { hasChanges = true }
}
}
.onChange(of: buzzerGPIO) { newBuzzerGPIO in
if node != nil && node!.deviceConfig != nil {
if newBuzzerGPIO != node!.deviceConfig!.buttonGpio { hasChanges = true }
}
}

View file

@ -8,13 +8,13 @@
import SwiftUI
struct DisplayConfig: View {
@Environment(\.managedObjectContext) var context
@EnvironmentObject var bleManager: BLEManager
@Environment(\.dismiss) private var goBack
var node: NodeInfoEntity?
@State private var isPresentingSaveConfirm: Bool = false
@State var hasChanges = false
@ -25,12 +25,12 @@ struct DisplayConfig: View {
@State var flipScreen = false
@State var oledType = 0
@State var displayMode = 0
var body: some View {
Form {
Section(header: Text("Device Screen")) {
Picker("Display Mode", selection: $displayMode ) {
ForEach(DisplayModes.allCases) { dm in
Text(dm.description)
@ -39,7 +39,7 @@ struct DisplayConfig: View {
.pickerStyle(DefaultPickerStyle())
Text("Override automatic OLED screen detection.")
.font(.caption)
Toggle(isOn: $compassNorthTop) {
Label("Always point north", systemImage: "location.north.circle")
@ -47,7 +47,7 @@ struct DisplayConfig: View {
.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")
@ -55,7 +55,7 @@ struct DisplayConfig: View {
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
Text("Flip screen vertically")
.font(.caption)
Picker("OLED Type", selection: $oledType ) {
ForEach(OledTypes.allCases) { ot in
Text(ot.description)
@ -64,7 +64,7 @@ struct DisplayConfig: View {
.pickerStyle(DefaultPickerStyle())
Text("Override automatic OLED screen detection.")
.font(.caption)
}
Section(header: Text("Timing & Format")) {
Picker("Screen on for", selection: $screenOnSeconds ) {
@ -75,7 +75,7 @@ struct DisplayConfig: View {
.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)
@ -84,27 +84,27 @@ struct DisplayConfig: View {
.pickerStyle(DefaultPickerStyle())
Text("Automatically toggles to the next page on the screen like a carousel, based the specified interval.")
.font(.caption)
Picker("GPS Format", selection: $gpsFormat ) {
ForEach(GpsFormats.allCases) { lu in
Text(lu.description)
}
}
.pickerStyle(DefaultPickerStyle())
Text("The format used to display GPS coordinates on the device screen.")
.font(.caption)
.listRowSeparator(.visible)
}
}
.disabled(self.bleManager.connectedPeripheral == nil || node?.displayConfig == nil)
Button {
isPresentingSaveConfirm = true
} label: {
Label("save", systemImage: "square.and.arrow.down")
}
.disabled(bleManager.connectedPeripheral == nil || !hasChanges)
@ -129,10 +129,10 @@ struct DisplayConfig: View {
dc.flipScreen = flipScreen
dc.oled = OledTypes(rawValue: oledType)!.protoEnumValue()
dc.displaymode = DisplayModes(rawValue: displayMode)!.protoEnumValue()
let adminMessageId = bleManager.saveDisplayConfig(config: dc, fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: node?.myInfo?.adminIndex ?? 0)
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
@ -159,12 +159,12 @@ struct DisplayConfig: View {
self.oledType = Int(node?.displayConfig?.oledType ?? 0)
self.displayMode = Int(node?.displayConfig?.displayMode ?? 0)
self.hasChanges = false
// Need to request a LoRaConfig from the remote node before allowing changes
if bleManager.connectedPeripheral != nil && node?.displayConfig == nil {
print("empty display config")
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context)
if connectedNode != nil && connectedNode!.num > 0 {
if node != nil && connectedNode != nil {
_ = bleManager.requestDisplayConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0)
}
}

View file

@ -6,62 +6,148 @@
//
import SwiftUI
import CoreData
struct LoRaConfig: View {
enum Field: Hashable {
case channelNum
}
let formatter: NumberFormatter = {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.groupingSeparator = ""
return formatter
}()
@Environment(\.managedObjectContext) var context
@EnvironmentObject var bleManager: BLEManager
@Environment(\.dismiss) private var goBack
@FocusState var focusedField: Field?
var node: NodeInfoEntity?
@State var isPresentingSaveConfirm = false
@State var hasChanges = false
@State var region = 0
@State var modemPreset = 0
@State var hopLimit = 0
@State var txPower = 0
@State var modemPreset = 0
@State var hopLimit = 0
@State var txPower = 0
@State var txEnabled = true
@State var usePreset = true
@State var channelNum = 0
@State var bandwidth = 0
@State var spreadFactor = 0
@State var codingRate = 0
var body: some View {
VStack {
Form {
Section(header: Text("Region")) {
Section(header: Text("Options")) {
Picker("Region", selection: $region ) {
ForEach(RegionCodes.allCases) { r in
Text(r.description)
}
}
.pickerStyle(DefaultPickerStyle())
.fixedSize()
Text("The region where you will be using your radios.")
.font(.caption)
}
Section(header: Text("Modem")) {
Picker("Presets", selection: $modemPreset ) {
ForEach(ModemPresets.allCases) { m in
Text(m.description)
}
Toggle(isOn: $usePreset) {
Label("Use Preset", systemImage: "list.bullet.rectangle")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
if usePreset {
Picker("Presets", selection: $modemPreset ) {
ForEach(ModemPresets.allCases) { m in
Text(m.description)
}
}
.pickerStyle(DefaultPickerStyle())
.fixedSize()
Text("Available modem presets, default is Long Fast.")
.font(.caption)
}
.pickerStyle(DefaultPickerStyle())
Text("Available modem presets, default is Long Fast.")
.font(.caption)
}
Section(header: Text("Mesh Options")) {
Picker("Number of hops", selection: $hopLimit) {
Text("Please Select")
.tag(0)
ForEach(HopValues.allCases) { hop in
Text(hop.description)
Section(header: Text("Advanced")) {
Toggle(isOn: $txEnabled) {
Label("Transmit Enabled", systemImage: "waveform.path")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
if !usePreset {
HStack {
Picker("Bandwidth", selection: $spreadFactor) {
Text("31 kHz")
.tag(31)
Text("62 kHz")
.tag(62)
Text("125 kHz")
.tag(125)
Text("250 kHz")
.tag(0)
Text("500 kHz")
.tag(500)
}
}
HStack {
Picker("Spread Factor", selection: $spreadFactor) {
ForEach(7..<13) {
Text("\($0)")
.tag($0 == 12 ? 0 : $0)
}
}
}
HStack {
Picker("Coding Rate", selection: $codingRate) {
ForEach(5..<9) {
Text("\($0)")
.tag($0 == 8 ? 0 : $0)
}
}
}
}
Picker("Number of hops", selection: $codingRate) {
ForEach(1..<8) {
Text("\($0)")
.tag($0 == 3 ? 0 : $0)
}
}
.pickerStyle(DefaultPickerStyle())
Text("Sets the maximum number of hops, default is 3. Increasing hops also increases air time utilization and should be used carefully.")
.font(.caption)
HStack {
Text("LoRa Channel Number")
.fixedSize()
TextField("Channel Number", value: $channelNum, formatter: formatter)
.multilineTextAlignment(.trailing)
.toolbar {
ToolbarItemGroup(placement: .keyboard) {
Button("dismiss.keyboard") {
focusedField = nil
}
.font(.subheadline)
}
}
.keyboardType(.decimalPad)
.scrollDismissesKeyboard(.immediately)
.focused($focusedField, equals: .channelNum)
}
Text("A hash of the primary channel's name sets the LoRa channel number, this determines the actual frequency you are transmitting on in the band. To ensure devices with different primary channel names transmit on the same frequency, you must explicitly set the LoRa channel number.")
.font(.caption)
}
}
.disabled(self.bleManager.connectedPeripheral == nil || node?.loRaConfig == nil)
Button {
isPresentingSaveConfirm = true
} label: {
@ -86,8 +172,12 @@ struct LoRaConfig: View {
lc.hopLimit = UInt32(hopLimit)
lc.region = RegionCodes(rawValue: region)!.protoEnumValue()
lc.modemPreset = ModemPresets(rawValue: modemPreset)!.protoEnumValue()
lc.usePreset = true
lc.txEnabled = true
lc.usePreset = usePreset
lc.txEnabled = txEnabled
lc.channelNum = UInt32(channelNum)
lc.bandwidth = UInt32(bandwidth)
lc.codingRate = UInt32(codingRate)
lc.spreadFactor = UInt32(spreadFactor)
let adminMessageId = bleManager.saveLoRaConfig(config: lc, fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: node?.myInfo?.adminIndex ?? 0)
if adminMessageId > 0 {
// Should show a saved successfully alert once I know that to be true
@ -103,11 +193,11 @@ struct LoRaConfig: View {
}
.navigationTitle("lora.config")
.navigationBarItems(trailing:
ZStack {
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????")
ZStack {
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????")
})
.onAppear {
self.bleManager.context = context
self.hopLimit = Int(node?.loRaConfig?.hopLimit ?? 3)
self.region = Int(node?.loRaConfig?.regionCode ?? 0)
@ -115,13 +205,19 @@ struct LoRaConfig: View {
self.modemPreset = Int(node?.loRaConfig?.modemPreset ?? 0)
self.txEnabled = node?.loRaConfig?.txEnabled ?? true
self.txPower = Int(node?.loRaConfig?.txPower ?? 0)
self.channelNum = Int(node?.loRaConfig?.channelNum ?? 0)
self.bandwidth = Int(node?.loRaConfig?.bandwidth ?? 0)
self.codingRate = Int(node?.loRaConfig?.codingRate ?? 0)
self.spreadFactor = Int(node?.loRaConfig?.spreadFactor ?? 0)
print("Spreadum: \(self.spreadFactor)")
self.hasChanges = false
// Need to request a LoRaConfig from the remote node before allowing changes
if bleManager.connectedPeripheral != nil && node?.loRaConfig == nil {
print("empty lora config")
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context)
if connectedNode != nil && connectedNode!.num > 0 {
if node != nil && connectedNode != nil {
_ = bleManager.requestLoRaConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0)
}
}
@ -131,6 +227,11 @@ struct LoRaConfig: View {
if newRegion != node!.loRaConfig!.regionCode { hasChanges = true }
}
}
.onChange(of: usePreset) { newUsePreset in
if node != nil && node!.loRaConfig != nil {
if newUsePreset != node!.loRaConfig!.usePreset { hasChanges = true }
}
}
.onChange(of: modemPreset) { newModemPreset in
if node != nil && node!.loRaConfig != nil {
if newModemPreset != node!.loRaConfig!.modemPreset { hasChanges = true }
@ -141,5 +242,25 @@ struct LoRaConfig: View {
if newHopLimit != node!.loRaConfig!.hopLimit { hasChanges = true }
}
}
.onChange(of: channelNum) { newChannelNum in
if node != nil && node!.loRaConfig != nil {
if newChannelNum != node!.loRaConfig!.channelNum { hasChanges = true }
}
}
.onChange(of: bandwidth) { newBandwidth in
if node != nil && node!.loRaConfig != nil {
if newBandwidth != node!.loRaConfig!.bandwidth { hasChanges = true }
}
}
.onChange(of: codingRate) { newCodingRate in
if node != nil && node!.loRaConfig != nil {
if newCodingRate != node!.loRaConfig!.codingRate { hasChanges = true }
}
}
.onChange(of: spreadFactor) { newSpreadFactor in
if node != nil && node!.loRaConfig != nil {
if newSpreadFactor != node!.loRaConfig!.spreadFactor { hasChanges = true }
}
}
}
}

View file

@ -7,18 +7,14 @@
import SwiftUI
struct CannedMessagesConfig: View {
@Environment(\.managedObjectContext) var context
@EnvironmentObject var bleManager: BLEManager
@Environment(\.dismiss) private var goBack
var node: NodeInfoEntity?
@State private var isPresentingSaveConfirm: Bool = false
@State var hasChanges = false
@State var hasMessagesChanges = false
@State var configPreset = 0
@State var enabled = false
/// CannedMessageModule will sends a bell character with the messages.
@State var sendBell: Bool = false
@ -38,29 +34,22 @@ struct CannedMessagesConfig: View {
@State var inputbrokerEventCcw = 0
/// Generate input event on Press of this kind.
@State var inputbrokerEventPress = 0
@State var messages = ""
var body: some View {
VStack {
Form {
Section(header: Text("options")) {
Toggle(isOn: $enabled) {
Label("enabled", systemImage: "list.bullet.rectangle.fill")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
Toggle(isOn: $sendBell) {
Label("Send Bell", systemImage: "bell")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
Picker("Configuration Presets", selection: $configPreset ) {
ForEach(ConfigPresets.allCases) { cp in
Text(cp.description)
@ -69,26 +58,21 @@ struct CannedMessagesConfig: View {
.pickerStyle(DefaultPickerStyle())
.padding(.top, 10)
.padding(.bottom, 10)
}
HStack {
Label("Messages", systemImage: "message.fill")
TextField("Messages separate with |", text: $messages, axis: .vertical)
.foregroundColor(.gray)
.autocapitalization(.none)
.disableAutocorrection(true)
.onChange(of: messages, perform: { value in
.onChange(of: messages, perform: { _ in
let totalBytes = messages.utf8.count
// Only mess with the value if it is too big
if totalBytes > 198 {
let firstNBytes = Data(messages.utf8.prefix(198))
if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) {
// Set the shortName back to the last place where it was the right size
messages = maxBytesString
}
@ -98,35 +82,27 @@ struct CannedMessagesConfig: View {
.foregroundColor(.gray)
}
.keyboardType(.default)
Section(header: Text("Control Type")) {
Toggle(isOn: $rotary1Enabled) {
Label("Rotary 1", systemImage: "dial.min")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
.disabled(updown1Enabled)
Toggle(isOn: $updown1Enabled) {
Label("Up Down 1", systemImage: "arrow.up.arrow.down")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
.disabled(rotary1Enabled)
}
.disabled(configPreset > 0)
Section(header: Text("Inputs")) {
Picker("Pin A", selection: $inputbrokerPinA) {
ForEach(0..<40) {
if $0 == 0 {
Text("unset")
} else {
Text("Pin \($0)")
}
}
@ -134,14 +110,11 @@ struct CannedMessagesConfig: View {
.pickerStyle(DefaultPickerStyle())
Text("GPIO pin for rotary encoder A port.")
.font(.caption)
Picker("Pin B", selection: $inputbrokerPinB) {
ForEach(0..<40) {
if $0 == 0 {
Text("unset")
} else {
Text("Pin \($0)")
}
}
@ -149,14 +122,11 @@ struct CannedMessagesConfig: View {
.pickerStyle(DefaultPickerStyle())
Text("GPIO pin for rotary encoder B port.")
.font(.caption)
Picker("Press Pin", selection: $inputbrokerPinPress) {
ForEach(0..<40) {
if $0 == 0 {
Text("unset")
} else {
Text("Pin \($0)")
}
}
@ -164,12 +134,9 @@ struct CannedMessagesConfig: View {
.pickerStyle(DefaultPickerStyle())
Text("GPIO pin for rotary encoder Press port.")
.font(.caption)
}
.disabled(configPreset > 0)
Section(header: Text("Key Mapping")) {
Picker("Clockwise Rotary Event", selection: $inputbrokerEventCw ) {
ForEach(InputEventChars.allCases) { iec in
Text(iec.description)
@ -178,7 +145,6 @@ struct CannedMessagesConfig: View {
.pickerStyle(DefaultPickerStyle())
.padding(.top, 10)
.padding(.bottom, 10)
Picker("Counter Clockwise Rotary Event", selection: $inputbrokerEventCcw ) {
ForEach(InputEventChars.allCases) { iec in
Text(iec.description)
@ -187,7 +153,6 @@ struct CannedMessagesConfig: View {
.pickerStyle(DefaultPickerStyle())
.padding(.top, 10)
.padding(.bottom, 10)
Picker("Encoder Press Event", selection: $inputbrokerEventPress ) {
ForEach(InputEventChars.allCases) { iec in
Text(iec.description)
@ -201,10 +166,8 @@ struct CannedMessagesConfig: View {
}
.scrollDismissesKeyboard(.immediately)
.disabled(self.bleManager.connectedPeripheral == nil || node?.cannedMessageConfig == nil)
Button {
isPresentingSaveConfirm = true
} label: {
Label("save", systemImage: "square.and.arrow.down")
}
@ -287,20 +250,20 @@ struct CannedMessagesConfig: View {
self.messages = node?.cannedMessageConfig?.messages ?? ""
self.hasChanges = false
self.hasMessagesChanges = false
// Need to request a CannedMessagesModuleConfig from the remote node before allowing changes
if bleManager.connectedPeripheral != nil && node?.cannedMessageConfig == nil {
print("empty canned messages module config")
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context)
if connectedNode != nil && connectedNode!.num > 0 {
if node != nil && connectedNode != nil {
_ = bleManager.requestCannedMessagesModuleConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0)
}
}
}
.onChange(of: configPreset) { newPreset in
if newPreset == 1 {
// RAK Rotary Encoder
updown1Enabled = true
rotary1Enabled = false
@ -310,9 +273,9 @@ struct CannedMessagesConfig: View {
inputbrokerEventCw = InputEventChars.down.rawValue
inputbrokerEventCcw = InputEventChars.up.rawValue
inputbrokerEventPress = InputEventChars.select.rawValue
} else if newPreset == 2 {
// CardKB / RAK Keypad
updown1Enabled = false
rotary1Enabled = false
@ -323,7 +286,7 @@ struct CannedMessagesConfig: View {
inputbrokerEventCcw = InputEventChars.none.rawValue
inputbrokerEventPress = InputEventChars.none.rawValue
}
hasChanges = true
}
.onChange(of: enabled) { newEnabled in

View file

@ -7,13 +7,13 @@
import SwiftUI
struct ExternalNotificationConfig: View {
@Environment(\.managedObjectContext) var context
@EnvironmentObject var bleManager: BLEManager
@Environment(\.dismiss) private var goBack
var node: NodeInfoEntity?
@State private var isPresentingSaveConfirm: Bool = false
@State var hasChanges = false
@State var enabled = false
@ -30,9 +30,9 @@ struct ExternalNotificationConfig: View {
@State var outputVibra = 0
@State var outputMilliseconds = 0
@State var nagTimeout = 0
var body: some View {
Form {
Section(header: Text("options")) {
Toggle(isOn: $enabled) {
@ -58,8 +58,7 @@ struct ExternalNotificationConfig: View {
Section(header: Text("Primary GPIO")
.font(.caption)
.foregroundColor(.gray)
.textCase(.uppercase))
{
.textCase(.uppercase)) {
Toggle(isOn: $active) {
Label("Active", systemImage: "togglepower")
}
@ -93,12 +92,11 @@ struct ExternalNotificationConfig: View {
Text("Specifies how long the monitored GPIO should output.")
.font(.caption)
}
Section(header: Text("Optional GPIO")
.font(.caption)
.foregroundColor(.gray)
.textCase(.uppercase))
{
.textCase(.uppercase)) {
Toggle(isOn: $alertBellBuzzer) {
Label("Alert GPIO buzzer when receiving a bell", systemImage: "bell")
}
@ -174,7 +172,7 @@ struct ExternalNotificationConfig: View {
enc.outputMs = UInt32(outputMilliseconds)
enc.usePwm = usePWM
let adminMessageId = bleManager.saveExternalNotificationModuleConfig(config: enc, fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0)
if adminMessageId > 0{
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
@ -208,12 +206,12 @@ struct ExternalNotificationConfig: View {
self.nagTimeout = Int(node?.externalNotificationConfig?.nagTimeout ?? 0)
self.usePWM = node?.externalNotificationConfig?.usePWM ?? false
self.hasChanges = false
// Need to request a TelemetryModuleConfig from the remote node before allowing changes
if bleManager.connectedPeripheral != nil && node?.externalNotificationConfig == nil {
print("empty external notification module config")
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context)
if connectedNode != nil && connectedNode!.num > 0 {
if node != nil && connectedNode != nil {
_ = bleManager.requestExternalNotificationModuleConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0)
}
}

View file

@ -7,7 +7,7 @@
import SwiftUI
struct MQTTConfig: View {
@Environment(\.managedObjectContext) var context
@EnvironmentObject var bleManager: BLEManager
@Environment(\.dismiss) private var goBack
@ -20,9 +20,9 @@ struct MQTTConfig: View {
@State var password = ""
@State var encryptionEnabled = false
@State var jsonEnabled = false
var body: some View {
Form {
Section(header: Text("options")) {
Toggle(isOn: $enabled) {
@ -30,7 +30,7 @@ struct MQTTConfig: View {
Label("enabled", systemImage: "dot.radiowaves.right")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
Toggle(isOn: $encryptionEnabled) {
Label("Encryption Enabled", systemImage: "lock.icloud")
@ -50,7 +50,7 @@ struct MQTTConfig: View {
.foregroundColor(.gray)
.autocapitalization(.none)
.disableAutocorrection(true)
.onChange(of: address, perform: { value in
.onChange(of: address, perform: { _ in
let totalBytes = address.utf8.count
// Only mess with the value if it is too big
if totalBytes > 30 {
@ -66,24 +66,24 @@ struct MQTTConfig: View {
.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
.onChange(of: username, perform: { _ 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
}
@ -100,17 +100,17 @@ struct MQTTConfig: View {
.foregroundColor(.gray)
.autocapitalization(.none)
.disableAutocorrection(true)
.onChange(of: password, perform: { value in
.onChange(of: password, perform: { _ in
let totalBytes = password.utf8.count
// Only mess with the value if it is too big
if totalBytes > 62 {
let firstNBytes = Data(password.utf8.prefix(62))
if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) {
// Set the shortName back to the last place where it was the right size
password = maxBytesString
}
@ -127,7 +127,7 @@ struct MQTTConfig: View {
}
.scrollDismissesKeyboard(.interactively)
.disabled(self.bleManager.connectedPeripheral == nil || node?.mqttConfig == nil)
Button {
isPresentingSaveConfirm = true
} label: {
@ -182,12 +182,12 @@ struct MQTTConfig: View {
self.encryptionEnabled = (node?.mqttConfig?.encryptionEnabled ?? false)
self.jsonEnabled = (node?.mqttConfig?.jsonEnabled ?? false)
self.hasChanges = false
// Need to request a TelemetryModuleConfig from the remote node before allowing changes
if bleManager.connectedPeripheral != nil && node?.telemetryConfig == nil {
print("empty mqtt module config")
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context)
if connectedNode != nil && connectedNode!.num > 0 {
if node != nil && connectedNode != nil {
_ = bleManager.requestMqttModuleConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0)
}
}

View file

@ -7,19 +7,19 @@
import SwiftUI
struct RangeTestConfig: View {
@Environment(\.managedObjectContext) var context
@EnvironmentObject var bleManager: BLEManager
@Environment(\.dismiss) private var goBack
var node: NodeInfoEntity?
@State private var isPresentingSaveConfirm: Bool = false
@State var hasChanges = false
@State var enabled = false
@State var sender = 0
@State var save = false
var body: some View {
VStack {
Form {
@ -65,7 +65,7 @@ struct RangeTestConfig: View {
let nodeName = node?.user?.longName ?? NSLocalizedString("unknown", comment: "Unknown")
let buttonText = String.localizedStringWithFormat(NSLocalizedString("save.config %@", comment: "Save Config for %@"), nodeName)
Button(buttonText) {
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context)
if connectedNode != nil {
var rtc = ModuleConfig.RangeTestConfig()
@ -96,12 +96,12 @@ struct RangeTestConfig: View {
self.save = node?.rangeTestConfig?.save ?? false
self.sender = Int(node?.rangeTestConfig?.sender ?? 0)
self.hasChanges = false
// Need to request a RangeTestModule Config from the remote node before allowing changes
if bleManager.connectedPeripheral != nil && node?.rangeTestConfig == nil {
print("empty range test module config")
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context)
if connectedNode != nil && connectedNode!.num > 0 {
if node != nil && connectedNode != nil {
_ = bleManager.requestRangeTestModuleConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0)
}
}

View file

@ -7,16 +7,16 @@
import SwiftUI
struct SerialConfig: View {
@Environment(\.managedObjectContext) var context
@EnvironmentObject var bleManager: BLEManager
@Environment(\.dismiss) private var goBack
var node: NodeInfoEntity?
@State private var isPresentingSaveConfirm: Bool = false
@State var hasChanges = false
@State var enabled = false
@State var echo = false
@State var rxd = 0
@ -24,21 +24,21 @@ struct SerialConfig: View {
@State var baudRate = 0
@State var timeout = 0
@State var mode = 0
var body: some View {
VStack {
Form {
Section(header: Text("options")) {
Toggle(isOn: $enabled) {
Label("enabled", systemImage: "terminal")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
Toggle(isOn: $echo) {
Label("echo", systemImage: "repeat")
@ -46,14 +46,14 @@ struct SerialConfig: View {
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
Text("If set, any packets you send will be echoed back to your device.")
.font(.caption)
Picker("Baud", selection: $baudRate ) {
ForEach(SerialBaudRates.allCases) { sbr in
Text(sbr.description)
}
}
.pickerStyle(DefaultPickerStyle())
Picker("timeout", selection: $timeout ) {
ForEach(SerialTimeoutIntervals.allCases) { sti in
Text(sti.description)
@ -62,7 +62,7 @@ struct SerialConfig: View {
.pickerStyle(DefaultPickerStyle())
Text("The amount of time to wait before we consider your packet as done.")
.font(.caption)
Picker("mode", selection: $mode ) {
ForEach(SerialModeTypes.allCases) { smt in
Text(smt.description)
@ -71,7 +71,7 @@ struct SerialConfig: View {
.pickerStyle(DefaultPickerStyle())
}
Section(header: Text("GPIO")) {
Picker("Receive data (rxd) GPIO pin", selection: $rxd) {
ForEach(0..<40) {
if $0 == 0 {
@ -98,13 +98,13 @@ struct SerialConfig: View {
}
}
.disabled(self.bleManager.connectedPeripheral == nil || node?.serialConfig == nil)
Button {
isPresentingSaveConfirm = true
} label: {
Label("save", systemImage: "square.and.arrow.down")
}
.disabled(bleManager.connectedPeripheral == nil || !hasChanges)
@ -113,7 +113,7 @@ struct SerialConfig: View {
.controlSize(.large)
.padding()
.confirmationDialog(
"are.you.sure",
isPresented: $isPresentingSaveConfirm,
titleVisibility: .visible
@ -131,9 +131,9 @@ struct SerialConfig: View {
sc.baud = SerialBaudRates(rawValue: baudRate)!.protoEnumValue()
sc.timeout = UInt32(timeout)
sc.mode = SerialModeTypes(rawValue: mode)!.protoEnumValue()
let adminMessageId = bleManager.saveSerialModuleConfig(config: sc, fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0)
if adminMessageId > 0 {
// Should show a saved successfully alert once I know that to be true
// for now just disable the button after a successful save
@ -153,7 +153,7 @@ struct SerialConfig: View {
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????")
})
.onAppear {
self.bleManager.context = context
self.enabled = node?.serialConfig?.enabled ?? false
self.echo = node?.serialConfig?.echo ?? false
@ -163,63 +163,63 @@ struct SerialConfig: View {
self.timeout = Int(node?.serialConfig?.timeout ?? 0)
self.mode = Int(node?.serialConfig?.mode ?? 0)
self.hasChanges = false
// Need to request a SerialModuleConfig from the remote node before allowing changes
if bleManager.connectedPeripheral != nil && node?.serialConfig == nil {
print("empty serial module config")
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context)
if connectedNode != nil && connectedNode!.num > 0 {
if node != nil && connectedNode != nil {
_ = bleManager.requestSerialModuleConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0)
}
}
}
.onChange(of: enabled) { newEnabled in
if node != nil && node!.serialConfig != nil {
if newEnabled != node!.serialConfig!.enabled { hasChanges = true }
}
}
.onChange(of: echo) { newEcho in
if node != nil && node!.serialConfig != nil {
if newEcho != node!.serialConfig!.echo { hasChanges = true }
}
}
.onChange(of: rxd) { newRxd in
if node != nil && node!.serialConfig != nil {
if newRxd != node!.serialConfig!.rxd { hasChanges = true }
}
}
.onChange(of: txd) { newTxd in
if node != nil && node!.serialConfig != nil {
if newTxd != node!.serialConfig!.txd { hasChanges = true }
}
}
.onChange(of: baudRate) { newBaud in
if node != nil && node!.serialConfig != nil {
if newBaud != node!.serialConfig!.baudRate { hasChanges = true }
}
}
.onChange(of: timeout) { newTimeout in
if node != nil && node!.serialConfig != nil {
if newTimeout != node!.serialConfig!.timeout { hasChanges = true }
}
}
.onChange(of: mode) { newMode in
if node != nil && node!.serialConfig != nil {
if newMode != node!.serialConfig!.mode { hasChanges = true }
}
}

View file

@ -7,13 +7,13 @@
import SwiftUI
struct TelemetryConfig: View {
@Environment(\.managedObjectContext) var context
@EnvironmentObject var bleManager: BLEManager
@Environment(\.dismiss) private var goBack
var node: NodeInfoEntity?
@State private var isPresentingSaveConfirm: Bool = false
@State var hasChanges = false
@State var deviceUpdateInterval = 0
@ -21,9 +21,9 @@ struct TelemetryConfig: View {
@State var environmentMeasurementEnabled = false
@State var environmentScreenEnabled = false
@State var environmentDisplayFahrenheit = false
var body: some View {
VStack {
Form {
Section(header: Text("update.interval")) {
@ -88,7 +88,7 @@ struct TelemetryConfig: View {
tc.environmentMeasurementEnabled = environmentMeasurementEnabled
tc.environmentScreenEnabled = environmentScreenEnabled
tc.environmentDisplayFahrenheit = environmentDisplayFahrenheit
let adminMessageId = bleManager.saveTelemetryModuleConfig(config: tc, fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0)
let adminMessageId = bleManager.saveTelemetryModuleConfig(config: tc, fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0)
if adminMessageId > 0 {
// Should show a saved successfully alert once I know that to be true
// for now just disable the button after a successful save
@ -114,12 +114,12 @@ struct TelemetryConfig: View {
self.environmentScreenEnabled = node?.telemetryConfig?.environmentScreenEnabled ?? false
self.environmentDisplayFahrenheit = node?.telemetryConfig?.environmentDisplayFahrenheit ?? false
self.hasChanges = false
// Need to request a TelemetryModuleConfig from the remote node before allowing changes
if bleManager.connectedPeripheral != nil && node?.telemetryConfig == nil {
print("empty telemetry module config")
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context)
if connectedNode != nil && connectedNode!.num > 0 {
if node != nil && connectedNode != nil {
_ = bleManager.requestTelemetryModuleConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0)
}
}

View file

@ -8,13 +8,13 @@
import SwiftUI
struct NetworkConfig: View {
@Environment(\.managedObjectContext) var context
@EnvironmentObject var bleManager: BLEManager
@Environment(\.dismiss) private var goBack
var node: NodeInfoEntity?
@State private var isPresentingSaveConfirm: Bool = false
@State var hasChanges: Bool = false
@State var wifiEnabled = false
@ -24,13 +24,13 @@ struct NetworkConfig: View {
@State var ntpServer = ""
@State var ethEnabled = false
@State var ethMode = 0
var body: some View {
VStack {
Form {
Section(header: Text("WiFi Options (ESP32 Only)")) {
Toggle(isOn: $wifiEnabled) {
Label("enabled", systemImage: "wifi")
}
@ -41,7 +41,7 @@ struct NetworkConfig: View {
.foregroundColor(.gray)
.autocapitalization(.none)
.disableAutocorrection(true)
.onChange(of: wifiSsid, perform: { value in
.onChange(of: wifiSsid, perform: { _ in
let totalBytes = wifiSsid.utf8.count
// Only mess with the value if it is too big
if totalBytes > 32 {
@ -51,7 +51,7 @@ struct NetworkConfig: View {
wifiSsid = maxBytesString
}
}
hasChanges = true
hasChanges = true
})
.foregroundColor(.gray)
}
@ -62,7 +62,7 @@ struct NetworkConfig: View {
.foregroundColor(.gray)
.autocapitalization(.none)
.disableAutocorrection(true)
.onChange(of: wifiPsk, perform: { value in
.onChange(of: wifiPsk, perform: { _ in
let totalBytes = wifiPsk.utf8.count
// Only mess with the value if it is too big
if totalBytes > 63 {
@ -117,8 +117,8 @@ struct NetworkConfig: View {
network.wifiSsid = self.wifiSsid
network.wifiPsk = self.wifiPsk
network.ethEnabled = self.ethEnabled
//network.addressMode = Config.NetworkConfig.AddressMode.dhcp
// network.addressMode = Config.NetworkConfig.AddressMode.dhcp
let adminMessageId = bleManager.saveNetworkConfig(config: network, fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: node?.myInfo?.adminIndex ?? 0)
if adminMessageId > 0 {
// Should show a saved successfully alert once I know that to be true
@ -145,12 +145,12 @@ struct NetworkConfig: View {
self.wifiMode = Int(node?.networkConfig?.wifiMode ?? 0)
self.ethEnabled = node?.networkConfig?.ethEnabled ?? false
self.hasChanges = false
// Need to request a NetworkConfig from the remote node before allowing changes
if bleManager.connectedPeripheral != nil && node?.positionConfig == nil {
print("empty network config")
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context)
if connectedNode != nil && connectedNode!.num > 0 {
if node != nil && connectedNode != nil {
_ = bleManager.requestNetworkConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0)
}
}

View file

@ -7,10 +7,9 @@
import SwiftUI
struct PositionFlags: OptionSet
{
struct PositionFlags: OptionSet {
let rawValue: Int
static let Altitude = PositionFlags(rawValue: 1)
static let AltitudeMsl = PositionFlags(rawValue: 2)
static let GeoidalSeparation = PositionFlags(rawValue: 4)
@ -24,17 +23,17 @@ struct PositionFlags: OptionSet
}
struct PositionConfig: View {
@Environment(\.managedObjectContext) var context
@EnvironmentObject var bleManager: BLEManager
@Environment(\.dismiss) private var goBack
var node: NodeInfoEntity?
@State private var isPresentingSaveConfirm: Bool = false
@State var hasChanges = false
@State var hasFlagChanges = false
@State var smartPositionEnabled = true
@State var deviceGpsEnabled = true
@State var fixedPosition = false
@ -42,7 +41,7 @@ struct PositionConfig: View {
@State var gpsAttemptTime = 0
@State var positionBroadcastSeconds = 0
@State var positionFlags = 3
/// Position Flags
/// Altitude value - 1
@State var includeAltitude = false
@ -68,9 +67,9 @@ struct PositionConfig: View {
/// Intended for use with vehicle not walking speeds
/// walking speeds are likely to be error prone like the compass
@State var includeHeading = false
var body: some View {
VStack {
Form {
Section(header: Text("Device GPS")) {
@ -103,36 +102,36 @@ struct PositionConfig: View {
.font(.caption)
}
}
Section(header: Text("Position Packet")) {
Toggle(isOn: $smartPositionEnabled) {
Label("Smart Position Broadcast", systemImage: "location.fill.viewfinder")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
Picker("Position Broadcast Interval", selection: $positionBroadcastSeconds) {
ForEach(UpdateIntervals.allCases) { at in
Text(at.description)
}
}
.pickerStyle(DefaultPickerStyle())
Text("We should send our position this often (but only if it has changed significantly)")
.font(.caption)
}
Section(header: Text("Position Flags")) {
Text("Optional fields to include when assembling position messages. the more fields are included, the larger the message will be - leading to longer airtime and a higher risk of packet loss")
.font(.caption)
.listRowSeparator(.visible)
Toggle(isOn: $includeAltitude) {
Label("Altitude", systemImage: "arrow.up")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
if includeAltitude {
Toggle(isOn: $includeAltitudeMsl) {
Label("Altitude is Mean Sea Level", systemImage: "arrow.up.to.line.compact")
@ -143,40 +142,40 @@ struct PositionConfig: View {
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
}
Toggle(isOn: $includeSatsinview) {
Label("Number of satellites", systemImage: "skew")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
Toggle(isOn: $includeSeqNo) { //64
Toggle(isOn: $includeSeqNo) { // 64
Label("Sequence number", systemImage: "number")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
Toggle(isOn: $includeTimestamp) { //128
Toggle(isOn: $includeTimestamp) { // 128
Label("timestamp", systemImage: "clock")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
Toggle(isOn: $includeHeading) { //128
Toggle(isOn: $includeHeading) { // 128
Label("Vehicle heading", systemImage: "location.circle")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
Toggle(isOn: $includeSpeed) { //128
Toggle(isOn: $includeSpeed) { // 128
Label("Vehicle speed", systemImage: "speedometer")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
}
Section(header: Text("Advanced Position Flags")) {
Toggle(isOn: $includeDop) {
Text("Dilution of precision (DOP) PDOP used by default")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
if includeDop {
Toggle(isOn: $includeHvdop) {
Text("If DOP is set use, HDOP / VDOP values instead of PDOP")
@ -186,7 +185,7 @@ struct PositionConfig: View {
}
}
.disabled(self.bleManager.connectedPeripheral == nil || node?.positionConfig == nil)
Button {
isPresentingSaveConfirm = true
} label: {
@ -205,12 +204,12 @@ struct PositionConfig: View {
let nodeName = node?.user?.longName ?? NSLocalizedString("unknown", comment: "Unknown")
let buttonText = String.localizedStringWithFormat(NSLocalizedString("save.config %@", comment: "Save Config for %@"), nodeName)
Button(buttonText) {
if fixedPosition {
_ = bleManager.sendPosition(destNum: node!.num, wantResponse: true)
}
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context)
if connectedNode != nil {
var pc = Config.PositionConfig()
pc.positionBroadcastSmartEnabled = smartPositionEnabled
@ -219,7 +218,7 @@ struct PositionConfig: View {
pc.gpsUpdateInterval = UInt32(gpsUpdateInterval)
pc.gpsAttemptTime = UInt32(gpsAttemptTime)
pc.positionBroadcastSecs = UInt32(positionBroadcastSeconds)
var pf : PositionFlags = []
var pf: PositionFlags = []
if includeAltitude { pf.insert(.Altitude) }
if includeAltitudeMsl { pf.insert(.AltitudeMsl) }
if includeGeoidalSeparation { pf.insert(.GeoidalSeparation) }
@ -253,7 +252,7 @@ struct PositionConfig: View {
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????")
})
.onAppear {
self.bleManager.context = context
self.smartPositionEnabled = node?.positionConfig?.smartPositionEnabled ?? true
self.deviceGpsEnabled = node?.positionConfig?.deviceGpsEnabled ?? true
@ -262,9 +261,9 @@ struct PositionConfig: View {
self.gpsAttemptTime = Int(node?.positionConfig?.gpsAttemptTime ?? 30)
self.positionBroadcastSeconds = Int(node?.positionConfig?.positionBroadcastSeconds ?? 900)
self.positionFlags = Int(node?.positionConfig?.positionFlags ?? 3)
let pf = PositionFlags(rawValue: self.positionFlags)
if pf.contains(.Altitude) { self.includeAltitude = true } else { self.includeAltitude = false }
if pf.contains(.AltitudeMsl) { self.includeAltitudeMsl = true } else { self.includeAltitudeMsl = false }
if pf.contains(.GeoidalSeparation) { self.includeGeoidalSeparation = true } else { self.includeGeoidalSeparation = false }
@ -275,14 +274,14 @@ struct PositionConfig: View {
if pf.contains(.Timestamp) { self.includeTimestamp = true } else { self.includeTimestamp = false }
if pf.contains(.Speed) { self.includeSpeed = true } else { self.includeSpeed = false }
if pf.contains(.Heading) { self.includeHeading = true } else { self.includeHeading = false }
self.hasChanges = false
// Need to request a PositionConfig from the remote node before allowing changes
if bleManager.connectedPeripheral != nil && node?.positionConfig == nil {
print("empty position config")
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context)
if connectedNode != nil && connectedNode!.num > 0 {
if node != nil && connectedNode != nil {
_ = bleManager.requestPositionConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0)
}
}

View file

@ -10,7 +10,7 @@ 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 {
@ -55,7 +55,7 @@ struct MeshLog: View {
)
.textSelection(.enabled)
.font(.caption)
HStack(alignment: .center) {
Spacer()
Button(role: .destructive) {
@ -74,7 +74,7 @@ struct MeshLog: View {
.controlSize(.large)
.padding()
Spacer()
Button {
isExporting = true
} label: {

Some files were not shown because too many files have changed in this diff Show more