diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index d6168a1e..58b7e8c2 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -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 = ""; }; C9697F9C279336B700250207 /* LocalMBTileOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalMBTileOverlay.swift; sourceTree = ""; }; - C9A7BC0F27759A9600760B50 /* PositionAnnotationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PositionAnnotationView.swift; sourceTree = ""; }; - C9A88B54278B503C00BD810A /* MapViewModule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MapViewModule.swift; sourceTree = ""; }; DD0F791A28713C8A00A6FDAD /* AdminMessageList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdminMessageList.swift; sourceTree = ""; }; DD1925B628CDA5A400720036 /* CannedMessagesConfigEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CannedMessagesConfigEnums.swift; sourceTree = ""; }; DD1925B828CDA93900720036 /* SerialConfigEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SerialConfigEnums.swift; sourceTree = ""; }; @@ -264,7 +260,7 @@ DDB6ABD528AE742000384BA1 /* BluetoothConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BluetoothConfig.swift; sourceTree = ""; }; DDB6ABD828B0A4BA00384BA1 /* BluetoothModes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BluetoothModes.swift; sourceTree = ""; }; DDB6ABDA28B0AC6000384BA1 /* DistanceText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DistanceText.swift; sourceTree = ""; }; - DDB6ABDF28B13AC700384BA1 /* DeviceRoles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceRoles.swift; sourceTree = ""; }; + DDB6ABDF28B13AC700384BA1 /* DeviceEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceEnums.swift; sourceTree = ""; }; DDB6ABE128B13FB500384BA1 /* PositionConfigEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PositionConfigEnums.swift; sourceTree = ""; }; DDB6ABE328B13FFF00384BA1 /* DisplayEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplayEnums.swift; sourceTree = ""; }; DDB6ABE528B1406100384BA1 /* LoraConfigEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoraConfigEnums.swift; sourceTree = ""; }; @@ -290,6 +286,7 @@ DDD3BBD4292D763200D609B3 /* MeshtasticTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MeshtasticTests.swift; sourceTree = ""; }; DDD94A4F2845C8F5004A87A0 /* DateTimeText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateTimeText.swift; sourceTree = ""; }; DDD9E4E3284B208E003777C5 /* UserEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserEntityExtension.swift; sourceTree = ""; }; + DDDD527729B5B83F0045BC3C /* MeshtasticDataModelV9.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV9.xcdatamodel; sourceTree = ""; }; 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 = ""; }; DDDE59FA29AF163D00490C6C /* WidgetsLiveActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetsLiveActivity.swift; sourceTree = ""; }; @@ -355,8 +352,6 @@ C9697F9C279336B700250207 /* LocalMBTileOverlay.swift */, DD964FC32974767D007C176F /* MapViewFitExtension.swift */, DD2AD8A7296D2DF9001FF0E7 /* MapViewSwiftUI.swift */, - C9A88B54278B503C00BD810A /* MapViewModule.swift */, - C9A7BC0F27759A9600760B50 /* PositionAnnotationView.swift */, ); path = Custom; sourceTree = ""; @@ -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 = ""; diff --git a/Meshtastic/Enums/AppSettingsEnums.swift b/Meshtastic/Enums/AppSettingsEnums.swift index e477ae1b..c9274bf0 100644 --- a/Meshtastic/Enums/AppSettingsEnums.swift +++ b/Meshtastic/Enums/AppSettingsEnums.swift @@ -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") } } } diff --git a/Meshtastic/Enums/BluetoothModes.swift b/Meshtastic/Enums/BluetoothModes.swift index 69f926e6..8304a9e8 100644 --- a/Meshtastic/Enums/BluetoothModes.swift +++ b/Meshtastic/Enums/BluetoothModes.swift @@ -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: diff --git a/Meshtastic/Enums/CannedMessagesConfigEnums.swift b/Meshtastic/Enums/CannedMessagesConfigEnums.swift index 565e375d..7cdbe6ef 100644 --- a/Meshtastic/Enums/CannedMessagesConfigEnums.swift +++ b/Meshtastic/Enums/CannedMessagesConfigEnums.swift @@ -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: diff --git a/Meshtastic/Enums/ChannelRoles.swift b/Meshtastic/Enums/ChannelRoles.swift index 83bb30a7..39f91dbc 100644 --- a/Meshtastic/Enums/ChannelRoles.swift +++ b/Meshtastic/Enums/ChannelRoles.swift @@ -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: diff --git a/Meshtastic/Enums/DeviceEnums.swift b/Meshtastic/Enums/DeviceEnums.swift new file mode 100644 index 00000000..a53a9893 --- /dev/null +++ b/Meshtastic/Enums/DeviceEnums.swift @@ -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 + } + } +} diff --git a/Meshtastic/Enums/DeviceRoles.swift b/Meshtastic/Enums/DeviceRoles.swift deleted file mode 100644 index 9691a52d..00000000 --- a/Meshtastic/Enums/DeviceRoles.swift +++ /dev/null @@ -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 - } - } -} diff --git a/Meshtastic/Enums/DisplayEnums.swift b/Meshtastic/Enums/DisplayEnums.swift index 52bde654..af2ed5a3 100644 --- a/Meshtastic/Enums/DisplayEnums.swift +++ b/Meshtastic/Enums/DisplayEnums.swift @@ -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 diff --git a/Meshtastic/Enums/EthernetModes.swift b/Meshtastic/Enums/EthernetModes.swift index 352586bd..beff9ea0 100644 --- a/Meshtastic/Enums/EthernetModes.swift +++ b/Meshtastic/Enums/EthernetModes.swift @@ -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: diff --git a/Meshtastic/Enums/IntervalEnums.swift b/Meshtastic/Enums/IntervalEnums.swift index 469ae308..06087893 100644 --- a/Meshtastic/Enums/IntervalEnums.swift +++ b/Meshtastic/Enums/IntervalEnums.swift @@ -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") } } } diff --git a/Meshtastic/Enums/LoraConfigEnums.swift b/Meshtastic/Enums/LoraConfigEnums.swift index ca076fd3..1e737a84 100644 --- a/Meshtastic/Enums/LoraConfigEnums.swift +++ b/Meshtastic/Enums/LoraConfigEnums.swift @@ -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 } } } diff --git a/Meshtastic/Enums/MessagingEnums.swift b/Meshtastic/Enums/MessagingEnums.swift index 9b9f145d..cb01341a 100644 --- a/Meshtastic/Enums/MessagingEnums.swift +++ b/Meshtastic/Enums/MessagingEnums.swift @@ -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") } } } diff --git a/Meshtastic/Enums/PositionConfigEnums.swift b/Meshtastic/Enums/PositionConfigEnums.swift index f4d7f4fa..fbfb956a 100644 --- a/Meshtastic/Enums/PositionConfigEnums.swift +++ b/Meshtastic/Enums/PositionConfigEnums.swift @@ -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") } } } diff --git a/Meshtastic/Enums/RoutingError.swift b/Meshtastic/Enums/RoutingError.swift index 390ad5a6..d9724aa3 100644 --- a/Meshtastic/Enums/RoutingError.swift +++ b/Meshtastic/Enums/RoutingError.swift @@ -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 - + } } } diff --git a/Meshtastic/Enums/SerialConfigEnums.swift b/Meshtastic/Enums/SerialConfigEnums.swift index 38a54de9..3948750f 100644 --- a/Meshtastic/Enums/SerialConfigEnums.swift +++ b/Meshtastic/Enums/SerialConfigEnums.swift @@ -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") } } } diff --git a/Meshtastic/Enums/TelemetryWeather.swift b/Meshtastic/Enums/TelemetryWeather.swift index e8a046d8..4da4e79c 100644 --- a/Meshtastic/Enums/TelemetryWeather.swift +++ b/Meshtastic/Enums/TelemetryWeather.swift @@ -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" } } } diff --git a/Meshtastic/Export/CsvDocument.swift b/Meshtastic/Export/CsvDocument.swift index 53f93a78..f4bb04ac 100644 --- a/Meshtastic/Export/CsvDocument.swift +++ b/Meshtastic/Export/CsvDocument.swift @@ -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) diff --git a/Meshtastic/Export/WriteCsvFile.swift b/Meshtastic/Export/WriteCsvFile.swift index 48d8d788..1040426f 100644 --- a/Meshtastic/Export/WriteCsvFile.swift +++ b/Meshtastic/Export/WriteCsvFile.swift @@ -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: "") diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 424f7dda..3375e012 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -8,63 +8,56 @@ import MapKit // Meshtastic BLE Device Manager // --------------------------------------------------------------------------------------- class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { - + private static var documentsFolder: URL { do { - return try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true) + return try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true) } catch { fatalError("Can't find documents directory.") } } - var context: NSManagedObjectContext? var userSettings: UserSettings? private var centralManager: CBCentralManager! private let restoreKey = "Meshtastic.BLE.Manager" - @Published var peripherals: [Peripheral] = [] @Published var connectedPeripheral: Peripheral! @Published var lastConnectionError: String @Published var invalidVersion = false @Published var isSwitchedOn: Bool = false @Published var automaticallyReconnect: Bool = true - public var minimumVersion = "2.0.0" public var connectedVersion: String public var isConnecting: Bool = false public var isConnected: Bool = false public var isSubscribed: Bool = false private var configNonce: UInt32 = 1 - var timeoutTimer: Timer? var timeoutTimerCount = 0 var timeoutTimerRuns = 0 var positionTimer: Timer? let emptyNodeNum: UInt32 = 4294967295 - /* Meshtastic Service Details */ var TORADIO_characteristic: CBCharacteristic! var FROMRADIO_characteristic: CBCharacteristic! var FROMNUM_characteristic: CBCharacteristic! - let meshtasticServiceCBUUID = CBUUID(string: "0x6BA1B218-15A8-461F-9FA8-5DCAE273EAFD") let TORADIO_UUID = CBUUID(string: "0xF75C76D2-129E-4DAD-A1DD-7866124401E7") let FROMRADIO_UUID = CBUUID(string: "0x2C55E69E-4993-11ED-B878-0242AC120002") let EOL_FROMRADIO_UUID = CBUUID(string: "0x8BA2BCC2-EE02-4A55-A531-C525C5E454D5") let FROMNUM_UUID = CBUUID(string: "0xED9DA18C-A800-4F66-A670-AA7547E34453") - - //private var meshLoggingEnabled: Bool = true + let meshLog = documentsFolder.appendingPathComponent("meshlog.txt") - + // MARK: init BLEManager override init() { self.lastConnectionError = "" self.connectedVersion = "0.0.0" super.init() centralManager = CBCentralManager(delegate: self, queue: nil) - //centralManager = CBCentralManager(delegate: self, queue: nil, options: [CBCentralManagerOptionRestoreIdentifierKey: restoreKey]) + // centralManager = CBCentralManager(delegate: self, queue: nil, options: [CBCentralManagerOptionRestoreIdentifierKey: restoreKey]) } - + // MARK: Scanning for BLE Devices // Scan for nearby BLE devices using the Meshtastic BLE service ID func startScanning() { @@ -101,7 +94,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { } connectedPeripheral = nil if self.timeoutTimer != nil { - + self.timeoutTimer!.invalidate() } self.isConnected = false @@ -109,7 +102,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { self.lastConnectionError = "🚨 " + String.localizedStringWithFormat(NSLocalizedString("ble.connection.timeout %d %@", comment: "Connection failed after %d attempts to connect to %@. You may need to forget your device under Settings > Bluetooth."), timeoutTimerCount, name) - + MeshLogger.log(lastConnectionError) self.timeoutTimerCount = 0 self.timeoutTimerRuns += 1 @@ -131,7 +124,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { print("ℹ️ BLE Disconnecting from: \(connectedPeripheral.name) to connect to \(peripheral.name ?? "Unknown")") disconnectPeripheral() } - + centralManager?.connect(peripheral) // Invalidate any existing timer if timeoutTimer != nil { @@ -178,15 +171,14 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { if timeoutTimer != nil { timeoutTimer!.invalidate() } - + // remove any connection errors self.lastConnectionError = "" // Map the peripheral to the connectedPeripheral ObservedObjects connectedPeripheral = peripherals.filter({ $0.peripheral.identifier == peripheral.identifier }).first if connectedPeripheral != nil { connectedPeripheral.peripheral.delegate = self - } - else { + } else { // we are null just disconnect and start over lastConnectionError = "Bluetooth connection error, please try again." disconnectPeripheral() @@ -257,19 +249,19 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { // MARK: Discover Characteristics Event func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) { - + if let e = error { print("🚫 BLE Discover Characteristics error for \(peripheral.name ?? "Unknown") \(e) disconnecting device") // Try and stop crashes when this error occurs disconnectPeripheral() return } - + guard let characteristics = service.characteristics else { return } for characteristic in characteristics { switch characteristic.uuid { - + case TORADIO_UUID: print("✅ BLE did discover TORADIO characteristic for Meshtastic by \(peripheral.name ?? "Unknown")") TORADIO_characteristic = characteristic @@ -288,14 +280,14 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { break } } - if (![FROMNUM_characteristic, TORADIO_characteristic].contains(nil)) { + if ![FROMNUM_characteristic, TORADIO_characteristic].contains(nil) { sendWantConfig() } } - + func requestDeviceMetadata(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32, context: NSManagedObjectContext) -> Int64 { - - guard (connectedPeripheral!.peripheral.state == CBPeripheralState.connected) else { return 0 } + + guard connectedPeripheral!.peripheral.state == CBPeripheralState.connected else { return 0 } var adminPacket = AdminMessage() adminPacket.getDeviceMetadataRequest = true @@ -317,11 +309,11 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { } return 0 } - - func sendTraceRouteRequest(destNum: Int64, wantResponse: Bool) -> Bool { - + + func sendTraceRouteRequest(destNum: Int64, wantResponse: Bool) -> Bool { + var success = false - guard (connectedPeripheral!.peripheral.state == CBPeripheralState.connected) else { return success } + guard connectedPeripheral!.peripheral.state == CBPeripheralState.connected else { return success } let fromNodeNum = connectedPeripheral.num let routePacket = RouteDiscovery() @@ -339,31 +331,31 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { toRadio = ToRadio() toRadio.packet = meshPacket let binaryData: Data = try! toRadio.serializedData() - + if connectedPeripheral!.peripheral.state == CBPeripheralState.connected { connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse) success = true - + let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.traceroute.sent %@", - comment: "Sent a Trace Route Request to node: %@"), String(destNum)) + comment: "Sent a Trace Route Request to node: %@"), String(destNum)) MeshLogger.log("🪧 \(logString)") } return success } - + func sendWantConfig() { - guard (connectedPeripheral!.peripheral.state == CBPeripheralState.connected) else { return } + guard connectedPeripheral!.peripheral.state == CBPeripheralState.connected else { return } if FROMRADIO_characteristic == nil { MeshLogger.log("🚨 \(NSLocalizedString("firmware.version.unsupported", comment: "Unsupported Firmware Version Detected, unable to connect to device."))") invalidVersion = true return } else { - + let nodeName = connectedPeripheral!.peripheral.name ?? NSLocalizedString("unknown", comment: NSLocalizedString("unknown", comment: "Unknown")) let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.wantconfig %@", comment: "Issuing Want Config to %@"), nodeName) MeshLogger.log("🛎️ \(logString)") - //BLE Characteristics discovered, issue wantConfig + // BLE Characteristics discovered, issue wantConfig var toRadio: ToRadio = ToRadio() configNonce += 1 toRadio.wantConfigID = configNonce @@ -382,13 +374,13 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { // MARK: Data Read / Update Characteristic Event func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) { - + if let e = error { - + print("🚫 didUpdateValueFor Characteristic error \(e)") let errorCode = (e as NSError).code - + if errorCode == 5 || errorCode == 15 { // BLE PIN connection errors // 5 CBATTErrorDomain Code=5 "Authentication is insufficient." @@ -404,30 +396,30 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { switch characteristic.uuid { case FROMRADIO_UUID: - + if characteristic.value == nil || characteristic.value!.isEmpty { return } var decodedInfo = FromRadio() - + do { decodedInfo = try FromRadio(serializedData: characteristic.value!) - + } catch { print(characteristic.value!) } switch decodedInfo.packet.decoded.portnum { - - // Handle Any local only packets we get over BLE - case .unknownApp: + + // Handle Any local only packets we get over BLE + case .unknownApp: var nowKnown = false - + // MyInfo from initial connection if decodedInfo.myInfo.isInitialized && decodedInfo.myInfo.myNodeNum > 0 { - + let lastDotIndex = decodedInfo.myInfo.firmwareVersion.lastIndex(of: ".") - + if lastDotIndex == nil { invalidVersion = true connectedVersion = "0.0.0" @@ -442,11 +434,11 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { invalidVersion = true lastConnectionError = "🚨" + NSLocalizedString("update.firmware", comment: "Update Your Firmware") return - + } else { - + let myInfo = myInfoPacket(myInfo: decodedInfo.myInfo, peripheralId: self.connectedPeripheral.id, context: context!) - + if myInfo != nil { connectedPeripheral.num = myInfo!.myNodeNum connectedPeripheral.firmwareVersion = myInfo?.firmwareVersion ?? NSLocalizedString("unknown", comment: "Unknown") @@ -457,10 +449,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { } // NodeInfo if decodedInfo.nodeInfo.num > 0 && !invalidVersion { - nowKnown = true let nodeInfo = nodeInfoPacket(nodeInfo: decodedInfo.nodeInfo, channel: decodedInfo.packet.channel, context: context!) - + if nodeInfo != nil { if self.connectedPeripheral != nil && self.connectedPeripheral.num == nodeInfo!.num { if nodeInfo!.user != nil { @@ -475,21 +466,20 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { nowKnown = true channelPacket(channel: decodedInfo.channel, fromNum: connectedPeripheral.num, context: context!) } - // Config if decodedInfo.config.isInitialized && !invalidVersion { - + nowKnown = true localConfig(config: decodedInfo.config, context: context!, nodeNum: self.connectedPeripheral.num, nodeLongName: self.connectedPeripheral.longName) } // Module Config if decodedInfo.moduleConfig.isInitialized && !invalidVersion { - + nowKnown = true moduleConfig(config: decodedInfo.moduleConfig, context: context!, nodeNum: self.connectedPeripheral.num, nodeLongName: self.connectedPeripheral.longName) if decodedInfo.moduleConfig.payloadVariant == ModuleConfig.OneOf_PayloadVariant.cannedMessage(decodedInfo.moduleConfig.cannedMessage) { - + if decodedInfo.moduleConfig.cannedMessage.enabled { _ = self.getCannedMessageModuleMessages(destNum: self.connectedPeripheral.num, wantResponse: true) } @@ -500,57 +490,56 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { nowKnown = true deviceMetadataPacket(metadata: decodedInfo.metadata, fromNum: connectedPeripheral.num, context: context!) } - // Log any other unknownApp calls - if !nowKnown { MeshLogger.log("🕸️ MESH PACKET received for Unknown App UNHANDLED \(try! decodedInfo.packet.jsonString())") } - - case .textMessageApp: - textMessageAppPacket(packet: decodedInfo.packet, connectedNode: (self.connectedPeripheral != nil ? connectedPeripheral.num : 0), context: context!) - case .remoteHardwareApp: - MeshLogger.log("🕸️ MESH PACKET received for Remote Hardware App UNHANDLED \(try! decodedInfo.packet.jsonString())") - case .positionApp: - upsertPositionPacket(packet: decodedInfo.packet, context: context!) - case .waypointApp: - waypointPacket(packet: decodedInfo.packet, context: context!) - case .nodeinfoApp: - if !invalidVersion { nodeInfoAppPacket(packet: decodedInfo.packet, context: context!) } - case .routingApp: - if !invalidVersion { routingPacket(packet: decodedInfo.packet, connectedNodeNum: self.connectedPeripheral.num, context: context!) } - case .adminApp: - adminAppPacket(packet: decodedInfo.packet, context: context!) - case .replyApp: - MeshLogger.log("🕸️ MESH PACKET received for Reply App UNHANDLED \(try! decodedInfo.packet.jsonString())") - case .ipTunnelApp: - MeshLogger.log("🕸️ MESH PACKET received for IP Tunnel App UNHANDLED \(try! decodedInfo.packet.jsonString())") - case .serialApp: - MeshLogger.log("🕸️ MESH PACKET received for Serial App UNHANDLED \(try! decodedInfo.packet.jsonString())") - case .storeForwardApp: - MeshLogger.log("🕸️ MESH PACKET received for Store Forward App UNHANDLED \(try! decodedInfo.packet.jsonString())") - case .rangeTestApp: - MeshLogger.log("🕸️ MESH PACKET received for Range Test App UNHANDLED \(try! decodedInfo.packet.jsonString())") - case .telemetryApp: - if !invalidVersion { telemetryPacket(packet: decodedInfo.packet, connectedNode: (self.connectedPeripheral != nil ? connectedPeripheral.num : 0), context: context!) } - case .textMessageCompressedApp: - MeshLogger.log("🕸️ MESH PACKET received for Text Message Compressed App UNHANDLED \(try! decodedInfo.packet.jsonString())") - case .zpsApp: - MeshLogger.log("🕸️ MESH PACKET received for ZPS App UNHANDLED \(try! decodedInfo.packet.jsonString())") - case .privateApp: - MeshLogger.log("🕸️ MESH PACKET received for Private App UNHANDLED \(try! decodedInfo.packet.jsonString())") - case .atakForwarder: - MeshLogger.log("🕸️ MESH PACKET received for ATAK Forwarder App UNHANDLED \(try! decodedInfo.packet.jsonString())") - case .simulatorApp: - MeshLogger.log("🕸️ MESH PACKET received for Simulator App UNHANDLED \(try! decodedInfo.packet.jsonString())") - case .audioApp: - MeshLogger.log("🕸️ MESH PACKET received for Audio App UNHANDLED \(try! decodedInfo.packet.jsonString())") - case .tracerouteApp: + if !nowKnown { MeshLogger.log("🕸️ MESH PACKET received for Unknown App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") } + + case .textMessageApp: + textMessageAppPacket(packet: decodedInfo.packet, connectedNode: (self.connectedPeripheral != nil ? connectedPeripheral.num : 0), context: context!) + case .remoteHardwareApp: + MeshLogger.log("🕸️ MESH PACKET received for Remote Hardware App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") + case .positionApp: + upsertPositionPacket(packet: decodedInfo.packet, context: context!) + case .waypointApp: + waypointPacket(packet: decodedInfo.packet, context: context!) + case .nodeinfoApp: + if !invalidVersion { nodeInfoAppPacket(packet: decodedInfo.packet, context: context!) } + case .routingApp: + if !invalidVersion { routingPacket(packet: decodedInfo.packet, connectedNodeNum: self.connectedPeripheral.num, context: context!) } + case .adminApp: + adminAppPacket(packet: decodedInfo.packet, context: context!) + case .replyApp: + MeshLogger.log("🕸️ MESH PACKET received for Reply App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") + case .ipTunnelApp: + MeshLogger.log("🕸️ MESH PACKET received for IP Tunnel App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") + case .serialApp: + MeshLogger.log("🕸️ MESH PACKET received for Serial App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") + case .storeForwardApp: + MeshLogger.log("🕸️ MESH PACKET received for Store Forward App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") + case .rangeTestApp: + MeshLogger.log("🕸️ MESH PACKET received for Range Test App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") + case .telemetryApp: + if !invalidVersion { telemetryPacket(packet: decodedInfo.packet, connectedNode: (self.connectedPeripheral != nil ? connectedPeripheral.num : 0), context: context!) } + case .textMessageCompressedApp: + MeshLogger.log("🕸️ MESH PACKET received for Text Message Compressed App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") + case .zpsApp: + MeshLogger.log("🕸️ MESH PACKET received for ZPS App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") + case .privateApp: + MeshLogger.log("🕸️ MESH PACKET received for Private App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") + case .atakForwarder: + MeshLogger.log("🕸️ MESH PACKET received for ATAK Forwarder App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") + case .simulatorApp: + MeshLogger.log("🕸️ MESH PACKET received for Simulator App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") + case .audioApp: + MeshLogger.log("🕸️ MESH PACKET received for Audio App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") + case .tracerouteApp: if let routingMessage = try? RouteDiscovery(serializedData: decodedInfo.packet.decoded.payload) { - + if routingMessage.route.count == 0 { let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.traceroute.received.direct %@", comment: "Trace Route request sent to node: %@ was recieived directly."), String(decodedInfo.packet.from)) MeshLogger.log("🪧 \(logString)") } else { - + var routeString = "\(decodedInfo.packet.to) --> " for node in routingMessage.route { routeString += "\(node) --> " @@ -561,18 +550,20 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { MeshLogger.log("🪧 \(logString)") } } - case .UNRECOGNIZED(_): - MeshLogger.log("🕸️ MESH PACKET received for Other App UNHANDLED \(try! decodedInfo.packet.jsonString())") - case .max: - print("MAX PORT NUM OF 511") + case .UNRECOGNIZED: + MeshLogger.log("🕸️ MESH PACKET received for Other App UNHANDLED \(try! decodedInfo.packet.jsonString())") + case .max: + print("MAX PORT NUM OF 511") } - + // MARK: Check for an All / Broadcast User and delete it as a transition to multi channel let fetchBCUserRequest: NSFetchRequest = NSFetchRequest.init(entityName: "UserEntity") fetchBCUserRequest.predicate = NSPredicate(format: "num == %lld", Int64(emptyNodeNum)) do { - let fetchedUser = try context?.fetch(fetchBCUserRequest) as! [UserEntity] + guard let fetchedUser = try context?.fetch(fetchBCUserRequest) as? [UserEntity] else { + return + } if fetchedUser.count > 0 { context?.delete(fetchedUser[0]) print("🗑️ Deleted the All - Broadcast User") @@ -600,24 +591,22 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { if positionTimer != nil { RunLoop.current.add(positionTimer!, forMode: .common) } - } + } return } - case FROMNUM_UUID : + case FROMNUM_UUID: print("🗞️ BLE (Notify) characteristic, value will be read next") default: print("🚨 Unhandled Characteristic UUID: \(characteristic.uuid)") } if FROMRADIO_characteristic != nil { - // Either Read the config complete value or from num notify value peripheral.readValue(for: FROMRADIO_characteristic) } } public func sendMessage(message: String, toUserNum: Int64, channel: Int32, isEmoji: Bool, replyID: Int64) -> Bool { - var success = false // Return false if we are not properly connected to a device, handle retry logic in the view for now @@ -637,7 +626,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { MeshLogger.log("🚫 \(logString)") success = false - } else if message.count < 1 { // Don't send an empty message @@ -653,13 +641,13 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { do { - let fetchedUsers = try context?.fetch(messageUsers) as! [UserEntity] - + guard let fetchedUsers = try context?.fetch(messageUsers) as? [UserEntity] else { + return false + } if fetchedUsers.isEmpty { print("🚫 Message Users Not Found, Fail") success = false - } else if fetchedUsers.count >= 1 { let newMessage = MessageEntity(context: context!) @@ -708,7 +696,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { toRadio = ToRadio() toRadio.packet = meshPacket let binaryData: Data = try! toRadio.serializedData() - if connectedPeripheral!.peripheral.state == CBPeripheralState.connected { connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse) let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.textmessage.sent %@ %@ %@", comment: "Sent message %@ from %@ to %@"), String(newMessage.messageId), String(fromUserNum), String(toUserNum)) @@ -732,13 +719,11 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { } return success } - + public func sendWaypoint(waypoint: Waypoint) -> Bool { - if waypoint.latitudeI == 373346000 && waypoint.longitudeI == -1220090000 { return false } - var success = false let fromNodeNum = UInt32(connectedPeripheral.num) var meshPacket = MeshPacket() @@ -753,13 +738,11 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { toRadio = ToRadio() toRadio.packet = meshPacket let binaryData: Data = try! toRadio.serializedData() - let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.waypoint.sent %@", comment: "Sent a Waypoint Packet from: %@"), String(fromNodeNum)) + let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.waypoint.sent %@", comment: "Sent a Waypoint Packet from: %@"), String(fromNodeNum)) MeshLogger.log("📍 \(logString)") - if connectedPeripheral!.peripheral.state == CBPeripheralState.connected { connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse) success = true - let wayPointEntity = getWaypoint(id: Int64(waypoint.id), context: context!) wayPointEntity.id = Int64(waypoint.id) wayPointEntity.name = waypoint.name.count >= 1 ? waypoint.name : "Dropped Pin" @@ -767,7 +750,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { wayPointEntity.icon = Int64(waypoint.icon) wayPointEntity.latitudeI = waypoint.latitudeI wayPointEntity.longitudeI = waypoint.longitudeI - if waypoint.expire > 1 { wayPointEntity.expire = Date.init(timeIntervalSince1970: Double(waypoint.expire)) } else { @@ -794,9 +776,8 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { } return success } - - public func sendPosition(destNum: Int64, wantResponse: Bool) -> Bool { - + + public func sendPosition(destNum: Int64, wantResponse: Bool) -> Bool { var success = false let fromNodeNum = connectedPeripheral.num if fromNodeNum <= 0 || LocationHelper.currentLocation.distance(from: LocationHelper.DefaultLocation) == 0.0 { @@ -819,7 +800,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { var meshPacket = MeshPacket() meshPacket.to = UInt32(destNum) meshPacket.from = UInt32(fromNodeNum) - var dataMessage = DataMessage() dataMessage.payload = try! positionPacket.serializedData() dataMessage.portnum = PortNum.positionApp @@ -830,16 +810,14 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { toRadio = ToRadio() toRadio.packet = meshPacket let binaryData: Data = try! toRadio.serializedData() - if connectedPeripheral!.peripheral.state == CBPeripheralState.connected { connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse) success = true - let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.sharelocation %@", comment: "Sent a Position Packet from the Apple device GPS to node: %@"), String(fromNodeNum)) + let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.sharelocation %@", comment: "Sent a Position Packet from the Apple device GPS to node: %@"), String(fromNodeNum)) MeshLogger.log("📍 \(logString)") } return success } - @objc func positionTimerFired(timer: Timer) { // Check for connected node if connectedPeripheral != nil { @@ -852,9 +830,8 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { } } } - + public func sendShutdown(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Bool { - var adminPacket = AdminMessage() adminPacket.shutdownSeconds = 5 var meshPacket: MeshPacket = MeshPacket() @@ -868,20 +845,16 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { dataMessage.payload = try! adminPacket.serializedData() dataMessage.portnum = PortNum.adminApp meshPacket.decoded = dataMessage - let messageDescription = "🚀 Sent Shutdown Admin Message to: \(toUser.longName ?? NSLocalizedString("unknown", comment: "")) from: \(fromUser.longName ?? NSLocalizedString("unknown", comment: ""))" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { return true } - return false } - + public func sendReboot(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Bool { - var adminPacket = AdminMessage() adminPacket.rebootSeconds = 5 - var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -893,26 +866,22 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { dataMessage.payload = try! adminPacket.serializedData() dataMessage.portnum = PortNum.adminApp meshPacket.decoded = dataMessage - let messageDescription = "🚀 Sent Reboot Admin Message to: \(toUser.longName ?? NSLocalizedString("unknown", comment: "")) from: \(fromUser.longName ?? NSLocalizedString("unknown", comment: ""))" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { return true } return false } - + public func sendFactoryReset(fromUser: UserEntity, toUser: UserEntity) -> Bool { - var adminPacket = AdminMessage() adminPacket.factoryReset = 5 - var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Bool { - var adminPacket = AdminMessage() adminPacket.nodedbReset = 5 - var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = 0 // UInt32(fromUser.num) @@ -939,7 +906,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { var dataMessage = DataMessage() dataMessage.payload = try! adminPacket.serializedData() dataMessage.portnum = PortNum.adminApp - + meshPacket.decoded = dataMessage let messageDescription = "🚀 Sent NodeDB Reset Admin Message to: \(toUser.longName ?? NSLocalizedString("unknown", comment: "")) from: \(fromUser.longName ?? NSLocalizedString("unknown", comment: ""))" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { @@ -947,16 +914,13 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { } return false } - + public func connectToPreferredPeripheral() -> Bool { - var success = false // Return false if we are not properly connected to a device, handle retry logic in the view for now if connectedPeripheral == nil || connectedPeripheral!.peripheral.state != CBPeripheralState.connected { - self.disconnectPeripheral() self.startScanning() - // Try and connect to the preferredPeripherial first let preferredPeripheral = peripherals.filter({ $0.peripheral.identifier.uuidString == UserDefaults.standard.object(forKey: "preferredPeripheralId") as? String ?? "" }).first if preferredPeripheral != nil && preferredPeripheral?.peripheral != nil { @@ -968,31 +932,26 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { } return success } - + public func getChannel(channel: Channel, fromUser: UserEntity, toUser: UserEntity) -> Int64 { var adminPacket = AdminMessage() adminPacket.getChannelRequest = UInt32(channel.index + 1) var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) - meshPacket.from = 0 //UInt32(fromUser.num) + meshPacket.from = UInt32(fromUser.num) meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Int64 { @@ -1001,10 +960,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { adminPacket.setChannel = channel var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) - meshPacket.from = 0 //UInt32(fromUser.num) + meshPacket.from = UInt32(fromUser.num) meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Bool { - if isConnected { - - //Before we get started delete the existing channels from the myNodeInfo + // Before we get started delete the existing channels from the myNodeInfo let fetchMyInfoRequest: NSFetchRequest = NSFetchRequest.init(entityName: "MyInfoEntity") fetchMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %lld", Int64(connectedPeripheral.num)) do { - let fetchedMyInfo = try context!.fetch(fetchMyInfoRequest) as! [MyInfoEntity] + guard let fetchedMyInfo = try context!.fetch(fetchMyInfoRequest) as? [MyInfoEntity] else { + return false + } if fetchedMyInfo.count == 1 { - - let mutableChannels = fetchedMyInfo[0].channels!.mutableCopy() as! NSMutableOrderedSet + guard let mutableChannels = fetchedMyInfo[0].channels!.mutableCopy() as? NSMutableOrderedSet else { + return false + } mutableChannels.removeAllObjects() fetchedMyInfo[0].channels = mutableChannels do { @@ -1044,17 +1001,16 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { } catch { print("Failed to find a node MyInfo to save these channels to") } - let decodedString = base64UrlString.base64urlToBase64() if let decodedData = Data(base64Encoded: decodedString) { do { let channelSet: ChannelSet = try ChannelSet(serializedData: decodedData) - var i:Int32 = 0 + var i: Int32 = 0 for cs in channelSet.settings { var chan = Channel() if i == 0 { chan.role = Channel.Role.primary - } else { + } else { chan.role = Channel.Role.secondary } chan.settings = cs @@ -1079,7 +1035,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { let binaryData: Data = try! toRadio.serializedData() if connectedPeripheral!.peripheral.state == CBPeripheralState.connected { self.connectedPeripheral.peripheral.writeValue(binaryData, for: self.TORADIO_characteristic, type: .withResponse) - let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.channel.sent %@ %d", comment: "Sent a Channel for: %@ Channel Index %d"), String(connectedPeripheral.num), chan.index) + let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.channel.sent %@ %d", comment: "Sent a Channel for: %@ Channel Index %d"), String(connectedPeripheral.num), chan.index) MeshLogger.log("🎛️ \(logString)") } } @@ -1088,7 +1044,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { adminPacket.setConfig.lora = channelSet.loraConfig var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(connectedPeripheral.num) - meshPacket.from = 0 //UInt32(connectedPeripheral.num) + meshPacket.from = UInt32(connectedPeripheral.num) meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Int64 { - var adminPacket = AdminMessage() adminPacket.setOwner = config var meshPacket: MeshPacket = MeshPacket() @@ -1132,16 +1086,13 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { dataMessage.portnum = PortNum.adminApp meshPacket.decoded = dataMessage let messageDescription = "🛟 Saved User Config for \(toUser.longName ?? NSLocalizedString("unknown", comment: "Unknown"))" - if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { return Int64(meshPacket.id) } - return 0 } - + public func saveLicensedUser(ham: HamParameters, fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Int64 { - var adminPacket = AdminMessage() adminPacket.setHamMode = ham var meshPacket: MeshPacket = MeshPacket() @@ -1156,16 +1107,12 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { dataMessage.portnum = PortNum.adminApp meshPacket.decoded = dataMessage let messageDescription = "🛟 Saved Ham Parameters for \(toUser.longName ?? NSLocalizedString("unknown", comment: "Unknown"))" - if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { return Int64(meshPacket.id) } - return 0 } - public func saveBluetoothConfig(config: Config.BluetoothConfig, fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Int64 { - var adminPacket = AdminMessage() adminPacket.setConfig.bluetooth = config var meshPacket: MeshPacket = MeshPacket() @@ -1180,20 +1127,20 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { dataMessage.portnum = PortNum.adminApp meshPacket.decoded = dataMessage let messageDescription = "🛟 Saved Bluetooth Config for \(toUser.longName ?? NSLocalizedString("unknown", comment: "Unknown"))" - + if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { upsertBluetoothConfigPacket(config: config, nodeNum: fromUser.num, context: context!) return Int64(meshPacket.id) } - + return 0 } - + public func saveDeviceConfig(config: Config.DeviceConfig, fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Int64 { - + var adminPacket = AdminMessage() adminPacket.setConfig.device = config - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -1205,19 +1152,15 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { dataMessage.payload = try! adminPacket.serializedData() dataMessage.portnum = PortNum.adminApp meshPacket.decoded = dataMessage - let messageDescription = "🛟 Saved Device Config for \(toUser.longName ?? NSLocalizedString("unknown", comment: "Unknown"))" - if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { upsertDeviceConfigPacket(config: config, nodeNum: fromUser.num, context: context!) return Int64(meshPacket.id) } - return 0 } - + public func saveDisplayConfig(config: Config.DisplayConfig, fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Int64 { - var adminPacket = AdminMessage() adminPacket.setConfig.display = config var meshPacket: MeshPacket = MeshPacket() @@ -1235,15 +1178,13 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { dataMessage.portnum = PortNum.adminApp meshPacket.decoded = dataMessage let messageDescription = "🛟 Saved Display Config for \(toUser.longName ?? NSLocalizedString("unknown", comment: "Unknown"))" - if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { upsertDisplayConfigPacket(config: config, nodeNum: fromUser.num, context: context!) return Int64(meshPacket.id) } - return 0 } - + public func saveLoRaConfig(config: Config.LoRaConfig, fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Int64 { var adminPacket = AdminMessage() @@ -1260,20 +1201,20 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { dataMessage.portnum = PortNum.adminApp meshPacket.decoded = dataMessage let messageDescription = "🛟 Saved LoRa Config for \(toUser.longName ?? NSLocalizedString("unknown", comment: "Unknown"))" - + if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { upsertLoRaConfigPacket(config: config, nodeNum: fromUser.num, context: context!) return Int64(meshPacket.id) } - + return 0 } - + public func savePositionConfig(config: Config.PositionConfig, fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Int64 { - + var adminPacket = AdminMessage() adminPacket.setConfig.position = config - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -1282,28 +1223,28 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { meshPacket.priority = MeshPacket.Priority.reliable meshPacket.wantAck = true meshPacket.hopLimit = 0 - + var dataMessage = DataMessage() dataMessage.payload = try! adminPacket.serializedData() dataMessage.portnum = PortNum.adminApp - + meshPacket.decoded = dataMessage - + let messageDescription = "🛟 Saved Position Config for \(toUser.longName ?? NSLocalizedString("unknown", comment: "Unknown"))" - + if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { upsertPositionConfigPacket(config: config, nodeNum: fromUser.num, context: context!) return Int64(meshPacket.id) } - + return 0 } - + public func saveNetworkConfig(config: Config.NetworkConfig, fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Int64 { - + var adminPacket = AdminMessage() adminPacket.setConfig.network = config - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -1312,28 +1253,28 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { meshPacket.priority = MeshPacket.Priority.reliable meshPacket.wantAck = true meshPacket.hopLimit = 0 - + var dataMessage = DataMessage() dataMessage.payload = try! adminPacket.serializedData() dataMessage.portnum = PortNum.adminApp - + meshPacket.decoded = dataMessage - + let messageDescription = "🛟 Saved Network Config for \(toUser.longName ?? NSLocalizedString("unknown", comment: "Unknown"))" - + if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { upsertNetworkConfigPacket(config: config, nodeNum: fromUser.num, context: context!) return Int64(meshPacket.id) } - + return 0 } - + public func saveCannedMessageModuleConfig(config: ModuleConfig.CannedMessageConfig, fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Int64 { var adminPacket = AdminMessage() adminPacket.setModuleConfig.cannedMessage = config - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -1341,28 +1282,28 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Int64 { - + var adminPacket = AdminMessage() adminPacket.setCannedMessageModuleMessages = messages - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -1370,29 +1311,29 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Int64 { var adminPacket = AdminMessage() adminPacket.setModuleConfig.externalNotification = config - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -1400,7 +1341,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Int64 { - + var adminPacket = AdminMessage() adminPacket.setModuleConfig.mqtt = config - + var meshPacket: MeshPacket = MeshPacket() meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Int64 { - + var adminPacket = AdminMessage() adminPacket.setModuleConfig.rangeTest = config - + var meshPacket: MeshPacket = MeshPacket() meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Int64 { - + var adminPacket = AdminMessage() adminPacket.setModuleConfig.serial = config - + var meshPacket: MeshPacket = MeshPacket() meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Int64 { - + var adminPacket = AdminMessage() adminPacket.setModuleConfig.telemetry = config - + var meshPacket: MeshPacket = MeshPacket() meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Bool { - + var adminPacket = AdminMessage() adminPacket.getChannelRequest = channelIndex - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Bool { - + + public func getCannedMessageModuleMessages(destNum: Int64, wantResponse: Bool) -> Bool { + var adminPacket = AdminMessage() adminPacket.getCannedMessageModuleMessagesRequest = true - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(destNum) meshPacket.from = UInt32(connectedPeripheral.num) @@ -1565,12 +1506,12 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { meshPacket.priority = MeshPacket.Priority.reliable meshPacket.wantAck = true meshPacket.decoded.wantResponse = wantResponse - + var dataMessage = DataMessage() dataMessage.payload = try! adminPacket.serializedData() dataMessage.portnum = PortNum.adminApp dataMessage.wantResponse = wantResponse - + meshPacket.decoded = dataMessage var toRadio: ToRadio! @@ -1578,22 +1519,22 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { toRadio.packet = meshPacket let binaryData: Data = try! toRadio.serializedData() - + if connectedPeripheral!.peripheral.state == CBPeripheralState.connected { connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse) - let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.cannedmessages.messages.get %@", comment: "Requested Canned Messages Module Messages for node: %@"), String(connectedPeripheral.num)) + let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.cannedmessages.messages.get %@", comment: "Requested Canned Messages Module Messages for node: %@"), String(connectedPeripheral.num)) MeshLogger.log("🥫 \(logString)") return true } - + return false } - + public func requestBluetoothConfig(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Bool { - + var adminPacket = AdminMessage() adminPacket.getConfigRequest = AdminMessage.ConfigType.bluetoothConfig - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -1601,27 +1542,27 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { meshPacket.priority = MeshPacket.Priority.reliable meshPacket.channel = UInt32(adminIndex) meshPacket.wantAck = true - + var dataMessage = DataMessage() dataMessage.payload = try! adminPacket.serializedData() dataMessage.portnum = PortNum.adminApp dataMessage.wantResponse = true - + meshPacket.decoded = dataMessage - + let messageDescription = "🛎️ Requested Bluetooth Config on admin channel \(adminIndex) for node: \(String(connectedPeripheral.num))" - + if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { return true } return false } - + public func requestDeviceConfig(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Bool { - + var adminPacket = AdminMessage() adminPacket.getConfigRequest = AdminMessage.ConfigType.deviceConfig - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -1629,27 +1570,27 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { meshPacket.priority = MeshPacket.Priority.reliable meshPacket.channel = UInt32(adminIndex) meshPacket.wantAck = true - + var dataMessage = DataMessage() dataMessage.payload = try! adminPacket.serializedData() dataMessage.portnum = PortNum.adminApp dataMessage.wantResponse = true - + meshPacket.decoded = dataMessage - + let messageDescription = "🛎️ Requested Device Config on admin channel \(adminIndex) for node: \(String(connectedPeripheral.num))" - + if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { return true } return false } - + public func requestDisplayConfig(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Bool { - + var adminPacket = AdminMessage() adminPacket.getConfigRequest = AdminMessage.ConfigType.displayConfig - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -1657,27 +1598,27 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { meshPacket.priority = MeshPacket.Priority.reliable meshPacket.channel = UInt32(adminIndex) meshPacket.wantAck = true - + var dataMessage = DataMessage() dataMessage.payload = try! adminPacket.serializedData() dataMessage.portnum = PortNum.adminApp dataMessage.wantResponse = true - + meshPacket.decoded = dataMessage - + let messageDescription = "🛎️ Requested Display Config on admin channel \(adminIndex) for node: \(String(connectedPeripheral.num))" - + if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { return true } return false } - + public func requestLoRaConfig(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Bool { - + var adminPacket = AdminMessage() adminPacket.getConfigRequest = AdminMessage.ConfigType.loraConfig - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -1685,29 +1626,29 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { meshPacket.priority = MeshPacket.Priority.reliable meshPacket.channel = UInt32(adminIndex) meshPacket.wantAck = true - + var dataMessage = DataMessage() dataMessage.payload = try! adminPacket.serializedData() dataMessage.portnum = PortNum.adminApp dataMessage.wantResponse = true - + meshPacket.decoded = dataMessage - + let messageDescription = "🛎️ Requested LoRa Config on admin channel \(adminIndex) for node: \(String(connectedPeripheral.num))" - + if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { - + return true } - + return false } - + public func requestNetworkConfig(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Bool { - + var adminPacket = AdminMessage() adminPacket.getConfigRequest = AdminMessage.ConfigType.networkConfig - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -1715,26 +1656,26 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { meshPacket.priority = MeshPacket.Priority.reliable meshPacket.channel = UInt32(adminIndex) meshPacket.wantAck = true - + var dataMessage = DataMessage() dataMessage.payload = try! adminPacket.serializedData() dataMessage.portnum = PortNum.adminApp dataMessage.wantResponse = true meshPacket.decoded = dataMessage - + let messageDescription = "🛎️ Requested Network Config on admin channel \(adminIndex) for node: \(String(connectedPeripheral.num))" - + if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { return true } return false } - + public func requestPositionConfig(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Bool { - + var adminPacket = AdminMessage() adminPacket.getConfigRequest = AdminMessage.ConfigType.positionConfig - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -1742,26 +1683,26 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { meshPacket.priority = MeshPacket.Priority.reliable meshPacket.channel = UInt32(adminIndex) meshPacket.wantAck = true - + var dataMessage = DataMessage() dataMessage.payload = try! adminPacket.serializedData() dataMessage.portnum = PortNum.adminApp dataMessage.wantResponse = true - + meshPacket.decoded = dataMessage - + let messageDescription = "🛎️ Requested Position Config on admin channel \(adminIndex) for node: \(String(connectedPeripheral.num))" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { return true } return false } - + public func requestCannedMessagesModuleConfig(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Bool { - + var adminPacket = AdminMessage() adminPacket.getModuleConfigRequest = AdminMessage.ModuleConfigType.cannedmsgConfig - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -1769,26 +1710,26 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { meshPacket.priority = MeshPacket.Priority.reliable meshPacket.channel = UInt32(adminIndex) meshPacket.wantAck = true - + var dataMessage = DataMessage() dataMessage.payload = try! adminPacket.serializedData() dataMessage.portnum = PortNum.adminApp dataMessage.wantResponse = true - + meshPacket.decoded = dataMessage - + let messageDescription = "🛎️ Requested Canned Messages Module Config on admin channel \(adminIndex) for node: \(String(connectedPeripheral.num))" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { return true } return false } - + public func requestExternalNotificationModuleConfig(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Bool { - + var adminPacket = AdminMessage() adminPacket.getModuleConfigRequest = AdminMessage.ModuleConfigType.extnotifConfig - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -1796,26 +1737,26 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { meshPacket.priority = MeshPacket.Priority.reliable meshPacket.channel = UInt32(adminIndex) meshPacket.wantAck = true - + var dataMessage = DataMessage() dataMessage.payload = try! adminPacket.serializedData() dataMessage.portnum = PortNum.adminApp dataMessage.wantResponse = true - + meshPacket.decoded = dataMessage - + let messageDescription = "🛎️ Requested External Notificaiton Module Config on admin channel \(adminIndex) for node: \(String(connectedPeripheral.num))" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { return true } return false } - + public func requestRangeTestModuleConfig(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Bool { - + var adminPacket = AdminMessage() adminPacket.getModuleConfigRequest = AdminMessage.ModuleConfigType.rangetestConfig - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -1823,26 +1764,26 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { meshPacket.priority = MeshPacket.Priority.reliable meshPacket.channel = UInt32(adminIndex) meshPacket.wantAck = true - + var dataMessage = DataMessage() dataMessage.payload = try! adminPacket.serializedData() dataMessage.portnum = PortNum.adminApp dataMessage.wantResponse = true - + meshPacket.decoded = dataMessage - + let messageDescription = "🛎️ Requested Range Test Module Config on admin channel \(adminIndex) for node: \(String(connectedPeripheral.num))" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { return true } return false } - + public func requestMqttModuleConfig(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Bool { - + var adminPacket = AdminMessage() adminPacket.getModuleConfigRequest = AdminMessage.ModuleConfigType.mqttConfig - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -1850,26 +1791,26 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { meshPacket.priority = MeshPacket.Priority.reliable meshPacket.channel = UInt32(adminIndex) meshPacket.wantAck = true - + var dataMessage = DataMessage() dataMessage.payload = try! adminPacket.serializedData() dataMessage.portnum = PortNum.adminApp dataMessage.wantResponse = true - + meshPacket.decoded = dataMessage - + let messageDescription = "🛎️ Requested MQTT Module Config on admin channel \(adminIndex) for node: \(String(connectedPeripheral.num))" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { return true } return false } - + public func requestSerialModuleConfig(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Bool { - + var adminPacket = AdminMessage() adminPacket.getModuleConfigRequest = AdminMessage.ModuleConfigType.serialConfig - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -1877,26 +1818,26 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { meshPacket.priority = MeshPacket.Priority.reliable meshPacket.channel = UInt32(adminIndex) meshPacket.wantAck = true - + var dataMessage = DataMessage() dataMessage.payload = try! adminPacket.serializedData() dataMessage.portnum = PortNum.adminApp dataMessage.wantResponse = true - + meshPacket.decoded = dataMessage - + let messageDescription = "🛎️ Requested Serial Module Config on admin channel \(adminIndex) for node: \(String(connectedPeripheral.num))" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { return true } return false } - + public func requestTelemetryModuleConfig(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Bool { - + var adminPacket = AdminMessage() adminPacket.getModuleConfigRequest = AdminMessage.ModuleConfigType.telemetryConfig - + var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -1904,29 +1845,29 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { meshPacket.priority = MeshPacket.Priority.reliable meshPacket.channel = UInt32(adminIndex) meshPacket.wantAck = true - + var dataMessage = DataMessage() dataMessage.payload = try! adminPacket.serializedData() dataMessage.portnum = PortNum.adminApp dataMessage.wantResponse = true - + meshPacket.decoded = dataMessage - + let messageDescription = "🛎️ Requested Telemetry Module Config on admin channel \(adminIndex) for node: \(String(connectedPeripheral.num))" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { return true } return false } - + // Send an admin message to a radio, save a message to core data for logging private func sendAdminMessageToRadio(meshPacket: MeshPacket, adminDescription: String, fromUser: UserEntity, toUser: UserEntity) -> Bool { - + var toRadio: ToRadio! toRadio = ToRadio() toRadio.packet = meshPacket let binaryData: Data = try! toRadio.serializedData() - + if connectedPeripheral!.peripheral.state == CBPeripheralState.connected { let newMessage = MessageEntity(context: context!) newMessage.messageId = Int64(meshPacket.id) @@ -1936,7 +1877,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { newMessage.adminDescription = adminDescription newMessage.fromUser = fromUser newMessage.toUser = toUser - + do { connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse) try context!.save() @@ -1954,42 +1895,41 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { // MARK: - CB Central Manager implmentation extension BLEManager: CBCentralManagerDelegate { - + // MARK: Bluetooth enabled/disabled func centralManagerDidUpdateState(_ central: CBCentralManager) { if central.state == CBManagerState.poweredOn { print("BLE powered on") isSwitchedOn = true startScanning() - } - else { + } else { isSwitchedOn = false } - + var status = "" switch central.state { - case .poweredOff: - status = "BLE is powered off" - case .poweredOn: - status = "BLE is poweredOn" - case .resetting: - status = "BLE is resetting" - case .unauthorized: - status = "BLE is unauthorized" - case .unknown: - status = "BLE is unknown" - case .unsupported: - status = "BLE is unsupported" - default: - status = "default" + case .poweredOff: + status = "BLE is powered off" + case .poweredOn: + status = "BLE is poweredOn" + case .resetting: + status = "BLE is resetting" + case .unauthorized: + status = "BLE is unauthorized" + case .unknown: + status = "BLE is unknown" + case .unsupported: + status = "BLE is unsupported" + default: + status = "default" } print("BLEManager status: \(status)") } - + // Called each time a peripheral is discovered func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String: Any], rssi RSSI: NSNumber) { - + if self.automaticallyReconnect && timeoutTimerRuns < 2 && peripheral.identifier.uuidString == UserDefaults.standard.object(forKey: "preferredPeripheralId") as? String ?? "" { self.connectTo(peripheral: peripheral) print("ℹ️ BLE Reconnecting to prefered peripheral: \(peripheral.name ?? "Unknown")") @@ -1997,7 +1937,7 @@ extension BLEManager: CBCentralManagerDelegate { let name = advertisementData[CBAdvertisementDataLocalNameKey] as? String let device = Peripheral(id: peripheral.identifier.uuidString, num: 0, name: name ?? "Unknown", shortName: "????", longName: name ?? "Unknown", firmwareVersion: "Unknown", rssi: RSSI.intValue, lastUpdate: Date(), peripheral: peripheral) let index = peripherals.map { $0.peripheral }.firstIndex(of: peripheral) - + if let peripheralIndex = index { peripherals[peripheralIndex] = device } else { @@ -2007,7 +1947,7 @@ extension BLEManager: CBCentralManagerDelegate { let visibleDuration = Calendar.current.date(byAdding: .second, value: -5, to: today)! self.peripherals.removeAll(where: { $0.lastUpdate < visibleDuration}) } - + // func centralManager(_ central: CBCentralManager, willRestoreState dict: [String : Any]) { // // guard let peripherals = dict[CBCentralManagerRestoredStatePeripheralsKey] as? [CBPeripheral] else { diff --git a/Meshtastic/Helpers/EmojiOnlyTextField.swift b/Meshtastic/Helpers/EmojiOnlyTextField.swift index ea30fb37..04736020 100644 --- a/Meshtastic/Helpers/EmojiOnlyTextField.swift +++ b/Meshtastic/Helpers/EmojiOnlyTextField.swift @@ -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") -// } -//} diff --git a/Meshtastic/Helpers/Extensions.swift b/Meshtastic/Helpers/Extensions.swift index 3437086e..9345d7ea 100644 --- a/Meshtastic/Helpers/Extensions.swift +++ b/Meshtastic/Helpers/Extensions.swift @@ -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) diff --git a/Meshtastic/Helpers/LocationHelper.swift b/Meshtastic/Helpers/LocationHelper.swift index df0a19fd..b5350426 100644 --- a/Meshtastic/Helpers/LocationHelper.swift +++ b/Meshtastic/Helpers/LocationHelper.swift @@ -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 { diff --git a/Meshtastic/Helpers/MeshLogger.swift b/Meshtastic/Helpers/MeshLogger.swift index 51d2a144..f8699376 100644 --- a/Meshtastic/Helpers/MeshLogger.swift +++ b/Meshtastic/Helpers/MeshLogger.swift @@ -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()) diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index 75b3fb2e..c5c6a377 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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.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 = 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 = 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 = 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() diff --git a/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion b/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion index 25c323b3..6e12995e 100644 --- a/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion +++ b/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion @@ -3,6 +3,6 @@ _XCCurrentVersionName - MeshtasticDataModelV8.xcdatamodel + MeshtasticDataModelV9.xcdatamodel diff --git a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV9.xcdatamodel/contents b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV9.xcdatamodel/contents new file mode 100644 index 00000000..13847836 --- /dev/null +++ b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV9.xcdatamodel/contents @@ -0,0 +1,305 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Meshtastic/MeshtasticApp.swift b/Meshtastic/MeshtasticApp.swift index 7d9b80cf..c37b214f 100644 --- a/Meshtastic/MeshtasticApp.swift +++ b/Meshtastic/MeshtasticApp.swift @@ -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") } diff --git a/Meshtastic/Model/PeripheralModel.swift b/Meshtastic/Model/PeripheralModel.swift index 4da4b264..1b71e9e7 100644 --- a/Meshtastic/Model/PeripheralModel.swift +++ b/Meshtastic/Model/PeripheralModel.swift @@ -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 } } diff --git a/Meshtastic/Model/UserSettings.swift b/Meshtastic/Model/UserSettings.swift index 0f759ee8..5dacf1a7 100644 --- a/Meshtastic/Model/UserSettings.swift +++ b/Meshtastic/Model/UserSettings.swift @@ -62,7 +62,7 @@ class UserSettings: ObservableObject { UserDefaults.standard.synchronize() } } - + init() { self.meshtasticUsername = UserDefaults.standard.object(forKey: "meshtasticusername") as? String ?? "" diff --git a/Meshtastic/Persistence/ChannelEntityExtension.swift b/Meshtastic/Persistence/ChannelEntityExtension.swift index de57a556..8f6562e5 100644 --- a/Meshtastic/Persistence/ChannelEntityExtension.swift +++ b/Meshtastic/Persistence/ChannelEntityExtension.swift @@ -7,9 +7,9 @@ import Foundation extension ChannelEntity { - + var allPrivateMessages: [MessageEntity] { - + self.value(forKey: "allPrivateMessages") as? [MessageEntity] ?? [MessageEntity]() } } diff --git a/Meshtastic/Persistence/Persistence.swift b/Meshtastic/Persistence/Persistence.swift index 325e4d45..d4a86742 100644 --- a/Meshtastic/Persistence/Persistence.swift +++ b/Meshtastic/Persistence/Persistence.swift @@ -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]) } } diff --git a/Meshtastic/Persistence/PositionEntityExtension.swift b/Meshtastic/Persistence/PositionEntityExtension.swift index 23ec54b2..b671e6e8 100644 --- a/Meshtastic/Persistence/PositionEntityExtension.swift +++ b/Meshtastic/Persistence/PositionEntityExtension.swift @@ -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 { diff --git a/Meshtastic/Persistence/QueryCoreData.swift b/Meshtastic/Persistence/QueryCoreData.swift index a026ad8d..cf1a3190 100644 --- a/Meshtastic/Persistence/QueryCoreData.swift +++ b/Meshtastic/Persistence/QueryCoreData.swift @@ -8,14 +8,16 @@ import CoreData public func getNodeInfo(id: Int64, context: NSManagedObjectContext) -> NodeInfoEntity? { - + let fetchNodeInfoRequest: NSFetchRequest = 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 = 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 = 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] } diff --git a/Meshtastic/Persistence/UpdateCoreData.swift b/Meshtastic/Persistence/UpdateCoreData.swift index e04170a6..6ba49d8e 100644 --- a/Meshtastic/Persistence/UpdateCoreData.swift +++ b/Meshtastic/Persistence/UpdateCoreData.swift @@ -7,26 +7,24 @@ import CoreData public func clearPositions(destNum: Int64, context: NSManagedObjectContext) -> Bool { - + let fetchNodeInfoRequest: NSFetchRequest = 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 = 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(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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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)") diff --git a/Meshtastic/Persistence/UserEntityExtension.swift b/Meshtastic/Persistence/UserEntityExtension.swift index 6b2c2c1a..1d2dd3da 100644 --- a/Meshtastic/Persistence/UserEntityExtension.swift +++ b/Meshtastic/Persistence/UserEntityExtension.swift @@ -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]() } } diff --git a/Meshtastic/Persistence/WaypointEntityExtension.swift b/Meshtastic/Persistence/WaypointEntityExtension.swift index bc897615..1744c30e 100644 --- a/Meshtastic/Persistence/WaypointEntityExtension.swift +++ b/Meshtastic/Persistence/WaypointEntityExtension.swift @@ -37,7 +37,7 @@ extension WaypointEntity { return nil } } - + var annotaton: MKPointAnnotation { let pointAnn = MKPointAnnotation() if waypointCoordinate != nil { diff --git a/Meshtastic/Protobufs/meshtastic/admin.pb.swift b/Meshtastic/Protobufs/meshtastic/admin.pb.swift index eb4aafba..8deabc66 100644 --- a/Meshtastic/Protobufs/meshtastic/admin.pb.swift +++ b/Meshtastic/Protobufs/meshtastic/admin.pb.swift @@ -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(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(decoder: inout D) throws { diff --git a/Meshtastic/Protobufs/meshtastic/apponly.pb.swift b/Meshtastic/Protobufs/meshtastic/apponly.pb.swift index dfa98782..03d3695a 100644 --- a/Meshtastic/Protobufs/meshtastic/apponly.pb.swift +++ b/Meshtastic/Protobufs/meshtastic/apponly.pb.swift @@ -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(decoder: inout D) throws { diff --git a/Meshtastic/Protobufs/meshtastic/cannedmessages.pb.swift b/Meshtastic/Protobufs/meshtastic/cannedmessages.pb.swift index 7d70da3b..9b01a442 100644 --- a/Meshtastic/Protobufs/meshtastic/cannedmessages.pb.swift +++ b/Meshtastic/Protobufs/meshtastic/cannedmessages.pb.swift @@ -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(decoder: inout D) throws { diff --git a/Meshtastic/Protobufs/meshtastic/channel.pb.swift b/Meshtastic/Protobufs/meshtastic/channel.pb.swift index f635dddd..8daac7aa 100644 --- a/Meshtastic/Protobufs/meshtastic/channel.pb.swift +++ b/Meshtastic/Protobufs/meshtastic/channel.pb.swift @@ -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(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(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") ] } diff --git a/Meshtastic/Protobufs/meshtastic/config.pb.swift b/Meshtastic/Protobufs/meshtastic/config.pb.swift index 580fd84e..3707b384 100644 --- a/Meshtastic/Protobufs/meshtastic/config.pb.swift +++ b/Meshtastic/Protobufs/meshtastic/config.pb.swift @@ -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(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(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(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(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(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(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(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(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(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") ] } diff --git a/Meshtastic/Protobufs/meshtastic/connection_status.pb.swift b/Meshtastic/Protobufs/meshtastic/connection_status.pb.swift index 2fc853a3..1b36cd25 100644 --- a/Meshtastic/Protobufs/meshtastic/connection_status.pb.swift +++ b/Meshtastic/Protobufs/meshtastic/connection_status.pb.swift @@ -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(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(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(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(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(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(decoder: inout D) throws { diff --git a/Meshtastic/Protobufs/meshtastic/deviceonly.pb.swift b/Meshtastic/Protobufs/meshtastic/deviceonly.pb.swift index 905574b1..1b526036 100644 --- a/Meshtastic/Protobufs/meshtastic/deviceonly.pb.swift +++ b/Meshtastic/Protobufs/meshtastic/deviceonly.pb.swift @@ -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(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(decoder: inout D) throws { diff --git a/Meshtastic/Protobufs/meshtastic/localonly.pb.swift b/Meshtastic/Protobufs/meshtastic/localonly.pb.swift index 7f16ac02..97fa0b18 100644 --- a/Meshtastic/Protobufs/meshtastic/localonly.pb.swift +++ b/Meshtastic/Protobufs/meshtastic/localonly.pb.swift @@ -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() diff --git a/Meshtastic/Protobufs/meshtastic/mesh.pb.swift b/Meshtastic/Protobufs/meshtastic/mesh.pb.swift index 7fa22878..1602d428 100644 --- a/Meshtastic/Protobufs/meshtastic/mesh.pb.swift +++ b/Meshtastic/Protobufs/meshtastic/mesh.pb.swift @@ -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(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(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(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(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(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(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(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(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(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(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(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(decoder: inout D) throws { diff --git a/Meshtastic/Protobufs/meshtastic/module_config.pb.swift b/Meshtastic/Protobufs/meshtastic/module_config.pb.swift index 914b5f9f..456a18ca 100644 --- a/Meshtastic/Protobufs/meshtastic/module_config.pb.swift +++ b/Meshtastic/Protobufs/meshtastic/module_config.pb.swift @@ -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(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(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(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(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(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(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(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(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(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(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") ] } diff --git a/Meshtastic/Protobufs/meshtastic/mqtt.pb.swift b/Meshtastic/Protobufs/meshtastic/mqtt.pb.swift index 73fe4c30..de297a69 100644 --- a/Meshtastic/Protobufs/meshtastic/mqtt.pb.swift +++ b/Meshtastic/Protobufs/meshtastic/mqtt.pb.swift @@ -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(decoder: inout D) throws { diff --git a/Meshtastic/Protobufs/meshtastic/portnums.pb.swift b/Meshtastic/Protobufs/meshtastic/portnums.pb.swift index 9b7198a5..5bd3c705 100644 --- a/Meshtastic/Protobufs/meshtastic/portnums.pb.swift +++ b/Meshtastic/Protobufs/meshtastic/portnums.pb.swift @@ -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") ] } diff --git a/Meshtastic/Protobufs/meshtastic/remote_hardware.pb.swift b/Meshtastic/Protobufs/meshtastic/remote_hardware.pb.swift index 1f24d0db..2b257297 100644 --- a/Meshtastic/Protobufs/meshtastic/remote_hardware.pb.swift +++ b/Meshtastic/Protobufs/meshtastic/remote_hardware.pb.swift @@ -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(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") ] } diff --git a/Meshtastic/Protobufs/meshtastic/rtttl.pb.swift b/Meshtastic/Protobufs/meshtastic/rtttl.pb.swift index 3adac8a2..2780c6ad 100644 --- a/Meshtastic/Protobufs/meshtastic/rtttl.pb.swift +++ b/Meshtastic/Protobufs/meshtastic/rtttl.pb.swift @@ -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(decoder: inout D) throws { diff --git a/Meshtastic/Protobufs/meshtastic/storeforward.pb.swift b/Meshtastic/Protobufs/meshtastic/storeforward.pb.swift index 925eb558..d467b249 100644 --- a/Meshtastic/Protobufs/meshtastic/storeforward.pb.swift +++ b/Meshtastic/Protobufs/meshtastic/storeforward.pb.swift @@ -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(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(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(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(decoder: inout D) throws { diff --git a/Meshtastic/Protobufs/meshtastic/telemetry.pb.swift b/Meshtastic/Protobufs/meshtastic/telemetry.pb.swift index 42fe5ab8..01fa9e2e 100644 --- a/Meshtastic/Protobufs/meshtastic/telemetry.pb.swift +++ b/Meshtastic/Protobufs/meshtastic/telemetry.pb.swift @@ -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(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(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(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(decoder: inout D) throws { diff --git a/Meshtastic/Protobufs/meshtastic/xmodem.pb.swift b/Meshtastic/Protobufs/meshtastic/xmodem.pb.swift index a70841f2..15d977b1 100644 --- a/Meshtastic/Protobufs/meshtastic/xmodem.pb.swift +++ b/Meshtastic/Protobufs/meshtastic/xmodem.pb.swift @@ -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(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") ] } diff --git a/Meshtastic/Views/Bluetooth/Connect.swift b/Meshtastic/Views/Bluetooth/Connect.swift index 80912e6a..9a2eac76 100644 --- a/Meshtastic/Views/Bluetooth/Connect.swift +++ b/Meshtastic/Views/Bluetooth/Connect.swift @@ -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 = 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.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) } diff --git a/Meshtastic/Views/Bluetooth/InvalidVersion.swift b/Meshtastic/Views/Bluetooth/InvalidVersion.swift index a92785cf..9c0cca78 100644 --- a/Meshtastic/Views/Bluetooth/InvalidVersion.swift +++ b/Meshtastic/Views/Bluetooth/InvalidVersion.swift @@ -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() } } diff --git a/Meshtastic/Views/ContentView.swift b/Meshtastic/Views/ContentView.swift index 945edcc2..448d5696 100644 --- a/Meshtastic/Views/ContentView.swift +++ b/Meshtastic/Views/ContentView.swift @@ -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") diff --git a/Meshtastic/Views/Helpers/BatteryGauge.swift b/Meshtastic/Views/Helpers/BatteryGauge.swift index 503d7c68..c2756be5 100644 --- a/Meshtastic/Views/Helpers/BatteryGauge.swift +++ b/Meshtastic/Views/Helpers/BatteryGauge.swift @@ -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) diff --git a/Meshtastic/Views/Helpers/CircleText.swift b/Meshtastic/Views/Helpers/CircleText.swift index 603cbc5a..b4b51515 100644 --- a/Meshtastic/Views/Helpers/CircleText.swift +++ b/Meshtastic/Views/Helpers/CircleText.swift @@ -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) diff --git a/Meshtastic/Views/Helpers/DateTimeText.swift b/Meshtastic/Views/Helpers/DateTimeText.swift index 068664fe..0fcabf76 100644 --- a/Meshtastic/Views/Helpers/DateTimeText.swift +++ b/Meshtastic/Views/Helpers/DateTimeText.swift @@ -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") } } diff --git a/Meshtastic/Views/Helpers/DistanceText.swift b/Meshtastic/Views/Helpers/DistanceText.swift index 5716ad57..0812e14f 100644 --- a/Meshtastic/Views/Helpers/DistanceText.swift +++ b/Meshtastic/Views/Helpers/DistanceText.swift @@ -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) diff --git a/Meshtastic/Views/Helpers/LastHeardText.swift b/Meshtastic/Views/Helpers/LastHeardText.swift index d4a877e9..1aaaa959 100644 --- a/Meshtastic/Views/Helpers/LastHeardText.swift +++ b/Meshtastic/Views/Helpers/LastHeardText.swift @@ -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") diff --git a/Meshtastic/Views/Helpers/MeshtasticLogo.swift b/Meshtastic/Views/Helpers/MeshtasticLogo.swift index a790e13a..84040d92 100644 --- a/Meshtastic/Views/Helpers/MeshtasticLogo.swift +++ b/Meshtastic/Views/Helpers/MeshtasticLogo.swift @@ -7,11 +7,11 @@ import SwiftUI struct MeshtasticLogo: View { - + @Environment(\.colorScheme) var colorScheme - + var body: some View { - + #if targetEnvironment(macCatalyst) VStack { Image("logo-white") diff --git a/Meshtastic/Views/Helpers/Messages/MessageTemplate.swift b/Meshtastic/Views/Helpers/Messages/MessageTemplate.swift index 5cb63190..e604b564 100644 --- a/Meshtastic/Views/Helpers/Messages/MessageTemplate.swift +++ b/Meshtastic/Views/Helpers/Messages/MessageTemplate.swift @@ -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 - + } } diff --git a/Meshtastic/Views/Helpers/NodeAnnotation.swift b/Meshtastic/Views/Helpers/NodeAnnotation.swift index 5de5656c..b2d592cb 100644 --- a/Meshtastic/Views/Helpers/NodeAnnotation.swift +++ b/Meshtastic/Views/Helpers/NodeAnnotation.swift @@ -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) diff --git a/Meshtastic/Views/Helpers/SignalStrengthIndicator.swift b/Meshtastic/Views/Helpers/SignalStrengthIndicator.swift index 29b020b1..afb84f43 100644 --- a/Meshtastic/Views/Helpers/SignalStrengthIndicator.swift +++ b/Meshtastic/Views/Helpers/SignalStrengthIndicator.swift @@ -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 diff --git a/Meshtastic/Views/Helpers/Weather/AirQualityIndexCompact.swift b/Meshtastic/Views/Helpers/Weather/AirQualityIndexCompact.swift index bf691666..642a0596 100644 --- a/Meshtastic/Views/Helpers/Weather/AirQualityIndexCompact.swift +++ b/Meshtastic/Views/Helpers/Weather/AirQualityIndexCompact.swift @@ -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) diff --git a/Meshtastic/Views/Helpers/Weather/CurrentConditionsCompact.swift b/Meshtastic/Views/Helpers/Weather/CurrentConditionsCompact.swift index 828e9c86..fe166e48 100644 --- a/Meshtastic/Views/Helpers/Weather/CurrentConditionsCompact.swift +++ b/Meshtastic/Views/Helpers/Weather/CurrentConditionsCompact.swift @@ -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) diff --git a/Meshtastic/Views/Helpers/Weather/NodeWeatherForecast.swift b/Meshtastic/Views/Helpers/Weather/NodeWeatherForecast.swift index d5ff2d30..28dcfeb7 100644 --- a/Meshtastic/Views/Helpers/Weather/NodeWeatherForecast.swift +++ b/Meshtastic/Views/Helpers/Weather/NodeWeatherForecast.swift @@ -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] { var currentLowerBound: Date? var results: [Range] = [] @@ -192,7 +192,7 @@ struct NodeWeatherForecast { } return results } - + var binRange: ClosedRange { 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 } diff --git a/Meshtastic/Views/Map/Custom/LocalMBTileOverlay.swift b/Meshtastic/Views/Map/Custom/LocalMBTileOverlay.swift index 11c5af3e..6ab30afb 100644 --- a/Meshtastic/Views/Map/Custom/LocalMBTileOverlay.swift +++ b/Meshtastic/Views/Map/Custom/LocalMBTileOverlay.swift @@ -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("name") let value = Expression("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("zoom_level") let tileColumn = Expression("tile_column") let tileRow = Expression("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)") diff --git a/Meshtastic/Views/Map/Custom/MapViewFitExtension.swift b/Meshtastic/Views/Map/Custom/MapViewFitExtension.swift index 52c13eaa..a5c8ef63 100644 --- a/Meshtastic/Views/Map/Custom/MapViewFitExtension.swift +++ b/Meshtastic/Views/Map/Custom/MapViewFitExtension.swift @@ -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) } } diff --git a/Meshtastic/Views/Map/Custom/MapViewModule.swift b/Meshtastic/Views/Map/Custom/MapViewModule.swift deleted file mode 100644 index cba18980..00000000 --- a/Meshtastic/Views/Map/Custom/MapViewModule.swift +++ /dev/null @@ -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 -// -// 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 = .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 diff --git a/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift b/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift index 22d949e7..a71cc4e8 100644 --- a/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift +++ b/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift @@ -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, diff --git a/Meshtastic/Views/Map/Custom/PositionAnnotationView.swift b/Meshtastic/Views/Map/Custom/PositionAnnotationView.swift deleted file mode 100644 index cfb843c3..00000000 --- a/Meshtastic/Views/Map/Custom/PositionAnnotationView.swift +++ /dev/null @@ -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) -// } -//} diff --git a/Meshtastic/Views/Map/WaypointFormView.swift b/Meshtastic/Views/Map/WaypointFormView.swift index d43cf860..0127e6f8 100644 --- a/Meshtastic/Views/Map/WaypointFormView.swift +++ b/Meshtastic/Views/Map/WaypointFormView.swift @@ -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 { diff --git a/Meshtastic/Views/Messages/ChannelMessageList.swift b/Meshtastic/Views/Messages/ChannelMessageList.swift index 88a15d4f..531c3672 100644 --- a/Meshtastic/Views/Messages/ChannelMessageList.swift +++ b/Meshtastic/Views/Messages/ChannelMessageList.swift @@ -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) diff --git a/Meshtastic/Views/Messages/Contacts.swift b/Meshtastic/Views/Messages/Contacts.swift index 75b81102..3a193e4f 100644 --- a/Meshtastic/Views/Messages/Contacts.swift +++ b/Meshtastic/Views/Messages/Contacts.swift @@ -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 - @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 = 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") } diff --git a/Meshtastic/Views/Messages/UserMessageList.swift b/Meshtastic/Views/Messages/UserMessageList.swift index 707796c5..87304f23 100644 --- a/Meshtastic/Views/Messages/UserMessageList.swift +++ b/Meshtastic/Views/Messages/UserMessageList.swift @@ -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) diff --git a/Meshtastic/Views/Nodes/DeviceMetricsLog.swift b/Meshtastic/Views/Nodes/DeviceMetricsLog.swift index 5d27efa3..3456447b 100644 --- a/Meshtastic/Views/Nodes/DeviceMetricsLog.swift +++ b/Meshtastic/Views/Nodes/DeviceMetricsLog.swift @@ -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).") } diff --git a/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift b/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift index 1fc40fa4..4758a95f 100644 --- a/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift +++ b/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift @@ -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") diff --git a/Meshtastic/Views/Nodes/NodeDetail.swift b/Meshtastic/Views/Nodes/NodeDetail.swift index 8f4c1cbf..d13bd286 100644 --- a/Meshtastic/Views/Nodes/NodeDetail.swift +++ b/Meshtastic/Views/Nodes/NodeDetail.swift @@ -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 - + /// The current weather condition for the city. @State private var condition: WeatherCondition? @State private var temperature: Measurement? @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 diff --git a/Meshtastic/Views/Nodes/NodeList.swift b/Meshtastic/Views/Nodes/NodeList.swift index bb8cdc17..983b9846 100644 --- a/Meshtastic/Views/Nodes/NodeList.swift +++ b/Meshtastic/Views/Nodes/NodeList.swift @@ -23,12 +23,12 @@ struct NodeList: View { private var nodes: FetchedResults - @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") } diff --git a/Meshtastic/Views/Nodes/NodeMap.swift b/Meshtastic/Views/Nodes/NodeMap.swift index 3d35ae36..59e42419 100644 --- a/Meshtastic/Views/Nodes/NodeMap.swift +++ b/Meshtastic/Views/Nodes/NodeMap.swift @@ -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 - + @FetchRequest(sortDescriptors: [NSSortDescriptor(key: "name", ascending: false)], predicate: NSPredicate( format: "expire == nil || expire >= %@", Date() as NSDate ), animation: .none) private var waypoints: FetchedResults - + @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 }) } diff --git a/Meshtastic/Views/Nodes/PositionLog.swift b/Meshtastic/Views/Nodes/PositionLog.swift index b4b67c23..d27997ba 100644 --- a/Meshtastic/Views/Nodes/PositionLog.swift +++ b/Meshtastic/Views/Nodes/PositionLog.swift @@ -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).") } } diff --git a/Meshtastic/Views/Settings/About.swift b/Meshtastic/Views/Settings/About.swift index 43e67809..8edb32a4 100644 --- a/Meshtastic/Views/Settings/About.swift +++ b/Meshtastic/Views/Settings/About.swift @@ -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") { diff --git a/Meshtastic/Views/Settings/AdminMessageList.swift b/Meshtastic/Views/Settings/AdminMessageList.swift index 13d798c8..309c703b 100644 --- a/Meshtastic/Views/Settings/AdminMessageList.swift +++ b/Meshtastic/Views/Settings/AdminMessageList.swift @@ -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) diff --git a/Meshtastic/Views/Settings/AppSettings.swift b/Meshtastic/Views/Settings/AppSettings.swift index 5e1ebdb2..70b525cc 100644 --- a/Meshtastic/Views/Settings/AppSettings.swift +++ b/Meshtastic/Views/Settings/AppSettings.swift @@ -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() } diff --git a/Meshtastic/Views/Settings/Channels.swift b/Meshtastic/Views/Settings/Channels.swift index 52ef1d8d..6e2eba28 100644 --- a/Meshtastic/Views/Settings/Channels.swift +++ b/Meshtastic/Views/Settings/Channels.swift @@ -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") diff --git a/Meshtastic/Views/Settings/Config/BluetoothConfig.swift b/Meshtastic/Views/Settings/Config/BluetoothConfig.swift index 854d53c1..b81cd053 100644 --- a/Meshtastic/Views/Settings/Config/BluetoothConfig.swift +++ b/Meshtastic/Views/Settings/Config/BluetoothConfig.swift @@ -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) } } diff --git a/Meshtastic/Views/Settings/Config/DeviceConfig.swift b/Meshtastic/Views/Settings/Config/DeviceConfig.swift index 67f87bca..50772f9b 100644 --- a/Meshtastic/Views/Settings/Config/DeviceConfig.swift +++ b/Meshtastic/Views/Settings/Config/DeviceConfig.swift @@ -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 } } } diff --git a/Meshtastic/Views/Settings/Config/DisplayConfig.swift b/Meshtastic/Views/Settings/Config/DisplayConfig.swift index f9da9c3e..8172b5f0 100644 --- a/Meshtastic/Views/Settings/Config/DisplayConfig.swift +++ b/Meshtastic/Views/Settings/Config/DisplayConfig.swift @@ -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) } } diff --git a/Meshtastic/Views/Settings/Config/LoRaConfig.swift b/Meshtastic/Views/Settings/Config/LoRaConfig.swift index cbc0f286..87b2f54a 100644 --- a/Meshtastic/Views/Settings/Config/LoRaConfig.swift +++ b/Meshtastic/Views/Settings/Config/LoRaConfig.swift @@ -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 } + } + } } } diff --git a/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift b/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift index f3cb7af4..0c0d805d 100644 --- a/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift @@ -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 diff --git a/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift b/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift index b1dcdf60..f31b3c74 100644 --- a/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift @@ -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) } } diff --git a/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift b/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift index 7872d90a..96b72da0 100644 --- a/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift @@ -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) } } diff --git a/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift b/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift index e3e9ef94..0460bfc2 100644 --- a/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift @@ -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) } } diff --git a/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift b/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift index 2eb1d9e8..5d0f830c 100644 --- a/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift @@ -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 } } } diff --git a/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift b/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift index bbcda4e0..feff320a 100644 --- a/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift @@ -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) } } diff --git a/Meshtastic/Views/Settings/Config/NetworkConfig.swift b/Meshtastic/Views/Settings/Config/NetworkConfig.swift index 33744b64..c7e1f663 100644 --- a/Meshtastic/Views/Settings/Config/NetworkConfig.swift +++ b/Meshtastic/Views/Settings/Config/NetworkConfig.swift @@ -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) } } diff --git a/Meshtastic/Views/Settings/Config/PositionConfig.swift b/Meshtastic/Views/Settings/Config/PositionConfig.swift index bec9b268..a080e1a2 100644 --- a/Meshtastic/Views/Settings/Config/PositionConfig.swift +++ b/Meshtastic/Views/Settings/Config/PositionConfig.swift @@ -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) } } diff --git a/Meshtastic/Views/Settings/MeshLog.swift b/Meshtastic/Views/Settings/MeshLog.swift index 63f86766..a5f27761 100644 --- a/Meshtastic/Views/Settings/MeshLog.swift +++ b/Meshtastic/Views/Settings/MeshLog.swift @@ -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: { diff --git a/Meshtastic/Views/Settings/SaveChannelQRCode.swift b/Meshtastic/Views/Settings/SaveChannelQRCode.swift index c556dcfa..7a825bb5 100644 --- a/Meshtastic/Views/Settings/SaveChannelQRCode.swift +++ b/Meshtastic/Views/Settings/SaveChannelQRCode.swift @@ -7,9 +7,9 @@ import SwiftUI struct SaveChannelQRCode: View { - + @Environment(\.dismiss) private var dismiss - + var channelSetLink: String var bleManager: BLEManager @State var connectedToDevice = false @@ -22,15 +22,15 @@ struct SaveChannelQRCode: View { .foregroundColor(.gray) .font(.callout) .padding() - + HStack { - + Button { let success = bleManager.saveChannelSet(base64UrlString: channelSetLink) if success { dismiss() } - + } label: { Label("save", systemImage: "square.and.arrow.down") } @@ -39,13 +39,13 @@ struct SaveChannelQRCode: View { .controlSize(.large) .padding() .disabled(!connectedToDevice) - + #if targetEnvironment(macCatalyst) Button { dismiss() } label: { Label("cancel", systemImage: "xmark") - + } .buttonStyle(.bordered) .buttonBorderShape(.capsule) diff --git a/Meshtastic/Views/Settings/Settings.swift b/Meshtastic/Views/Settings/Settings.swift index 38874011..edcba954 100644 --- a/Meshtastic/Views/Settings/Settings.swift +++ b/Meshtastic/Views/Settings/Settings.swift @@ -8,7 +8,7 @@ import SwiftUI struct Settings: View { - + @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager @EnvironmentObject var userSettings: UserSettings @@ -17,9 +17,9 @@ struct Settings: View { @State private var selectedNode: Int = 0 @State private var connectedNodeNum: Int = 0 @State private var initialLoad: Bool = true - + @State private var selection: SettingsSidebar = .about - + enum SettingsSidebar { case appSettings case shareChannels @@ -41,11 +41,11 @@ struct Settings: View { case adminMessageLog case about } - + var body: some View { NavigationSplitView { List { - NavigationLink() { + NavigationLink { AppSettings() } label: { Image(systemName: "gearshape") @@ -80,10 +80,10 @@ struct Settings: View { let node = nodes.first(where: { $0.num == newValue }) let connectedNode = nodes.first(where: { $0.num == connectedNodeNum }) connectedNodeNum = Int(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral?.num ?? 0 : 0) - + if node?.metadata == nil { let adminMessageId = bleManager.requestDeviceMetadata(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode!.myInfo!.adminIndex, context: context) - + if adminMessageId > 0 { print("Sent node metadata request from node details") } @@ -92,9 +92,9 @@ struct Settings: View { } } } - + Section("radio.configuration") { - + NavigationLink { ShareChannels(node: nodes.first(where: { $0.num == connectedNodeNum })) } label: { @@ -104,18 +104,18 @@ struct Settings: View { } .tag(SettingsSidebar.shareChannels) .disabled(selectedNode > 0 && selectedNode != connectedNodeNum) - + NavigationLink { UserConfig(node: nodes.first(where: { $0.num == selectedNode })) } label: { - + Image(systemName: "person.crop.rectangle.fill") .symbolRenderingMode(.hierarchical) Text("user") } .tag(SettingsSidebar.userConfig) - - NavigationLink() { + + NavigationLink { LoRaConfig(node: nodes.first(where: { $0.num == selectedNode })) } label: { Image(systemName: "dot.radiowaves.left.and.right") @@ -123,8 +123,8 @@ struct Settings: View { Text("lora") } .tag(SettingsSidebar.loraConfig) - - NavigationLink() { + + NavigationLink { Channels(node: nodes.first(where: { $0.num == connectedNodeNum })) } label: { Image(systemName: "fibrechannel") @@ -133,8 +133,8 @@ struct Settings: View { } .tag(SettingsSidebar.channelConfig) .disabled(selectedNode > 0 && selectedNode != connectedNodeNum) - - NavigationLink() { + + NavigationLink { BluetoothConfig(node: nodes.first(where: { $0.num == selectedNode })) } label: { Image(systemName: "antenna.radiowaves.left.and.right") @@ -142,7 +142,7 @@ struct Settings: View { Text("bluetooth") } .tag(SettingsSidebar.bluetoothConfig) - + NavigationLink { DeviceConfig(node: nodes.first(where: { $0.num == selectedNode })) } label: { @@ -151,7 +151,7 @@ struct Settings: View { Text("device") } .tag(SettingsSidebar.deviceConfig) - + NavigationLink { DisplayConfig(node: nodes.first(where: { $0.num == selectedNode })) } label: { @@ -160,30 +160,30 @@ struct Settings: View { Text("display") } .tag(SettingsSidebar.displayConfig) - + NavigationLink { NetworkConfig(node: nodes.first(where: { $0.num == selectedNode })) } label: { - + Image(systemName: "network") .symbolRenderingMode(.hierarchical) Text("network") } .tag(SettingsSidebar.networkConfig) - + NavigationLink { PositionConfig(node: nodes.first(where: { $0.num == selectedNode })) } label: { - + Image(systemName: "location") .symbolRenderingMode(.hierarchical) Text("position") } .tag(SettingsSidebar.positionConfig) - + } Section("module.configuration") { - + NavigationLink { CannedMessagesConfig(node: nodes.first(where: { $0.num == selectedNode })) } label: { @@ -194,7 +194,7 @@ struct Settings: View { Text("canned.messages") } .tag(SettingsSidebar.cannedMessagesConfig) - + NavigationLink { ExternalNotificationConfig(node: nodes.first(where: { $0.num == selectedNode })) } label: { @@ -203,7 +203,7 @@ struct Settings: View { Text("external.notification") } .tag(SettingsSidebar.externalNotificationConfig) - + NavigationLink { MQTTConfig(node: nodes.first(where: { $0.num == selectedNode })) } label: { @@ -212,7 +212,7 @@ struct Settings: View { Text("mqtt") } .tag(SettingsSidebar.mqttConfig) - + NavigationLink { RangeTestConfig(node: nodes.first(where: { $0.num == selectedNode })) } label: { @@ -221,7 +221,7 @@ struct Settings: View { Text("range.test") } .tag(SettingsSidebar.rangeTestConfig) - + NavigationLink { SerialConfig(node: nodes.first(where: { $0.num == selectedNode })) } label: { @@ -230,7 +230,7 @@ struct Settings: View { Text("serial") } .tag(SettingsSidebar.serialConfig) - + NavigationLink { TelemetryConfig(node: nodes.first(where: { $0.num == selectedNode })) } label: { @@ -249,7 +249,7 @@ struct Settings: View { Text("mesh.log") } .tag(SettingsSidebar.meshLog) - + NavigationLink { let connectedNode = nodes.first(where: { $0.num == connectedNodeNum }) AdminMessageList(user: connectedNode?.user) @@ -266,7 +266,7 @@ struct Settings: View { } label: { Image(systemName: "questionmark.app") .symbolRenderingMode(.hierarchical) - + Text("about.meshtastic") } .tag(SettingsSidebar.about) @@ -280,7 +280,7 @@ struct Settings: View { selectedNode = Int(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral?.num ?? 0 : 0) initialLoad = false } - + } .listStyle(GroupedListStyle()) .navigationTitle("settings") diff --git a/Meshtastic/Views/Settings/ShareChannels.swift b/Meshtastic/Views/Settings/ShareChannels.swift index 79d91a28..9a9eda8f 100644 --- a/Meshtastic/Views/Settings/ShareChannels.swift +++ b/Meshtastic/Views/Settings/ShareChannels.swift @@ -10,13 +10,13 @@ import CoreImage.CIFilterBuiltins struct QrCodeImage { let context = CIContext() - + func generateQRCode(from text: String) -> UIImage { var qrImage = UIImage(systemName: "xmark.circle") ?? UIImage() let data = Data(text.utf8) let filter = CIFilter.qrCodeGenerator() filter.setValue(data, forKey: "inputMessage") - + let transform = CGAffineTransform(scaleX: 20, y: 20) if let outputImage = filter.outputImage?.transformed(by: transform) { if let image = context.createCGImage( @@ -30,7 +30,7 @@ struct QrCodeImage { } struct ShareChannels: View { - + @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager @Environment(\.dismiss) private var dismiss @@ -47,13 +47,13 @@ struct ShareChannels: View { var node: NodeInfoEntity? @State private var channelsUrl = "https://www.meshtastic.org/e/#" var qrCodeImage = QrCodeImage() - + var body: some View { GeometryReader { bounds in let smallest = min(bounds.size.width, bounds.size.height) ScrollView { if node != nil && node?.myInfo != nil { - Grid() { + Grid { GridRow { Spacer() Text("include") @@ -195,7 +195,7 @@ struct ShareChannels: View { .buttonBorderShape(.capsule) .controlSize(.large) .padding(.bottom) - + Image(uiImage: qrImage) .resizable() .scaledToFit() @@ -227,21 +227,21 @@ struct ShareChannels: View { Text("Primary Channel").font(.title2) Text("The first channel is the Primary channel and is where much of the mesh activity takes place. DM's are only available on the primary channel and it can not be disabled.") .font(.callout) - .padding([.leading,.trailing,.bottom]) + .padding([.leading, .trailing, .bottom]) Text("Admin Channel").font(.title2) Text("A channel with the name 'admin' is the Admin channel and can be used to remotely administer nodes on your mesh, text messages can not be sent over the admin channel.") .font(.callout) - .padding([.leading,.trailing,.bottom]) + .padding([.leading, .trailing, .bottom]) Text("Private Channels").font(.title2) Text("The other channels can be used for private group converations. Each of these groups has its own encryption key.") .font(.callout) - .padding([.leading,.trailing,.bottom]) + .padding([.leading, .trailing, .bottom]) Divider() } .padding() .presentationDetents([.large]) .presentationDragIndicator(.automatic) - + #if targetEnvironment(macCatalyst) Button { isPresentingHelp = false @@ -262,18 +262,18 @@ struct ShareChannels: View { }) .onAppear { bleManager.context = context - GenerateChannelSet() + generateChannelSet() } - .onChange(of: includeChannel1) { includeCh1 in GenerateChannelSet() } - .onChange(of: includeChannel2) { includeCh2 in GenerateChannelSet() } - .onChange(of: includeChannel3) { includeCh3 in GenerateChannelSet() } - .onChange(of: includeChannel4) { includeCh4 in GenerateChannelSet() } - .onChange(of: includeChannel5) { includeCh5 in GenerateChannelSet() } - .onChange(of: includeChannel6) { includeCh6 in GenerateChannelSet() } - .onChange(of: includeChannel7) { includeCh7 in GenerateChannelSet() } + .onChange(of: includeChannel1) { _ in generateChannelSet() } + .onChange(of: includeChannel2) { _ in generateChannelSet() } + .onChange(of: includeChannel3) { _ in generateChannelSet() } + .onChange(of: includeChannel4) { _ in generateChannelSet() } + .onChange(of: includeChannel5) { _ in generateChannelSet() } + .onChange(of: includeChannel6) { _ in generateChannelSet() } + .onChange(of: includeChannel7) { _ in generateChannelSet() } } } - func GenerateChannelSet() { + func generateChannelSet() { channelSet = ChannelSet() var loRaConfig = Config.LoRaConfig() loRaConfig.region = RegionCodes(rawValue: Int(node?.loRaConfig?.regionCode ?? 0))!.protoEnumValue() @@ -291,10 +291,10 @@ struct ShareChannels: View { if node?.myInfo?.channels != nil && node?.myInfo?.channels?.count ?? 0 > 0 { for ch in node!.myInfo!.channels!.array as! [ChannelEntity] { if ch.role > 0 { - + if ch.index == 0 && includeChannel0 || ch.index == 1 && includeChannel1 || ch.index == 2 && includeChannel2 || ch.index == 3 && includeChannel3 || - ch.index == 4 && includeChannel4 || ch.index == 5 && includeChannel5 || ch.index == 6 && includeChannel6 || ch.index == 7 && includeChannel7 { - + ch.index == 4 && includeChannel4 || ch.index == 5 && includeChannel5 || ch.index == 6 && includeChannel6 || ch.index == 7 && includeChannel7 { + var channelSettings = ChannelSettings() channelSettings.name = ch.name! channelSettings.psk = ch.psk! diff --git a/Meshtastic/Views/Settings/UserConfig.swift b/Meshtastic/Views/Settings/UserConfig.swift index 2057fe05..f920527e 100644 --- a/Meshtastic/Views/Settings/UserConfig.swift +++ b/Meshtastic/Views/Settings/UserConfig.swift @@ -8,17 +8,17 @@ import SwiftUI import CoreData struct UserConfig: View { - + @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager @Environment(\.dismiss) private var goBack - + var node: NodeInfoEntity? - + enum Field: Hashable { case frequencyOverride } - + @State private var isPresentingFactoryResetConfirm: Bool = false @State private var isPresentingSaveConfirm: Bool = false @State var hasChanges = false @@ -28,24 +28,24 @@ struct UserConfig: View { @State var overrideDutyCycle = false @State var overrideFrequency: Float = 0.0 @State var txPower = 0 - + @FocusState var focusedField: Field? - + let floatFormatter: NumberFormatter = { let formatter = NumberFormatter() formatter.numberStyle = .decimal return formatter }() - + var body: some View { - + VStack { Form { Section(header: Text("User Details")) { HStack { Label(isLicensed ? "Call Sign" : "Long Name", systemImage: "person.crop.rectangle.fill") TextField("Long Name", text: $longName) - .onChange(of: longName, perform: { value in + .onChange(of: longName, perform: { _ in let totalBytes = longName.utf8.count // Only mess with the value if it is too big if totalBytes > (isLicensed ? 8 : 36) { @@ -61,12 +61,12 @@ struct UserConfig: View { .disableAutocorrection(true) Text("\(String(isLicensed ? "Call Sign" : "Long Name")) can be up to \(isLicensed ? "8" : "36") bytes long.") .font(.caption2) - + HStack { Label("Short Name", systemImage: "circlebadge.fill") TextField("Short Name", text: $shortName) .foregroundColor(.gray) - .onChange(of: shortName, perform: { value in + .onChange(of: shortName, perform: { _ in let totalBytes = shortName.utf8.count // Only mess with the value if it is too big if totalBytes > 4 { @@ -83,20 +83,20 @@ struct UserConfig: View { .disableAutocorrection(true) Text("The last 4 of the device MAC address will be appended to the short name to set the device's BLE Name. Short name can be up to 4 bytes long.") .font(.caption2) - + // Only manage ham mode for the locally connected node - if node?.num ?? 0 > 0 && node?.num ?? 0 == bleManager.connectedPeripheral?.num ?? 0 { + if node?.num ?? 0 > 0 && node?.num ?? 0 == bleManager.connectedPeripheral?.num ?? 0 { Toggle(isOn: $isLicensed) { Label("Licensed Operator", systemImage: "person.text.rectangle") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) if isLicensed { - + Text("Onboarding for licensed operators requires firmware 2.0.20 or greater. Make sure to refer to your local regulations and contact the local amateur frequency coordinators with questions.") .font(.caption2) Text("What licensed operator mode does:\n* Sets the node name to your call sign \n* Broadcasts node info every 10 minutes \n* Overrides frequency, dutycycle and tx power \n* Disables encryption") .font(.caption2) - + HStack { Label("Frequency", systemImage: "waveform.path.ecg") Spacer() @@ -141,11 +141,11 @@ struct UserConfig: View { titleVisibility: .visible ) { Button("Save User Config to \(node?.user?.longName ?? "Unknown")?") { - + let connectedUser = getUser(id: bleManager.connectedPeripheral?.num ?? -1, context: context) let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1, context: context) - if connectedNode != nil { - + if node != nil && connectedNode != nil { + if !isLicensed { var u = User() u.shortName = shortName @@ -157,7 +157,7 @@ struct UserConfig: View { } } else { var ham = HamParameters() - //ham.shortName = shortName + // ham.shortName = shortName ham.callSign = longName ham.txPower = Int32(txPower) ham.frequency = overrideFrequency @@ -204,11 +204,11 @@ struct UserConfig: View { if newIsLicensed != node?.user!.isLicensed { hasChanges = true } } } - .onChange(of: overrideFrequency) { newOverrideFrequency in - //hasChanges = true + .onChange(of: overrideFrequency) { _ in + // hasChanges = true } - .onChange(of: txPower) { newTxPower in - //hasChanges = true + .onChange(of: txPower) { _ in + // hasChanges = true } } } diff --git a/MeshtasticTests/MeshtasticTests.swift b/MeshtasticTests/MeshtasticTests.swift index 6606b5cd..bc75d0da 100644 --- a/MeshtasticTests/MeshtasticTests.swift +++ b/MeshtasticTests/MeshtasticTests.swift @@ -17,7 +17,7 @@ class MeshtasticTests: XCTestCase { } override func tearDownWithError() throws { - + // Put teardown code here. This method is called after the invocation of each test method in the class. super.tearDown() } diff --git a/Widgets/Widgets.swift b/Widgets/Widgets.swift index 2dccff37..07354ffd 100644 --- a/Widgets/Widgets.swift +++ b/Widgets/Widgets.swift @@ -5,10 +5,10 @@ //// Created by Garth Vander Houwen on 2/28/23. //// // -//import WidgetKit -//import SwiftUI +// import WidgetKit +// import SwiftUI // -//struct Provider: TimelineProvider { +// struct Provider: TimelineProvider { // func placeholder(in context: Context) -> SimpleEntry { // SimpleEntry(date: Date()) // } @@ -32,21 +32,21 @@ // let timeline = Timeline(entries: entries, policy: .atEnd) // completion(timeline) // } -//} +// } // -//struct SimpleEntry: TimelineEntry { +// struct SimpleEntry: TimelineEntry { // let date: Date -//} +// } // -//struct WidgetsEntryView : View { +// struct WidgetsEntryView : View { // var entry: Provider.Entry // // var body: some View { // Text(entry.date, style: .time) // } -//} +// } // -//struct Widgets: Widget { +// struct Widgets: Widget { // let kind: String = "Widgets" // // var body: some WidgetConfiguration { @@ -56,11 +56,11 @@ // .configurationDisplayName("My Widget") // .description("This is an example widget.") // } -//} +// } // -//struct Widgets_Previews: PreviewProvider { +// struct Widgets_Previews: PreviewProvider { // static var previews: some View { // WidgetsEntryView(entry: SimpleEntry(date: Date())) // .previewContext(WidgetPreviewContext(family: .systemSmall)) // } -//} +// } diff --git a/Widgets/WidgetsBundle.swift b/Widgets/WidgetsBundle.swift index 950954a7..83877dff 100644 --- a/Widgets/WidgetsBundle.swift +++ b/Widgets/WidgetsBundle.swift @@ -12,10 +12,10 @@ import SwiftUI @main struct WidgetsBundle: WidgetBundle { var body: some Widget { - //Widgets() + // Widgets() #if canImport(ActivityKit) WidgetsLiveActivity() #endif - + } } diff --git a/Widgets/WidgetsLiveActivity.swift b/Widgets/WidgetsLiveActivity.swift index 744539f7..3e079b0f 100644 --- a/Widgets/WidgetsLiveActivity.swift +++ b/Widgets/WidgetsLiveActivity.swift @@ -12,7 +12,7 @@ import SwiftUI @available(iOS 16.2, *) struct WidgetsLiveActivity: Widget { var body: some WidgetConfiguration { - + ActivityConfiguration(for: MeshActivityAttributes.self) { context in LiveActivityView(nodeName: context.attributes.name, channelUtilization: context.state.channelUtilization, airtime: context.state.airtime, batteryLevel: context.state.batteryLevel, timerRange: context.state.timerRange) .widgetURL(URL(string: "meshtastic://node/\(context.attributes.name)")) @@ -28,15 +28,15 @@ struct WidgetsLiveActivity: Widget { // various regions, like leading/trailing/center/bottom DynamicIslandExpandedRegion(.trailing, priority: 1) { HStack(alignment: .lastTextBaseline) { - + Spacer() TimerView(timerRange: context.state.timerRange) .tint(Color("LightIndigo")) } .padding(.top) - + } - + } compactLeading: { Image("logo-black") .resizable() @@ -67,7 +67,7 @@ struct WidgetsLiveActivity: Widget { struct WidgetsLiveActivity_Previews: PreviewProvider { static let attributes = MeshActivityAttributes(nodeNum: 123456789, name: "Meshtastic 8E6G") static let state = MeshActivityAttributes.ContentState( - timerRange: Date.now...Date(timeIntervalSinceNow: 3600), connected: true, channelUtilization: 25.84, airtime: 10.01, batteryLevel: 39) + timerRange: Date.now...Date(timeIntervalSinceNow: 3600), connected: true, channelUtilization: 25.84, airtime: 10.01, batteryLevel: 39) static var previews: some View { attributes @@ -89,14 +89,14 @@ struct WidgetsLiveActivity_Previews: PreviewProvider { struct LiveActivityView: View { @Environment(\.colorScheme) private var colorScheme @Environment(\.isLuminanceReduced) var isLuminanceReduced - + var nodeName: String - //var connected: Bool + // var connected: Bool var channelUtilization: Float var airtime: Float var batteryLevel: UInt32 var timerRange: ClosedRange - + var body: some View { HStack { Image(colorScheme == .light ? "logo-black" : "logo-white") @@ -116,13 +116,13 @@ struct LiveActivityView: View { struct NodeInfoView: View { @Environment(\.isLuminanceReduced) var isLuminanceReduced - + var nodeName: String var timerRange: ClosedRange var channelUtilization: Float var airtime: Float var batteryLevel: UInt32 - + var body: some View { VStack(alignment: .leading, spacing: 0) { Text(nodeName) @@ -142,12 +142,21 @@ struct NodeInfoView: View { .foregroundStyle(.secondary) .opacity(isLuminanceReduced ? 0.5 : 1.0) .fixedSize() - Text("Battery Level: \(batteryLevel)%") - .font(.subheadline) - .fontWeight(.medium) - .foregroundStyle(.secondary) - .opacity(isLuminanceReduced ? 0.5 : 1.0) - .fixedSize() + if batteryLevel < 101 { + Text("Battery Level: \(batteryLevel > 0 ? String(batteryLevel) : "< 1")%") + .font(.subheadline) + .fontWeight(.medium) + .foregroundStyle(.secondary) + .opacity(isLuminanceReduced ? 0.5 : 1.0) + .fixedSize() + } else { + Text("Plugged In") + .font(.subheadline) + .fontWeight(.medium) + .foregroundStyle(.secondary) + .opacity(isLuminanceReduced ? 0.5 : 1.0) + .fixedSize() + } Text(Date().formatted()) .font(.subheadline) .fontWeight(.medium) @@ -160,9 +169,9 @@ struct NodeInfoView: View { struct TimerView: View { @Environment(\.isLuminanceReduced) var isLuminanceReduced - + var timerRange: ClosedRange - + var body: some View { VStack(alignment: .center) { Text("NEXT") @@ -200,10 +209,10 @@ struct ExpandedTrailingView: View { var airtime: Float var batteryLevel: UInt32 var timerInterval: ClosedRange - + var body: some View { HStack(alignment: .lastTextBaseline) { - + Spacer() TimerView(timerRange: timerInterval) }