From 0dedf94a989f577ed4a398ca17e11d8fe034b526 Mon Sep 17 00:00:00 2001 From: Blake McAnally Date: Mon, 3 Jun 2024 19:05:58 -0500 Subject: [PATCH 01/32] General Project Cleanup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Change SwiftLint from a "Run Script" build phase to a "Run Build Tool Plugins" build phase. * Developers no longer need to install and update SwiftLint manually. This can be useful when other projects are using different versions of SwiftLint. * [SwiftLint Docs](https://github.com/realm/SwiftLint?tab=readme-ov-file#xcode-projects) * [WWDC 22 - Meet Swift Package Plugins](https://developer.apple.com/videos/play/wwdc2022/110359/?time=162) * Moved several scripts into a `.scripts` directory to keep the project root organized. * Modified `.scripts/gen_protos.sh` to intelligently update git submodules and install `swift-protobuf` when it detects issues, reducing developer friction. * Deleted the empty unit and UI testing targets. These can be added later as we write tests. someday 🙏 * Updated the README.md to reflect the above changes, and organized a few of the sections. --- gen_protos.sh => .scripts/gen_protos.sh | 8 +- thebenternify.sh => .scripts/thebenternify.sh | 0 .../unthebenternify.sh | 0 Meshtastic.xcodeproj/project.pbxproj | 184 +++--------------- .../xcshareddata/swiftpm/Package.resolved | 51 ----- .../xcschemes/WidgetsExtension.xcscheme | 1 - MeshtasticTests/Info.plist | 22 --- MeshtasticTests/MeshtasticTests.swift | 37 ---- MeshtasticUITests/Info.plist | 22 --- MeshtasticUITests/MeshtasticUITests.swift | 42 ---- README.md | 52 ++--- 11 files changed, 61 insertions(+), 358 deletions(-) rename gen_protos.sh => .scripts/gen_protos.sh (60%) rename thebenternify.sh => .scripts/thebenternify.sh (100%) rename unthebenternify.sh => .scripts/unthebenternify.sh (100%) delete mode 100644 Meshtastic.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved delete mode 100644 MeshtasticTests/Info.plist delete mode 100644 MeshtasticTests/MeshtasticTests.swift delete mode 100644 MeshtasticUITests/Info.plist delete mode 100644 MeshtasticUITests/MeshtasticUITests.swift diff --git a/gen_protos.sh b/.scripts/gen_protos.sh similarity index 60% rename from gen_protos.sh rename to .scripts/gen_protos.sh index b829095f..ae079a75 100755 --- a/gen_protos.sh +++ b/.scripts/gen_protos.sh @@ -1,15 +1,13 @@ #!/bin/bash # simple sanity checking for repo -if [ ! -d "./protobufs" ]; then - echo 'Please check out the protobuf submodule by running: `git submodule update --init`' - exit +if [ ! -d "./../protobufs" ]; then + git submodule update --init fi # simple sanity checking for executable if [ ! -x "$(which protoc)" ]; then - echo 'Please install swift-protobuf by running: `brew install swift-protobuf`' - exit + brew install swift-protobuf fi protoc --proto_path=./protobufs --swift_out=./Meshtastic/Protobufs ./protobufs/meshtastic/*.proto diff --git a/thebenternify.sh b/.scripts/thebenternify.sh similarity index 100% rename from thebenternify.sh rename to .scripts/thebenternify.sh diff --git a/unthebenternify.sh b/.scripts/unthebenternify.sh similarity index 100% rename from unthebenternify.sh rename to .scripts/unthebenternify.sh diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index cb1f6722..1c0f9b2f 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -156,7 +156,6 @@ DDC2E15826CE248E0042C5E4 /* MeshtasticApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC2E15726CE248E0042C5E4 /* MeshtasticApp.swift */; }; DDC2E15C26CE248F0042C5E4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DDC2E15B26CE248F0042C5E4 /* Assets.xcassets */; }; DDC2E15F26CE248F0042C5E4 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DDC2E15E26CE248F0042C5E4 /* Preview Assets.xcassets */; }; - DDC2E17A26CE248F0042C5E4 /* MeshtasticUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC2E17926CE248F0042C5E4 /* MeshtasticUITests.swift */; }; DDC2E18F26CE25FE0042C5E4 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC2E18E26CE25FE0042C5E4 /* ContentView.swift */; }; DDC2E1A726CEB3400042C5E4 /* LocationHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC2E1A626CEB3400042C5E4 /* LocationHelper.swift */; }; DDC3B274283F411B00AC321C /* LastHeardText.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC3B273283F411B00AC321C /* LastHeardText.swift */; }; @@ -214,13 +213,6 @@ /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ - DDC2E17626CE248F0042C5E4 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = DDC2E14C26CE248E0042C5E4 /* Project object */; - proxyType = 1; - remoteGlobalIDString = DDC2E15326CE248E0042C5E4; - remoteInfo = MeshtasticClient; - }; DDDE5A0129AF163E00490C6C /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = DDC2E14C26CE248E0042C5E4 /* Project object */; @@ -246,6 +238,7 @@ /* Begin PBXFileReference section */ 25183D452C0A6D97001E31D5 /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; + 258EE1262C0E833D0025A5FB /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 6DA39D8D2A92DC52007E311C /* MeshtasticAppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshtasticAppDelegate.swift; sourceTree = ""; }; 6DEDA5592A957B8E00321D2E /* DetectionSensorLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetectionSensorLog.swift; sourceTree = ""; }; 6DEDA55B2A9592F900321D2E /* MessageEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageEntityExtension.swift; sourceTree = ""; }; @@ -422,10 +415,6 @@ DDC2E15B26CE248F0042C5E4 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = ../Assets.xcassets; sourceTree = ""; }; DDC2E15E26CE248F0042C5E4 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; DDC2E16526CE248F0042C5E4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - DDC2E17026CE248F0042C5E4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - DDC2E17526CE248F0042C5E4 /* MeshtasticUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MeshtasticUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - DDC2E17926CE248F0042C5E4 /* MeshtasticUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshtasticUITests.swift; sourceTree = ""; }; - DDC2E17B26CE248F0042C5E4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; DDC2E18E26CE25FE0042C5E4 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; DDC2E1A626CEB3400042C5E4 /* LocationHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationHelper.swift; sourceTree = ""; }; DDC3B273283F411B00AC321C /* LastHeardText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LastHeardText.swift; sourceTree = ""; }; @@ -441,7 +430,6 @@ DDCE4E2B2869F92900BE9F8F /* UserConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserConfig.swift; sourceTree = ""; }; DDD28D362C0CCCD10063CFA3 /* pt-PT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-PT"; path = "pt-PT.lproj/Localizable.strings"; sourceTree = ""; }; DDD28D372C0CD2670063CFA3 /* MeshtasticDataModelV 37.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 37.xcdatamodel"; sourceTree = ""; }; - DDD3BBD4292D763200D609B3 /* MeshtasticTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MeshtasticTests.swift; sourceTree = ""; }; DDD43FE22A78C8900083A3E9 /* MqttClientProxyManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MqttClientProxyManager.swift; sourceTree = ""; }; DDD6EEAE29BC024700383354 /* Firmware.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Firmware.swift; sourceTree = ""; }; DDD94A4F2845C8F5004A87A0 /* DateTimeText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateTimeText.swift; sourceTree = ""; }; @@ -509,13 +497,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - DDC2E17226CE248F0042C5E4 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; DDDE59F129AF163D00490C6C /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -794,12 +775,11 @@ DDC2E14B26CE248E0042C5E4 = { isa = PBXGroup; children = ( + 258EE1262C0E833D0025A5FB /* README.md */, DDDBC87A2BC62E4E001E8DF7 /* Settings.bundle */, DDCDC6CD29481FCC004C1DDA /* Localizable.strings */, DD3CC6BA28E366DF00FA9159 /* Meshtastic.xcdatamodeld */, DDC2E15626CE248E0042C5E4 /* Meshtastic */, - DDC2E16D26CE248F0042C5E4 /* MeshtasticTests */, - DDC2E17826CE248F0042C5E4 /* MeshtasticUITests */, DDDE59F729AF163D00490C6C /* Widgets */, DDC2E15526CE248E0042C5E4 /* Products */, DD8EDE9226F97A2B00A5A10B /* Frameworks */, @@ -811,7 +791,6 @@ isa = PBXGroup; children = ( DDC2E15426CE248E0042C5E4 /* Meshtastic.app */, - DDC2E17526CE248F0042C5E4 /* MeshtasticUITests.xctest */, DDDE59F429AF163D00490C6C /* WidgetsExtension.appex */, ); name = Products; @@ -847,24 +826,6 @@ path = "Preview Content"; sourceTree = ""; }; - DDC2E16D26CE248F0042C5E4 /* MeshtasticTests */ = { - isa = PBXGroup; - children = ( - DDD3BBD4292D763200D609B3 /* MeshtasticTests.swift */, - DDC2E17026CE248F0042C5E4 /* Info.plist */, - ); - path = MeshtasticTests; - sourceTree = ""; - }; - DDC2E17826CE248F0042C5E4 /* MeshtasticUITests */ = { - isa = PBXGroup; - children = ( - DDC2E17926CE248F0042C5E4 /* MeshtasticUITests.swift */, - DDC2E17B26CE248F0042C5E4 /* Info.plist */, - ); - path = MeshtasticUITests; - sourceTree = ""; - }; DDC2E18726CE24E40042C5E4 /* Views */ = { isa = PBXGroup; children = ( @@ -1037,7 +998,6 @@ isa = PBXNativeTarget; buildConfigurationList = DDC2E17E26CE248F0042C5E4 /* Build configuration list for PBXNativeTarget "Meshtastic" */; buildPhases = ( - BB450974275599CE00509624 /* ShellScript */, DDC2E15026CE248E0042C5E4 /* Sources */, DDC2E15126CE248E0042C5E4 /* Frameworks */, DDC2E15226CE248E0042C5E4 /* Resources */, @@ -1046,6 +1006,7 @@ buildRules = ( ); dependencies = ( + 258EE1232C0E81DC0025A5FB /* PBXTargetDependency */, DDDE5A0229AF163E00490C6C /* PBXTargetDependency */, ); name = Meshtastic; @@ -1058,24 +1019,6 @@ productReference = DDC2E15426CE248E0042C5E4 /* Meshtastic.app */; productType = "com.apple.product-type.application"; }; - DDC2E17426CE248F0042C5E4 /* MeshtasticUITests */ = { - isa = PBXNativeTarget; - buildConfigurationList = DDC2E18426CE248F0042C5E4 /* Build configuration list for PBXNativeTarget "MeshtasticUITests" */; - buildPhases = ( - DDC2E17126CE248F0042C5E4 /* Sources */, - DDC2E17226CE248F0042C5E4 /* Frameworks */, - DDC2E17326CE248F0042C5E4 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - DDC2E17726CE248F0042C5E4 /* PBXTargetDependency */, - ); - name = MeshtasticUITests; - productName = MeshtasticClientUITests; - productReference = DDC2E17526CE248F0042C5E4 /* MeshtasticUITests.xctest */; - productType = "com.apple.product-type.bundle.ui-testing"; - }; DDDE59F329AF163D00490C6C /* WidgetsExtension */ = { isa = PBXNativeTarget; buildConfigurationList = DDDE5A0529AF163F00490C6C /* Build configuration list for PBXNativeTarget "WidgetsExtension" */; @@ -1087,6 +1030,7 @@ buildRules = ( ); dependencies = ( + 258EE1252C0E826E0025A5FB /* PBXTargetDependency */, ); name = WidgetsExtension; productName = WidgetsExtension; @@ -1107,10 +1051,6 @@ CreatedOnToolsVersion = 12.5.1; LastSwiftMigration = 1340; }; - DDC2E17426CE248F0042C5E4 = { - CreatedOnToolsVersion = 12.5.1; - TestTargetID = DDC2E15326CE248E0042C5E4; - }; DDDE59F329AF163D00490C6C = { CreatedOnToolsVersion = 14.2; }; @@ -1137,13 +1077,13 @@ DD5394FA276993AD00AD86B1 /* XCRemoteSwiftPackageReference "swift-protobuf" */, C9697FA327933B8C00250207 /* XCRemoteSwiftPackageReference "SQLite.swift" */, DD0D3D202A55CEB10066DB71 /* XCRemoteSwiftPackageReference "CocoaMQTT" */, + 258EE1212C0E81AE0025A5FB /* XCRemoteSwiftPackageReference "SwiftLint" */, ); productRefGroup = DDC2E15526CE248E0042C5E4 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( DDC2E15326CE248E0042C5E4 /* Meshtastic */, - DDC2E17426CE248F0042C5E4 /* MeshtasticUITests */, DDDE59F329AF163D00490C6C /* WidgetsExtension */, ); }; @@ -1164,13 +1104,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - DDC2E17326CE248F0042C5E4 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; DDDE59F229AF163D00490C6C /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -1181,27 +1114,6 @@ }; /* End PBXResourcesBuildPhase section */ -/* Begin PBXShellScriptBuildPhase section */ - BB450974275599CE00509624 /* ShellScript */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "if [[ \"$(uname -m)\" == arm64 ]]\nthen\n export PATH=\"/opt/homebrew/bin:$PATH\"\nfi\n\nif command -v swiftlint >/dev/null 2>&1\nthen\n swiftlint\nelse\n echo \"warning: `swiftlint` command not found - See https://github.com/realm/SwiftLint#installation for installation instructions.\"\nfi\n"; - }; -/* End PBXShellScriptBuildPhase section */ - /* Begin PBXSourcesBuildPhase section */ DDC2E15026CE248E0042C5E4 /* Sources */ = { isa = PBXSourcesBuildPhase; @@ -1395,14 +1307,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - DDC2E17126CE248F0042C5E4 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - DDC2E17A26CE248F0042C5E4 /* MeshtasticUITests.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; DDDE59F029AF163D00490C6C /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -1417,10 +1321,13 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ - DDC2E17726CE248F0042C5E4 /* PBXTargetDependency */ = { + 258EE1232C0E81DC0025A5FB /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = DDC2E15326CE248E0042C5E4 /* Meshtastic */; - targetProxy = DDC2E17626CE248F0042C5E4 /* PBXContainerItemProxy */; + productRef = 258EE1222C0E81DC0025A5FB /* SwiftLintBuildToolPlugin */; + }; + 258EE1252C0E826E0025A5FB /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + productRef = 258EE1242C0E826E0025A5FB /* SwiftLintBuildToolPlugin */; }; DDDE5A0229AF163E00490C6C /* PBXTargetDependency */ = { isa = PBXTargetDependency; @@ -1639,48 +1546,6 @@ }; name = Release; }; - DDC2E18526CE248F0042C5E4 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = GCH7VS5Y9R; - INFOPLIST_FILE = MeshtasticUITests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 16.2; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticUITests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - TEST_TARGET_NAME = Meshtastic; - }; - name = Debug; - }; - DDC2E18626CE248F0042C5E4 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = GCH7VS5Y9R; - INFOPLIST_FILE = MeshtasticUITests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 16.2; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticUITests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - TEST_TARGET_NAME = Meshtastic; - }; - name = Release; - }; DDDE5A0629AF163F00490C6C /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -1768,15 +1633,6 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - DDC2E18426CE248F0042C5E4 /* Build configuration list for PBXNativeTarget "MeshtasticUITests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - DDC2E18526CE248F0042C5E4 /* Debug */, - DDC2E18626CE248F0042C5E4 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; DDDE5A0529AF163F00490C6C /* Build configuration list for PBXNativeTarget "WidgetsExtension" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -1789,6 +1645,14 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ + 258EE1212C0E81AE0025A5FB /* XCRemoteSwiftPackageReference "SwiftLint" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/realm/SwiftLint"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 0.55.1; + }; + }; C9697FA327933B8C00250207 /* XCRemoteSwiftPackageReference "SQLite.swift" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/stephencelis/SQLite.swift.git"; @@ -1816,6 +1680,16 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ + 258EE1222C0E81DC0025A5FB /* SwiftLintBuildToolPlugin */ = { + isa = XCSwiftPackageProductDependency; + package = 258EE1212C0E81AE0025A5FB /* XCRemoteSwiftPackageReference "SwiftLint" */; + productName = "plugin:SwiftLintBuildToolPlugin"; + }; + 258EE1242C0E826E0025A5FB /* SwiftLintBuildToolPlugin */ = { + isa = XCSwiftPackageProductDependency; + package = 258EE1212C0E81AE0025A5FB /* XCRemoteSwiftPackageReference "SwiftLint" */; + productName = "plugin:SwiftLintBuildToolPlugin"; + }; C9697FA427933B8C00250207 /* SQLite */ = { isa = XCSwiftPackageProductDependency; package = C9697FA327933B8C00250207 /* XCRemoteSwiftPackageReference "SQLite.swift" */; diff --git a/Meshtastic.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Meshtastic.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved deleted file mode 100644 index a62998be..00000000 --- a/Meshtastic.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ /dev/null @@ -1,51 +0,0 @@ -{ - "originHash" : "e9855e3a299c14a10f11ee0b8f29e4170b09548533939361223a0f50e7caac8c", - "pins" : [ - { - "identity" : "cocoamqtt", - "kind" : "remoteSourceControl", - "location" : "https://github.com/emqx/CocoaMQTT", - "state" : { - "revision" : "85387a2478551ad84f39be8a3c8587d34dd2bcf5", - "version" : "2.1.5" - } - }, - { - "identity" : "mqttcocoaasyncsocket", - "kind" : "remoteSourceControl", - "location" : "https://github.com/leeway1208/MqttCocoaAsyncSocket", - "state" : { - "revision" : "ce3e18607fd01079495f86ff6195d8a3ca469f73", - "version" : "1.0.8" - } - }, - { - "identity" : "sqlite.swift", - "kind" : "remoteSourceControl", - "location" : "https://github.com/stephencelis/SQLite.swift.git", - "state" : { - "revision" : "7a2e3cd27de56f6d396e84f63beefd0267b55ccb", - "version" : "0.14.1" - } - }, - { - "identity" : "starscream", - "kind" : "remoteSourceControl", - "location" : "https://github.com/daltoniam/Starscream.git", - "state" : { - "revision" : "a063fda2b8145a231953c20e7a646be254365396", - "version" : "3.1.2" - } - }, - { - "identity" : "swift-protobuf", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-protobuf.git", - "state" : { - "revision" : "ce20dc083ee485524b802669890291c0d8090170", - "version" : "1.22.1" - } - } - ], - "version" : 3 -} diff --git a/Meshtastic.xcodeproj/xcshareddata/xcschemes/WidgetsExtension.xcscheme b/Meshtastic.xcodeproj/xcshareddata/xcschemes/WidgetsExtension.xcscheme index decd8381..880339bc 100644 --- a/Meshtastic.xcodeproj/xcshareddata/xcschemes/WidgetsExtension.xcscheme +++ b/Meshtastic.xcodeproj/xcshareddata/xcschemes/WidgetsExtension.xcscheme @@ -89,7 +89,6 @@ savedToolIdentifier = "" useCustomWorkingDirectory = "NO" debugDocumentVersioning = "YES" - askForAppToLaunch = "Yes" launchAutomaticallySubstyle = "2"> diff --git a/MeshtasticTests/Info.plist b/MeshtasticTests/Info.plist deleted file mode 100644 index 64d65ca4..00000000 --- a/MeshtasticTests/Info.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - $(PRODUCT_BUNDLE_PACKAGE_TYPE) - CFBundleShortVersionString - 1.0 - CFBundleVersion - 1 - - diff --git a/MeshtasticTests/MeshtasticTests.swift b/MeshtasticTests/MeshtasticTests.swift deleted file mode 100644 index bc75d0da..00000000 --- a/MeshtasticTests/MeshtasticTests.swift +++ /dev/null @@ -1,37 +0,0 @@ -// -// MeshtasticAppleTests.swift -// MeshtasticAppleTests -// -// Created by Garth Vander Houwen on 8/18/21. -// - -import XCTest - -@testable import Meshtastic - -class MeshtasticTests: XCTestCase { - - override func setUpWithError() throws { - super.setUp() - // Put setup code here. This method is called before the invocation of each test method in the class. - } - - override func tearDownWithError() throws { - - // Put teardown code here. This method is called after the invocation of each test method in the class. - super.tearDown() - } - - func testExample() throws { - // This is an example of a functional test case. - // Use XCTAssert and related functions to verify your tests produce the correct results. - } - - func testPerformanceExample() throws { - // This is an example of a performance test case. - self.measure { - // Put the code you want to measure the time of here. - } - } - -} diff --git a/MeshtasticUITests/Info.plist b/MeshtasticUITests/Info.plist deleted file mode 100644 index 64d65ca4..00000000 --- a/MeshtasticUITests/Info.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - $(PRODUCT_BUNDLE_PACKAGE_TYPE) - CFBundleShortVersionString - 1.0 - CFBundleVersion - 1 - - diff --git a/MeshtasticUITests/MeshtasticUITests.swift b/MeshtasticUITests/MeshtasticUITests.swift deleted file mode 100644 index ae140de9..00000000 --- a/MeshtasticUITests/MeshtasticUITests.swift +++ /dev/null @@ -1,42 +0,0 @@ -// -// MeshtasticUITests.swift -// MeshtasticUITests -// -// Copyright(c) Garth Vander Houwen 8/18/21. -// - -import XCTest - -class MeshtasticUITests: XCTestCase { - - override func setUpWithError() throws { - // Put setup code here. This method is called before the invocation of each test method in the class. - - // In UI tests it is usually best to stop immediately when a failure occurs. - continueAfterFailure = false - - // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. - } - - override func tearDownWithError() throws { - // Put teardown code here. This method is called after the invocation of each test method in the class. - } - - func testExample() throws { - // UI tests must launch the application that they test. - let app = XCUIApplication() - app.launch() - - // Use recording to get started writing UI tests. - // Use XCTAssert and related functions to verify your tests produce the correct results. - } - - func testLaunchPerformance() throws { - if #available(macOS 13, iOS 16.0, watchOS 8.0, *) { - // This measures how long it takes to launch your application. - measure(metrics: [XCTApplicationLaunchMetric()]) { - XCUIApplication().launch() - } - } - } -} diff --git a/README.md b/README.md index 9b888e90..43dd88aa 100644 --- a/README.md +++ b/README.md @@ -8,35 +8,41 @@ SwiftUI client applications for iOS, iPadOS and macOS. -## OS Requirements +## Getting Started -* iOS App Requires iOS 16 + -* iPadOS App Requires iPadOS 16 + -* Mac App Reguires macOS 13 + +This project is currently using **Xcode 15.4**. -## Code Standards +1. Clone the repo. +2. Open `Meshtastic.xcodeproj` +2. Build and run the `Meshtastic` target. -- Use SwiftUI (Maps are the exception) -- Use Hierarchical icons +```sh +git clone git@github.com:meshtastic/Meshtastic-Apple.git +cd Meshtastic-Apple +open Meshtastic.xcodeproj +``` + +## Technical Standards + +### Supported Operating Systems + +* iOS 16+ +* iPadOS 16+ +* macOS 13+ + +### Code Standards + +- Use SwiftUI +- Use SFSymbols for icons - Use Core Data for persistence -- Requires SwiftLint - see https://github.com/realm/SwiftLint -## To update protobufs: - -- install swift-protobuf: - ```bash - brew install swift-protobuf - ``` -- check out the latest protobuf commit from the master branch - ```bash - git submodule update --init - ``` +## Updating Protobufs: - run: ```bash - ./gen_protos.sh + .scripts/gen_protos.sh ``` - build, test, commit changes -- You may need to run: - ```bash - swiftlint --fix - ``` + +## License + +This project is licensed under the GPL v3. See the [LICENSE](LICENSE) file for details. From b8baf047b74adaa8133a4c447fab7cccebd814f6 Mon Sep 17 00:00:00 2001 From: Blake McAnally Date: Mon, 3 Jun 2024 20:17:19 -0500 Subject: [PATCH 02/32] Refactor an unnecessarily duplicated code branch --- Meshtastic/Views/ContentView.swift | 51 ++++++++++-------------------- 1 file changed, 16 insertions(+), 35 deletions(-) diff --git a/Meshtastic/Views/ContentView.swift b/Meshtastic/Views/ContentView.swift index 46f7f903..d9239dfe 100644 --- a/Meshtastic/Views/ContentView.swift +++ b/Meshtastic/Views/ContentView.swift @@ -8,13 +8,24 @@ import SwiftUI struct ContentView: View { @StateObject var appState = AppState.shared + + var meshMap: some View { + SwiftUI.Group { + if #available(iOS 17.0, macOS 14.0, *), !UserDefaults.mapUseLegacy { + MeshMap() + } else { + NodeMap() + } + } + } + var body: some View { TabView(selection: $appState.tabSelection) { Messages() .tabItem { Label("messages", systemImage: "message") } - .tag(Tab.contacts) + .tag(Tab.messages) .badge(appState.unreadDirectMessages + appState.unreadChannelMessages) Connect() .tabItem { @@ -26,27 +37,11 @@ struct ContentView: View { Label("nodes", systemImage: "flipphone") } .tag(Tab.nodes) - if #available(iOS 17.0, macOS 14.0, *) { - if UserDefaults.mapUseLegacy { - NodeMap() - .tabItem { - Label("map", systemImage: "map") - } - .tag(Tab.map) - } else { - MeshMap() - .tabItem { - Label("map", systemImage: "map") - } - .tag(Tab.map) + meshMap + .tabItem { + Label("map", systemImage: "map") } - } else { - NodeMap() - .tabItem { - Label("map", systemImage: "map") - } - .tag(Tab.map) - } + .tag(Tab.map) Settings() .tabItem { Label("settings", systemImage: "gear") @@ -56,22 +51,8 @@ struct ContentView: View { } } } -// #Preview { -// if #available(iOS 17.0, *) { -// // ContentView(deepLinkManager: .init()) -// } else { -// // Fallback on earlier versions -// } -// } - -// struct ContentView_Previews: PreviewProvider { -// static var previews: some View { -// ContentView() -// } -// } enum Tab: Hashable { - case contacts case messages case map case ble From ded26e6f215f3fc1c9c87836639bff7e40d397af Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Mon, 3 Jun 2024 18:49:33 -0700 Subject: [PATCH 03/32] Bump version, add some emoji to logs --- Meshtastic.xcodeproj/project.pbxproj | 4 ++-- Meshtastic/Helpers/BLEManager.swift | 6 +++--- Meshtastic/Helpers/Map/OfflineTileManager.swift | 2 +- Meshtastic/Helpers/Mqtt/MqttClientProxyManager.swift | 5 ++--- Meshtastic/MeshtasticApp.swift | 12 ++++++------ .../Views/Nodes/Helpers/Map/PositionPopover.swift | 4 +++- 6 files changed, 17 insertions(+), 16 deletions(-) diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index cb1f6722..ad215f33 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -1595,7 +1595,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.3.10; + MARKETING_VERSION = 2.3.11; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; @@ -1629,7 +1629,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.3.10; + MARKETING_VERSION = 2.3.11; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index caab235d..5885e375 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -452,7 +452,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } catch { context!.rollback() let nsError = error as NSError - Logger.data.error("Error Updating Core Data BluetoothConfigEntity: \(nsError)") + Logger.data.error("💥 Error Updating Core Data BluetoothConfigEntity: \(nsError)") } let logString = String.localizedStringWithFormat("mesh.log.traceroute.sent %@".localized, String(destNum)) @@ -492,7 +492,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) { - Logger.services.error("didUpdateNotificationStateFor error: \(error?.localizedDescription ?? "Unknown")") + Logger.services.error("💥 didUpdateNotificationStateFor error: \(error?.localizedDescription ?? "Unknown")") } // MARK: Data Read / Update Characteristic Event @@ -507,7 +507,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate // 5 CBATTErrorDomain Code=5 "Authentication is insufficient." // 15 CBATTErrorDomain Code=15 "Encryption is insufficient." lastConnectionError = "🚨" + String.localizedStringWithFormat("ble.errorcode.pin %@".localized, error.localizedDescription) - Logger.services.error("\(error.localizedDescription) Please try connecting again and check the PIN carefully.") + Logger.services.error("💥 \(error.localizedDescription) Please try connecting again and check the PIN carefully.") self.disconnectPeripheral(reconnect: false) } return diff --git a/Meshtastic/Helpers/Map/OfflineTileManager.swift b/Meshtastic/Helpers/Map/OfflineTileManager.swift index 89e9bc16..12b159ba 100644 --- a/Meshtastic/Helpers/Map/OfflineTileManager.swift +++ b/Meshtastic/Helpers/Map/OfflineTileManager.swift @@ -21,7 +21,7 @@ class OfflineTileManager: ObservableObject { } init() { - Logger.services.debug("Documents Directory = \(self.documentsDirectory.absoluteString)") + Logger.services.debug("🗄️ Documents Directory = \(self.documentsDirectory.absoluteString)") createDirectoriesIfNecessary() } diff --git a/Meshtastic/Helpers/Mqtt/MqttClientProxyManager.swift b/Meshtastic/Helpers/Mqtt/MqttClientProxyManager.swift index a2e363d0..29a3548a 100644 --- a/Meshtastic/Helpers/Mqtt/MqttClientProxyManager.swift +++ b/Meshtastic/Helpers/Mqtt/MqttClientProxyManager.swift @@ -130,8 +130,7 @@ extension MqttClientProxyManager: CocoaMQTTDelegate { } } func mqttDidDisconnect(_ mqtt: CocoaMQTT, withError err: Error?) { - Logger.services.debug("mqttDidDisconnect: \(err?.localizedDescription ?? "")") - + Logger.services.debug("📲 MQTT Client Proxy mqttDidDisconnect: \(err?.localizedDescription ?? "")") if let error = err { delegate?.onMqttError(message: error.localizedDescription) } @@ -152,7 +151,7 @@ extension MqttClientProxyManager: CocoaMQTTDelegate { Logger.services.info("📲 MQTT Client Proxy didSubscribeTopics: \(success.allKeys.count) topics. failed: \(failed.count) topics") } func mqtt(_ mqtt: CocoaMQTT, didUnsubscribeTopics topics: [String]) { - Logger.services.info("didUnsubscribeTopics: \(topics.joined(separator: ", "))") + Logger.services.info("📲 MQTT Client Proxy didUnsubscribeTopics: \(topics.joined(separator: ", "))") } func mqttDidPing(_ mqtt: CocoaMQTT) { Logger.services.info("📲 MQTT Client Proxy mqttDidPing") diff --git a/Meshtastic/MeshtasticApp.swift b/Meshtastic/MeshtasticApp.swift index da3383d2..b6cb96c1 100644 --- a/Meshtastic/MeshtasticApp.swift +++ b/Meshtastic/MeshtasticApp.swift @@ -48,7 +48,7 @@ struct MeshtasticAppleApp: App { Logger.services.debug("Add Channel \(self.addChannels)") } self.saveChannels = true - Logger.mesh.debug("User wants to open a Channel Settings URL: \(self.incomingUrl?.absoluteString ?? "No QR Code Link")") + Logger.mesh.debug("User wants to open a Channel Settings URL: \(self.incomingUrl?.absoluteString ?? "No QR Code Link", privacy: .private)") } if self.saveChannels { Logger.mesh.debug("User wants to open Channel Settings URL: \(String(describing: self.incomingUrl!.relativeString))") @@ -56,14 +56,14 @@ struct MeshtasticAppleApp: App { } .onOpenURL(perform: { (url) in - Logger.mesh.debug("Some sort of URL was received \(url)") + Logger.mesh.debug("Some sort of URL was received \(url, privacy: .private)") self.incomingUrl = url if url.absoluteString.lowercased().contains("meshtastic.org/e/#") { if let components = self.incomingUrl?.absoluteString.components(separatedBy: "#") { self.channelSettings = components.last! } self.saveChannels = true - Logger.mesh.debug("User wants to open a Channel Settings URL: \(self.incomingUrl?.absoluteString ?? "No QR Code Link")") + Logger.mesh.debug("User wants to open a Channel Settings URL: \(self.incomingUrl?.absoluteString ?? "No QR Code Link", privacy: .private)") } else if url.absoluteString.lowercased().contains("meshtastic://") { appState.navigationPath = url.absoluteString let path = appState.navigationPath ?? "" @@ -140,7 +140,7 @@ struct MeshtasticAppleApp: App { .onChange(of: scenePhase) { (newScenePhase) in switch newScenePhase { case .background: - Logger.services.info("🍏 Scene is in the background") + Logger.services.info("🎬 Scene is in the background") do { try persistenceController.container.viewContext.save() @@ -151,9 +151,9 @@ struct MeshtasticAppleApp: App { Logger.services.error("💥 Failed to save viewContext when the app goes to the background.") } case .inactive: - Logger.services.info("🍏 Scene is inactive") + Logger.services.info("🎬 Scene is inactive") case .active: - Logger.services.info("🍏 Scene is active") + Logger.services.info("🎬 Scene is active") @unknown default: Logger.services.error("🍎 Apple must have changed something") } diff --git a/Meshtastic/Views/Nodes/Helpers/Map/PositionPopover.swift b/Meshtastic/Views/Nodes/Helpers/Map/PositionPopover.swift index 221bf24f..8cfcb612 100644 --- a/Meshtastic/Views/Nodes/Helpers/Map/PositionPopover.swift +++ b/Meshtastic/Views/Nodes/Helpers/Map/PositionPopover.swift @@ -193,7 +193,9 @@ struct PositionPopover: View { } BatteryGauge(node: position.nodePosition!) } - LoRaSignalStrengthMeter(snr: position.nodePosition?.snr ?? 0.0, rssi: position.nodePosition?.rssi ?? 0, preset: ModemPresets(rawValue: UserDefaults.modemPreset) ?? ModemPresets.longFast, compact: false) + if !(position.nodePosition?.viaMqtt ?? true) && position.nodePosition?.hopsAway == 0 { + LoRaSignalStrengthMeter(snr: position.nodePosition?.snr ?? 0.0, rssi: position.nodePosition?.rssi ?? 0, preset: ModemPresets(rawValue: UserDefaults.modemPreset) ?? ModemPresets.longFast, compact: false) + } Spacer() } } From dccf5f56019f83ce453c982ab361b7626c7a1d6c Mon Sep 17 00:00:00 2001 From: Phil Rosa-Leeke <100378823+PhilRosa-Leeke@users.noreply.github.com> Date: Tue, 4 Jun 2024 18:31:59 +0100 Subject: [PATCH 04/32] Update Localizable.strings Fully translated the Localizable.strings file in the pt-PT.lproj folder --- pt-PT.lproj/Localizable.strings | 712 ++++++++++++++++---------------- 1 file changed, 356 insertions(+), 356 deletions(-) diff --git a/pt-PT.lproj/Localizable.strings b/pt-PT.lproj/Localizable.strings index 4c507879..f5454497 100644 --- a/pt-PT.lproj/Localizable.strings +++ b/pt-PT.lproj/Localizable.strings @@ -1,377 +1,377 @@ -/* + /* Localizable.strings Meshtastic - Copyright(c) Garth Vander Houwen on 12/12/22. + Copyright(c) Garth Vander Houwen on 12/12/22. Translated from English to Portuguese by Philip Rosa-Leeke 2024 */ -"about"="About"; -"about.meshtastic"="About Meshtastic"; -"activity"="Activity"; +"about"="Sobre"; +"about.meshtastic"="Sobre Meshtastic"; +"activity"="Actividade"; "admin"="Admin"; -"admin.log"="Admin Message Log"; -"ago"="ago"; -"airtime"="Airtime"; -"always.on"="Always On"; -"ambient.lighting"="Ambient Lighting"; -"ambient.lighting.config"="Ambient Lighting Config"; -"appsettings"="App Settings"; -"appsettings.provide.location"="Share location"; -"appsettings.smartposition"="Smart Position"; -"are.you.sure"="Are you sure?"; -"ascii.capable"="ASCII Capable"; -"available.radios"="Available Radios"; -"automatic.detection"="Automatic Detection"; -"battery.level"="Battery Level"; -"ble.name"="BLE Name"; -"ble.connection.timeout %d %@"="Connection failed after %d attempts to connect to %@. You may need to forget your device under Settings > Bluetooth."; -"ble.errorcode.6 %@"="%@ The app will automatically reconnect to the preferred radio if it comes back in range."; -"ble.errorcode.14 %@"="%@ This error usually cannot be fixed without forgetting the device unders Settings > Bluetooth and re-connecting to the radio."; -"ble.errorcode.pin %@"="%@ Please try connecting again and check the PIN carefully."; +"admin.log"="Log das Mensagens do Admin"; +"ago"="há"; +"airtime"="Tempo ao Ár"; +"always.on"="Sempre Ligado"; +"ambient.lighting"="Iluminação Ambiental"; +"ambient.lighting.config"="Configuração Iluminação Ambiental"; +"appsettings"="Definições do App"; +"appsettings.provide.location"="Partilha localização"; +"appsettings.smartposition"="Posição Inteligente"; +"are.you.sure"="Tem a certeza?"; +"ascii.capable"="Capacidade ASCII"; +"available.radios"="Rádios Disponíveis"; +"automatic.detection"="Deteção Automático"; +"battery.level"="Nível de Bataria"; +"ble.name"="Nome BLE"; +"ble.connection.timeout %d %@"="Falha de conexão após %d tentativas de conectar a %@. Você pode precisar esquecer seu dispositivo em Configurações > Bluetooth."; +"ble.errorcode.6 %@"="%@ O App vai reconetar automaticamente ao rádio preferido se ele voltar ao alcance."; +"ble.errorcode.14 %@"="%@ Esse erro geralmente não pode ser corrigido sem esquecer o dispositivo em Configurações > Bluetooth e reconetar ao rádio."; +"ble.errorcode.pin %@"="%@ Por favor, tente conectar novamente e verifique cuidadosamente o PIN."; "bluetooth"="Bluetooth"; -"bluetooth.off"="Bluetooth is off"; -"bluetooth.config"="Bluetooth Config"; -"bluetooth.mode.randompin"="Random PIN"; -"bluetooth.mode.fixedpin"="Fixed PIN"; -"bluetooth.mode.nopin"="No PIN (Just Works)"; -"bluetooth.pairingmode"="Pairing Mode"; -"bluetooth.pin.validation"="BLE Pin must be 6 digits long."; +"bluetooth.off"="Bluetooth está desligado"; +"bluetooth.config"="Configuração Bluetooth"; +"bluetooth.mode.randompin"="PIN Aleatório"; +"bluetooth.mode.fixedpin"="PIN fixo"; +"bluetooth.mode.nopin"="Sem PIN (Simplesmente Funciona)"; +"bluetooth.pairingmode"="Modo Pairing"; +"bluetooth.pin.validation"="O Pin do BLE deve ter 6 dígitos."; "bytes"="Bytes"; -"cancel"="Cancel"; -"canned.messages"="Canned Messages"; -"canned.messages.config"="Canned Messages Config"; -"canned.messages.preset.manual"="Manual Configuration"; -"canned.messages.preset.rakrotary"="RAK Rotary Encoder Module"; -"canned.messages.preset.cardkb"="M5 Stack Card KB / RAK Keypad"; -"channel"="Channel"; -"channel.role.disabled"="Disabled"; -"channel.role.primary"="Primary"; -"channel.role.secondary"="Secondary"; -"channel.utilization"="Channel Utilization"; -"channels"="Channels"; -"clear.app.data"="Clear App Data"; -"clear.log"="Clear"; -"close"="Close"; -"config.power.settings"="Power"; -"config.power.title"="Power Config"; -"config.power.section.battery"="Battery"; -"config.power.section.sleep"="Sleep"; -"config.power.adc.override"="ADC Override"; -"config.power.adc.multiplier"="Multiplier"; -"config.power.ls.secs"="Light Sleep Interval"; -"config.power.min.wake.secs"="Minimum Wake Interval"; -"config.power.saving"="Power Saving"; -"config.power.saving.description"="Will sleep everything as much as possible, for the tracker and sensor role this will also include the lora radio. Don't use this setting if you want to use your device with the phone apps or are using a device without a user button."; -"config.power.shutdown.on.power.loss"="Shutdown on Power Loss"; -"config.power.shutdown.after.secs"="After"; -"config.power.wait.bluetooth.secs"="Bluetooth Off After"; -"config.ringtone"="RTTTL Ringtone"; -"config.ringtone.title"="Ringtone Config"; -"config.ringtone.label"="Ringtone Transfer Language"; -"config.ringtone.description"="Ringtone Transfer Language(RTTTL) Ringtone String used by supported buzzers in external notifications."; -"config.module.paxcounter.settings"="PAX Counter"; -"config.module.paxcounter.title"="PAX Counter Config"; -"config.module.paxcounter.enabled.description"="When enabled the PAX Counter module counts the number of people passing by using WiFi and Bluetooth. Both WiFI and Bluetooth must be disabled for PAX counter to work."; -"config.module.paxcounter.updateinterval"="Update Interval"; -"config.module.paxcounter.updateinterval.description"="How often we can send a message to the mesh when people are detected."; -"config.save.confirm"="After config values save the node will reboot."; -"communicating"="Communicating with device. ."; -"connected.radio"="Connected Radio"; -"connected"="Bluetooth Connected"; -"connecting"="Connecting . ."; -"contacts"="Contacts"; -"contacts %@"="Contacts (%@)"; -"copy"="Copy"; -"current"="Current"; -"default"="Default"; -"delete"="Delete"; -"detection.sensor"="Detection Sensor"; -"detection.sensor.config"="Detection Sensor Config"; -"detection.sensor.log"="Detection Sensor Log"; -"device"="Device"; -"device.config"="Device Config"; -"device.configuration"="Device Configuration"; -"device.metrics.delete"="Delete all device metrics?"; -"device.metrics.log"="Device Metrics Log"; -"device.role.client"="App connected or stand alone messaging device."; -"device.role.clientmute"="Device that does not forward packets from other devices."; -"device.role.clienthidden"="Device that only broadcasts as needed for stealth or power savings."; -"device.role.tracker"="Broadcasts GPS position packets as priority."; -"device.role.lostandfound"="Broadcasts location as message to default channel regularly for to assist with device recovery."; -"device.role.sensor"="Broadcasts telemetry packets as priority."; -"device.role.tak"="Optimized for ATAK system communication, reduces routine broadcasts."; -"device.role.taktracker"="Enables automatic TAK PLI broadcasts and reduces routine broadcasts."; -"device.role.repeater"="Infrastructure node for extending network coverage by relaying messages with minimal overhead. Not visible in Nodes list."; -"device.role.router"="Infrastructure node for extending network coverage by relaying messages. Visible in Nodes list."; -"device.role.routerclient"="Combination of both ROUTER and CLIENT. Not for mobile devices."; -"direct.messages"="Direct Messages"; -"dismiss.keyboard"="Dismiss"; -"display"="Display"; -"display.config"="Display Config"; -"distance"="Distance"; -"disconnect"="Disconnect"; -"echo"="Echo"; -"email.address"="Email Address"; -"enabled"="Enabled"; -"encrypted"="Encrypted"; -"export"="Export"; -"external.notification"="External Notification"; -"external.notification.config"="External Notification Config"; -"finish"="Finish"; -"firmware.version"="Firmware Version"; -"firmware.version.unsupported"="Unsupported Firmware Version Detected, unable to connect to device."; +"cancel"="Cancelar"; +"canned.messages"="Mensagens Enlatados"; +"canned.messages.config"="Configuração dos Mensagens Enlatados"; +"canned.messages.preset.manual"="Configuração Manual"; +"canned.messages.preset.rakrotary"="Module Codificador do RAK Rotary"; +"canned.messages.preset.cardkb"="M5 Stack Card KB / Teclado RAK"; +"channel"="Canal"; +"channel.role.disabled"="Desativado"; +"channel.role.primary"="Primário"; +"channel.role.secondary"="Secundária"; +"channel.utilization"="Utilização do Canal"; +"channels"="Canais"; +"clear.app.data"="Apagar os dados do App"; +"clear.log"="Apagar"; +"close"="Fechar"; +"config.power.settings"="Energia"; +"config.power.title"="Configuração de Energia"; +"config.power.section.battery"="Bataria"; +"config.power.section.sleep"="Dormir"; +"config.power.adc.override"="Substituir ADC"; +"config.power.adc.multiplier"="Multiplicador"; +"config.power.ls.secs"="Intervalo de Dormir Leve"; +"config.power.min.wake.secs"="Intervalo Mínimo de Despertar"; +"config.power.saving"="Poupar a Energia"; +"config.power.saving.description"="Vai por dormir o máximo possível, para o papel do rastreador e o papel do sensor isso incluirá também o rádio lora. Não use essa configuração se deseja usar seu dispositivo com os aplicativos do telefone ou está usando um dispositivo sem um botão do usuário."; +"config.power.shutdown.on.power.loss"="Desligar em caso de Perda de Energia"; +"config.power.shutdown.after.secs"="Após"; +"config.power.wait.bluetooth.secs"="Desligar o Bluetooth Após"; +"config.ringtone"="="Toque RTTTL"; +"config.ringtone.title"="="Configuração de Toque"; +"config.ringtone.label"="="Idioma de Transferência de Toque"; +"config.ringtone.description"="="Idioma de Transferência de Toque (RTTTL) Sequência de Toque usada por campainhas suportadas em notificações externas."; +"config.module.paxcounter.settings"="="Contador de PAX"; +"config.module.paxcounter.title"="="Configuração do Contador de PAX"; +"config.module.paxcounter.enabled.description"="="Quando ativado, o módulo de Contador de PAX conta o número de pessoas que passam usando Wi-Fi e Bluetooth. Tanto o Wi-Fi quanto o Bluetooth devem estar desativados para que o contador de PAX funcione."; +"config.module.paxcounter.updateinterval"="="Intervalo de Atualização"; +"config.module.paxcounter.updateinterval.description"="="Com que frequência podemos enviar uma mensagem para a malha quando as pessoas são detectadas."; +"config.save.confirm"="="Após salvar os valores de configuração, o nó reiniciará"; +"communicating"="="Comunicando com dispositivo. ."; +"connected.radio"="="Rádio Conectado"; +"connected"="Bluetooth Connectado"; +"connecting"="="Conectando . ."; +"contacts"="Contactos"; +"contacts %@"="Contactos (%@)"; +"copy"="Copiar"; +"current"="Atual"; +"default"="Padrão"; +"delete"="Apagar"; +"detection.sensor"="Sensor de Detecção"; +"detection.sensor.config"="="Configuração do Sensor de Detecção"; +"detection.sensor.log"="Log Sensor de Detecção"; +"device"="="Dispositivo"; +"device.config"="="Configuração do Dispositivo"; +"device.configuration"="Configuração do Dispositivo"; +"device.metrics.delete"="="Apagar todas as métricas do dispositivo?"; +"device.metrics.log"="Log g de Métricas do Dispositivo"; +"device.role.client"="="Dispositivo conectado ao App ou independente para mensagens."; +"device.role.clientmute"="="Dispositivo que não encaminha pacotes de outros dispositivos."; +"device.role.clienthidden"="="Dispositivo que apenas transmite conforme necessário em modo furtivo ou economia de energia."; +"device.role.tracker"="Transmite pacotes de posição GPS como prioridade."; +"device.role.lostandfound"="="Transmite a localização como mensagem para o canal padrão regularmente para auxiliar na recuperação do dispositivo."; +"device.role.sensor"="="="Transmite pacotes de telemetria como prioridade."; +"device.role.tak"="Otimizado para comunicação do sistema ATAK, reduz transmissões rotineiras."; +"device.role.taktracker"="="="Permite transmissões automáticas de TAK PLI e reduz transmissões rotineiras."; +"device.role.repeater"="="Nó de infraestrutura para ampliar a cobertura da rede transmitindo mensagens com sobrecarga mínima. Não visível na lista de Nós."; +"device.role.router"="="Nó de infraestrutura para ampliar a cobertura da rede transmitindo mensagens. Visível na lista de Nós."; +"device.role.routerclient"="="Combinação de ROTEADOR e CLIENTE. Não para dispositivos móveis."; +"direct.messages"="Mensagens Directas"; +"dismiss.keyboard"="Dispensar"; +"display"="Icrã"; +"display.config"="Configuração do Icrã"; +"distance"="Distância"; +"disconnect"="Desconectar"; +"echo"="Eco"; +"email.address"="Endereço de Email"; +"enabled"="Activado"; +"encrypted"="Encriptado"; +"export"="Exportar"; +"external.notification"="Notificação Externa"; +"external.notification.config"="Configuração de Notificação Externa"; +"finish"="Terminar"; +"firmware.version"="Versão do Firmware"; +"firmware.version.unsupported"=" "Versão de Firmware não suportada detetada, impossível conectar ao dispositivo."; "gas"="Gas"; -"gas.resistance"="Gas Resistance"; -"generate.qr.code"="Generate QR Code"; -"gpsformat.dec"="Decimal Degrees Format"; -"gpsformat.dms"="Degrees Minutes Seconds"; +"gas.resistance"=" "Resistência ao Gas"; +"generate.qr.code"=" "Gerar Código QR"; +"gpsformat.dec"=" "Formato de Graus Decimais"; +"gpsformat.dms"=" "Graus Minutos Segundos"; "gpsformat.utm"="Universal Transverse Mercator"; -"gpsformat.mgrs"="Military Grid Reference System"; -"gpsformat.olc"="Open Location Code (aka Plus Codes)"; -"gpsformat.osgr"="Ordnance Survey Grid Reference"; -"gpsmode.disabled"="Disabled"; -"gpsmode.enabled"="Enabled"; -"gpsmode.notPresent"="Not Present"; -"heard"="Heard"; -"heard.last"="Last Heard"; -"hybrid"="Hybrid"; -"hybrid.flyover"="Hybrid Flyover"; -"include"="Include"; -"incomplete"="Incomplete"; -"inputevent.none"="None"; -"inputevent.up"="Up"; -"inputevent.down"="Down"; -"inputevent.left"="Left"; -"inputevent.right"="Right"; -"inputevent.select"="Select"; -"inputevent.back"="Back"; -"inputevent.cancel"="Cancel"; -"interval.one.second"="One Second"; -"interval.two.seconds"="Two Seconds"; -"interval.three.seconds"="Three Seconds"; -"interval.four.seconds"="Four Seconds"; -"interval.five.seconds"="Five Seconds"; -"interval.ten.seconds"="Ten Seconds"; -"interval.fifteen.seconds"="Fifteen Seconds"; -"interval.twenty.seconds"="Twenty Seconds"; -"interval.twentyfive.seconds"="Twenty Five Seconds"; -"interval.thirty.seconds"="Thirty Seconds"; -"interval.fortyfive.seconds"="Forty Five Seconds"; -"interval.one.minute"="One Minute"; -"interval.two.minutes"="Two Minutes"; -"interval.five.minutes"="Five Minutes"; -"interval.ten.minutes"="Ten Minutes"; -"interval.fifteen.minutes"="Fifteen Minutes"; -"interval.thirty.minutes"="Thirty Minutes"; -"interval.one.hour"="One Hour"; -"interval.two.hours"="Two Hours"; -"interval.three.hours"="Three Hours"; -"interval.four.hours"="Four Hours"; -"interval.five.hours"="Five Hours"; -"interval.six.hours"="Six Hours"; -"interval.twelve.hours"="Twelve Hours"; -"interval.eighteen.hours"="Eighteen Hours"; -"interval.twentyfour.hours"="Twenty Four Hours"; -"interval.thirtysix.hours"="Thirty Six Hours"; -"interval.fortyeight.hours"="Forty Eight Hours"; -"interval.seventytwo.hours"="Seventy Two Hours"; -"keyboard.type"="Keyboard Type"; -"logging"="Logging"; +"gpsformat.mgrs"=" "Sistema de Referência de Grelha Militar"; +"gpsformat.olc"=" "Código de Localização Aberto (também conhecido como Plus Codes)"; +"gpsformat.osgr"=" "Referência de Grelha da Ordnance Survey"; +"gpsmode.disabled"=" "Desativado"; +"gpsmode.enabled"=" "Ativado"; +"gpsmode.notPresent"="Não Presente"; +"heard"="Ouvido"; +"heard.last"="Último Ouvido"; +"hybrid"="Híbrido"; +"hybrid.flyover"="Híbrido o de Sobrevoo"; +"include"="Incluir"; +"incomplete"="Incompleto”; +"inputevent.none"="Nenhum"; +"inputevent.up"="Para Cima"; +"inputevent.down"="Para Baixo"; +"inputevent.left"="Esquerda"; +"inputevent.right"="Direita"; +"inputevent.select"="Selecionar"; +"inputevent.back"="Voltar"; +"inputevent.cancel"="Cancelar"; +"interval.one.second"="Um Segundo"; +"interval.two.seconds"="Dois Segundos"; +"interval.three.seconds"="Três Segundos"; +"interval.four.seconds"="Quatro Segundos"; +"interval.five.seconds"="Cinco Segundos"; +"interval.ten.seconds"="Dez Segundos"; +"interval.fifteen.seconds"="Quinze Segundos"; +"interval.twenty.seconds"="Vinte Segundos"; +"interval.twentyfive.seconds"="Vinte e Cinco Segundos"; +"interval.thirty.seconds"="Trinta Segundos"; +"interval.fortyfive.seconds"="Quarenta e Cinco Segundos"; +"interval.one.minute"="Um Minuto"; +"interval.two.minutes"="Dois Minutos"; +"interval.five.minutes"="Cinco Minutos"; +"interval.ten.minutes"="Dez Minutos"; +"interval.fifteen.minutes"="Quinze Minutos"; +"interval.thirty.minutes"="Trinta Minutos"; +"interval.one.hour"="Uma Hora"; +"interval.two.hours"="Duas Horas"; +"interval.three.hours"="Três Horas"; +"interval.four.hours"="Quatro Horas"; +"interval.five.hours"="Cinco Horas"; +"interval.six.hours"="Seis Horas"; +"interval.twelve.hours"="Doze Horas"; +"interval.eighteen.hours"="Dezoito Horas"; +"interval.twentyfour.hours"="Vinte e Quatro Horas"; +"interval.thirtysix.hours"="Trinta e Seis Horas"; +"interval.fortyeight.hours"="Quarenta e Oito Horas"; +"interval.seventytwo.hours"="Setenta e Duas Horas"; +"keyboard.type"="Tipo de Teclado"; +"logging"="Registo"; "lora"="LoRa"; -"lora.config"="LoRa Config"; -"map"="Mesh Map"; -"map.type"="Default Type"; -"map.centering"="Centering Mode"; -"map.tiles.delete"="Delete All Map Tiles"; -"map.recentering"="Automatic Re-centering"; -"map.use.legacy"="Use Legacy Mesh Map"; -"map.usertrackingmode"="User tracking mode"; -"map.usertrackingmode.follow"="Follow"; -"map.usertrackingmode.followwithheading"="Follow with heading"; -"map.usertrackingmode.none"="None"; -"mesh.live.activity"="Mesh Live Activity"; -"mesh.log"="Mesh Log"; -"mesh.log.ambientlighting.config %@"="Ambient Lighting module config received: %@"; -"mesh.log.bluetooth.config %@"="Bluetooth config received: %@"; -"mesh.log.cannedmessage.config %@"="Canned Message module config received: %@"; -"mesh.log.cannedmessages.messages.get %@"="Requested Canned Messages Module Messages for node: %@"; -"mesh.log.cannedmessages.messages.received %@"="Canned Messages Messages Received For: %@"; -"mesh.log.channel.sent %@ %d"="Sent a Channel for: %@ Channel Index %d"; -"mesh.log.channel.received %d %@"="Channel %d received from: %@"; -"mesh.log.device.config %@"="Device config received: %@"; -"mesh.log.display.config %@"="Display config received: %@"; -"mesh.log.devicemetadata %@"="Requesting Device Metadata for %@"; -"mesh.log.device.metadata.received %@"="Device Metadata received from: %@"; -"mesh.log.detectionsensor.config %@"="Detection Sensor module config received: %@"; -"mesh.log.externalnotification.config %@"="External Notification module config received: %@"; -"mesh.log.lora.config %@"="LoRa config received: %@"; -"mesh.log.lora.config.sent %@"="Sent a LoRa.Config for: %@"; -"mesh.log.mqtt.config %@"="MQTT module config received: %@"; -"mesh.log.myinfo %@"="MyInfo received: %@"; -"mesh.log.network.config %@"="Network config received: %@"; -"mesh.log.nodeinfo.received %@"="Node info received for: %@"; -"mesh.log.paxcounter %@"="PAX Counter message received from: %@"; -"mesh.log.paxcounter.config %@"="PAX Counter config received: %@"; -"mesh.log.position.config %@"="Positon config received: %@"; -"mesh.log.position.received %@"="Position Packet received from node: %@"; -"mesh.log.power.config %@"="Power config received: %@"; -"mesh.log.rangetest.config %@"="Range Test module config received: %@"; -"mesh.log.ringtone.config %@"="RTTTL Ringtone config received: %@"; -"mesh.log.routing.message %@ %@"="Routing received for RequestID: %@ Ack Status: %@"; -"mesh.log.serial.config %@"="Serial module config received: %@"; -"mesh.log.sharelocation %@"="Sent a Position Packet from the Apple device GPS to node: %@"; -"mesh.log.storeforward.config %@"="Store & Forward module config received: %@"; -"mesh.log.telemetry.config %@"="Telemetry module config received: %@"; -"mesh.log.telemetry.received %@"="Telemetry received for: %@"; -"mesh.log.textmessage.received"="Message received from the text message app."; -"mesh.log.textmessage.send.failed %@"="Message Send Failed, not properly connected to %@"; -"mesh.log.textmessage.sent %@ %@ %@"="Sent message %@ from %@ to %@"; -"mesh.log.traceroute.received.direct %@"="Trace Route request sent to node: %@ was recieived directly."; -"mesh.log.traceroute.received.route %@"="Trace Route request returned: %@"; -"mesh.log.traceroute.sent %@"="Sent a Trace Route Request to node: %@"; -"mesh.log.wantconfig %@"="Issuing Want Config to %@"; -"mesh.log.waypoint.sent %@"="Sent a Waypoint Packet from: %@"; -"mesh.log.waypoint.received %@"="Waypoint Packet received from node: %@"; -"message"="Message"; -"message.details"="Message Details"; -"messages"="Messages"; -"mode"="Mode"; -"module.configuration"="Module Configuration"; +"lora.config"="Configuração LoRa"; +"map"="Mapa do Mesh"; +"map.type"="Tipo Padrão"; +"map.centering"=" "Modo de Centralização"; +"map.tiles.delete"="Apagar Todas as Imagens da Mapa"; +"map.recentering"=" "Re-centralização Automática"; +"map.use.legacy"=" "Utilizar Mapa do Mesh Antigo"; +"map.usertrackingmode"=" "Modo de Rastreamento do Utilizador"; +"map.usertrackingmode.follow"=" "Seguir"; +"map.usertrackingmode.followwithheading"=" "Seguir com Direção"; +"map.usertrackingmode.none"="Nenhum"; +"mesh.live.activity"=" "Atividade Ao Vivo do Mesh"; +"mesh.log"="Log do Mesh"; +"mesh.log.ambientlighting.config %@"=" "Configuração do módulo de Iluminação Ambiente recebida: %@"; +"mesh.log.bluetooth.config %@"=" "Configuração Bluetooth recebida: %@"; +"mesh.log.cannedmessage.config %@"=" "Configuração do módulo de Mensagens Padrão recebida: %@"; +"mesh.log.cannedmessages.messages.get %@"=" "Mensagens Padrão solicitadas para o módulo de mensagens para o nó: %@"; +"mesh.log.cannedmessages.messages.received %@"=" "Mensagens Padrão recebidas para: %@"; +"mesh.log.channel.sent %@ %d"=" "Um Canal Enviado para: %@ Índice do Canal %d"; +"mesh.log.channel.received %d %@"=" "Canal %d recebido de: %@"; +"mesh.log.device.config %@"=" "Configuração do dispositivo recebida: %@"; +"mesh.log.display.config %@"=" "Configuração do icrãn recebida: %@"; +"mesh.log.devicemetadata %@"=" "Solicitando os Metadados do Dispositivo para %@"; +"mesh.log.device.metadata.received %@"=" "Os Metadados do dispositivo recebidos de: %@"; +"mesh.log.detectionsensor.config %@"=" "Configuração do módulo de sensor de detecção recebida: %@"; +"mesh.log.externalnotification.config %@"=" "Configuração do módulo de notificação externa recebida: %@"; +"mesh.log.lora.config %@"=" "Configuração LoRa recebida: %@"; +"mesh.log.lora.config.sent %@"="Configuração do LoRa Enviado para: %@"; +"mesh.log.mqtt.config %@"=" "Configuração do módulo MQTT recebida: %@"; +"mesh.log.myinfo %@"="MyInfo recebido: %@"; +"mesh.log.network.config %@"=" "Configuração de rede recebida: %@"; +"mesh.log.nodeinfo.received %@"=" "Informações do nó recebidas para: %@"; +"mesh.log.paxcounter %@"=" "Mensagem do Contador PAX recebida de: %@"; +"mesh.log.paxcounter.config %@"=" "Configuração do Contador PAX recebida: %@"; +"mesh.log.position.config %@"=" "Configuração de posição recebida: %@"; +"mesh.log.position.received %@"=" "Pacote de posição recebido do nó: %@"; +"mesh.log.power.config %@"=" "Configuração de energia recebida: %@"; +"mesh.log.rangetest.config %@"=" "Configuração do módulo de teste de alcance recebida: %@"; +"mesh.log.ringtone.config %@"=" "Configuração de toque RTTTL recebida: %@"; +"mesh.log.routing.message %@ %@"=" "Roteamento recebido para RequestID: %@ Estado de Ack: %@"; +"mesh.log.serial.config %@"=" "Configuração do módulo serial recebida: %@"; +"mesh.log.sharelocation %@"=" "Enviado um Pacote de Posição do GPS do dispositivo Apple para o nó: %@"; +"mesh.log.storeforward.config %@"=" "Configuração do módulo Store & Forward recebida: %@"; +"mesh.log.telemetry.config %@"=" "Configuração do módulo de telemetria recebida: %@"; +"mesh.log.telemetry.received %@"=" "Telemetria recebida para: %@"; +"mesh.log.textmessage.received"=" "Mensagem recebida do App de mensagem de texto."; +"mesh.log.textmessage.send.failed %@"=" "Falha no envio da mensagem, não conectado corretamente a %@"; +"mesh.log.textmessage.sent %@ %@ %@"=" "Mensagem enviada %@ de %@ para %@"; +"mesh.log.traceroute.received.direct %@"=" "Solicitação de Rastreamento enviada para o nó: %@" foi recebida diretamente."; +"mesh.log.traceroute.received.route %@"=" "Solicitação de Rastreamento retornada: %@"; +"mesh.log.traceroute.sent %@"=" "Enviei uma solicitação de Rastreamento para o nó: %@"; +"mesh.log.wantconfig %@"=" "Emitindo Configuração Desejada para %@"; +"mesh.log.waypoint.sent %@"=" "Enviado um Pacote de Ponto de Referência de: %@"; +"mesh.log.waypoint.received %@"=" "Pacote de Ponto de Referência recebido do nó: %@"; +"message"="Mensagem"; +"message.details"="Dados de Mensagem"; +"messages"="Mensagens"; +"mode"="Modo"; +"module.configuration"="Configuração do Módulo"; "mqtt"="MQTT"; -"mqtt.connect"="Connect to MQTT"; -"mqtt.config"="MQTT Config"; -"mqtt.clientproxy"="MQTT Client Proxy"; -"mqtt.disconnect"="Disconnect from MQTT"; -"mqtt.username"="Username"; -"name"="Name"; -"network"="Network"; -"network.config"="Network Config"; -"nodes"="Nodes"; -"nodes %@"="Nodes (%@)"; -"nodelist.filter.distance %@"="up to %@ away"; -"save.config %@"="Save Config for %@"; -"no.nodes"="No Meshtastic Nodes Found"; -"not.connected"="No device connected"; -"numbers.punctuation"="Numbers and Punctuation"; -"off"="Off"; +"mqtt.connect"="Conectar ao MQTT"; +"mqtt.config"="Configuração MQTT"; +"mqtt.clientproxy"=" "Proxy do Cliente MQTT"; +"mqtt.disconnect"=" "Desconectar do MQTT"; +"mqtt.username"=" "Nome de Utilizador"; +"name"="Nome"; +"network"="Rede"; +"network.config"="Configuração de Rede"; +"nodes"="Nós"; +"nodes %@"="Nós (%@)"; +"nodelist.filter.distance %@"="até %@ de distância"; +"save.config %@"="Salvar Configuração para %@"; +"no.nodes"="Nenhum Nó Meshtastic Encontrado"; +"not.connected"=" "Nenhum dispositivo conectado"; +"numbers.punctuation"=" "Números e Pontuação"; +"off"="Desligado"; "offline"="Offline"; -"on.boot"="On Boot Only"; -"options"="Options"; -"password"="Password"; -"pause"="Pause"; +"on.boot"=" "No arranque"; +"options"="Opções"; +"password"="Senha"; +"pause"="Pausa"; "paxcounter.ble"="BLE"; -"paxcounter.delete"="Delete all pax data?"; +"paxcounter.delete"=" "Apagar todos os dados de pax?"; "paxcounter.wifi"="WiFi"; -"paxcounter.content.unavailable"="No PAX Counter Logs"; -"paxcounter.log"="PAX Counter Log"; -"paxcounter.total"="Total PAX"; -"phone.gps"="Phone GPS"; -"phone.gps.interval.description"="How frequently your phone will send your location to the device, location updates to the mesh are managed by the device."; -"position"="Position"; -"position.config"="Position Config"; -"position.precision %@"="Within %@"; -"preferred.radio"="Preferred Radio"; -"radio.configuration"="Radio Configuration"; -"range.test"="Range Test"; -"range.test.blocked"="Block Range Test"; -"range.test.config"="Range Test Config"; -"reply"="Reply"; -"reboot"="Reboot"; -"reboot.node"="Reboot node?"; -"received.ack"="Received Ack"; -"received.ack.real"="Recipient Ack"; -"relativetimeofday.morning"="Morning"; -"relativetimeofday.midday"="Midday"; -"relativetimeofday.afternoon"="Afternoon"; -"relativetimeofday.evening"="Evening"; -"relativetimeofday.nighttime"="Nighttime"; -"resume"="Resume"; -"ringtone"="Ringtone"; -"ringtone.config"="Ringtone Config"; -"route.recorder"="Route Recorder"; -"routes"="Routes"; -"routes.activitytype.walking"="Walking"; -"routes.activitytype.hiking"="Hiking"; -"routes.activitytype.biking"="Biking"; -"routes.activitytype.driving"="Driving"; +"paxcounter.content.unavailable"=" "Nenhum Log do Contador PAX Disponível"; +"paxcounter.log"=" "Log do Contador PAX"; +"paxcounter.total"="Total de PAX"; +"phone.gps"="GPS do Telefone"; +"phone.gps.interval.description"=" "Com que frequência seu telefone enviará sua localização para o dispositivo, as atualizações de localização no mesh são geridas pelo dispositivo."; +"position"="Posição"; +"position.config"="Configuração de Posição"; +"position.precision %@"="Dentro de %@"; +"preferred.radio"="Rádio Preferido"; +"radio.configuration"="Configuração de Rádio"; +"range.test"="Teste de Alcance"; +"range.test.blocked"=" "Bloquear Teste de Alcance"; +"range.test.config"="Configuração do teste de Alcance"; +"reply"="Responder"; +"reboot"="Reiniciar"; +"reboot.node"="Reiniciar nó?"; +"received.ack"="Ack Recebido"; +"received.ack.real"="Ack do Destinário"; +"relativetimeofday.morning"="Manhã"; +"relativetimeofday.midday"="Meio-dia"; +"relativetimeofday.afternoon"="Tarde"; +"relativetimeofday.evening"="Noite"; +"relativetimeofday.nighttime"="Noite"; +"resume"="Continuar"; +"ringtone"="Toque"; +"ringtone.config"="Configuração de Toque"; +"route.recorder"="Gravador de Rotas"; +"routes"="Rotas"; +"routes.activitytype.walking"="Caminhada"; +"routes.activitytype.hiking"="Caminhada na Montanha"; +"routes.activitytype.biking"=" "Passeio de Bicicleta"; +"routes.activitytype.driving"="Conduzir"; "routes.activitytype.overlanding"="Overlanding"; -"routes.activitytype.skiing"="Skiing"; -"routes.activitytype.filename.walking"="walk"; -"routes.activitytype.filename.hiking"="hike"; -"routes.activitytype.filename.biking"="bike tour"; -"routes.activitytype.filename.driving"="drive"; -"routes.activitytype.filename.overlanding"="overland drive"; -"routes.activitytype.filename.skiing"="ski tour"; -"routing.acknowledged"="Acknowledged"; -"routing.noroute"="No Route"; -"routing.gotnak"="Received a negative acknowledgment"; -"routing.timeout"="Timeout"; -"routing.nointerface"="No Interface"; -"routing.maxretransmit"="Max Retransmission Reached"; -"routing.nochannel"="No Channel"; -"routing.toolarge"="The packet is too large"; -"routing.noresponse"="No Response"; -"routing.dutycyclelimit"="Regional Duty Cycle Limit Reached"; -"routing.badRequest"="Bad Request"; -"routing.notauthorized"="Not Authorized"; -"satellite"="Satellite"; -"satellite.flyover"="Satellite Flyover"; -"save"="Save"; -"save.config %@"="Save Config for %@"; +"routes.activitytype.skiing"="Esqui"; +"routes.activitytype.filename.walking"="Caminhar"; +"routes.activitytype.filename.hiking"="Caminhar na Montanha"; +"routes.activitytype.filename.biking"="Passeio de Bicicleta"; +"routes.activitytype.filename.driving"="Conduzir"; +"routes.activitytype.filename.overlanding"="Caminhar overland"; +"routes.activitytype.filename.skiing"="Passeio de esqui"; +"routing.acknowledged"=" "Reconhecido"; +"routing.noroute"="Sem Rota"; +"routing.gotnak"=" "Recebido um reconhecimento negativo"; +"routing.timeout"=" "Tempo Esgotado"; +"routing.nointerface"="Sem Interface"; +"routing.maxretransmit"=" "Máximo de Retransmissão Alcançado"; +"routing.nochannel"="Sem Canal"; +"routing.toolarge"="O pacote é grande de mais"; +"routing.noresponse"="Sem Resposta"; +"routing.dutycyclelimit"="O limite do Regional Duty Cycle foi abrangido"; +"routing.badRequest"="="Pedido Ruim"; +"routing.notauthorized"="Não Autorizado"; +"satellite"="Satéllite"; +"satellite.flyover"="Passagem de Satélite"; +"save"="Salvar"; +"save.config %@"="Salvar a Configuração para %@"; "serial"="Serial"; -"serial.config"="Serial Config"; -"serial.mode.default"="Default"; -"serial.mode.simple"="Simple"; +"serial.config"="Configuração Serial"; +"serial.mode.default"="Padrão"; +"serial.mode.simple"="Simples"; "serial.mode.proto"="Protobufs"; -"serial.mode.txtmsg"="Text Message"; -"serial.mode.nmea"="NMEA Positions"; -"settings"="Settings"; -"share.channels"="Share QR Code"; -"share.position"="Share Position"; -"subscribed"="Subscribed to mesh"; -"select.contact"="Select a Contact"; -"select.node"="Select a Node"; -"select.menu.item"="Select an item from the menu"; -"set.region"="Set LoRa Region"; -"standard"="Standard"; -"standard.muted"="Standard Muted"; -"start"="Start"; -"storeforward"="Store & Forward"; -"storeforward.config"="Store & Forward Config"; -"storeforward.heartbeat"="Send Heartbeat"; +"serial.mode.txtmsg"="Mensagem de Texto"; +"serial.mode.nmea"="Posições NMEA"; +"settings"="Definições"; +"share.channels"="Partilhar o Código do QR"; +"share.position"="Partilhar o Posição"; +"subscribed"="Inscrito no mesh"; +"select.contact"="Seleciona a Contacto"; +"select.node"="Seleciona a Nó"; +"select.menu.item"="Seleciona um opção do menu"; +"set.region"="Seleciona o Região da LoRa"; +"standard"="Padrão"; +"standard.muted"="Padrão Silenciado"; +"start"="Iniciar"; +"storeforward"="="Armazenar e Encaminhar"; +"storeforward.config"="Configuração de Armazenar e Encaminhar"; +"storeforward.heartbeat"="="Enviar Batimento Cardíaco"; "ssid"="SSID"; -"tapback"="Tapback Response"; -"tapback.heart"="Heart"; -"tapback.thumbsup"="Thumbs Up"; -"tapback.thumbsdown"="Thumbs Down"; +"tapback"="Resposta Tapback"; +"tapback.heart"="Coração"; +"tapback.thumbsup"="="Polegar para Cima"; +"tapback.thumbsdown"="Polegar para Baixo"; "tapback.haha"="HaHa"; -"tapback.exclamation"="Exclamation Mark"; -"tapback.question"="Question Mark"; -"tapback.poop"="Poop"; -"tapback.wave"="Wave"; -"telemetry"="Telemetry (Sensors)"; -"telemetry.config"="Telemetry Config"; -"timeout"="Timeout"; -"timestamp"="Timestamp"; -"tip.bluetooth.connect.title"="Connected Radio"; -"tip.bluetooth.connect.message"="Shows information for the Lora radio connected via bluetooth. You can swipe left to disconnect the radio and long press to view stats or start the live activity."; -"tip.channel.admin.title"="Admin Channel"; -"tip.channel.admin.message"="Admin channel detected: Select a node from the drop down to manage connected or remote devices."; -"tip.channels.create.title"="Manage Channels"; -"tip.channels.create.message"="Most data on your mesh is sent over the primary channel. You can set up secondary channels to create additional messaging groups secured by their own key. [Channel config tips](https://meshtastic.org/docs/configuration/tips/)"; -"tip.channels.share.title"="Sharing Meshtastic Channels"; -"tip.channels.share.message"="A Meshtastic QR code contains the LoRa config and channel values needed for radios to communicate. You can share a complete channel configuration using the Replace Channels option, if you choose Add Channels your shared channels will be added to the channels on the receiving radio."; -"tip.messages.title"="Messages"; -"tip.messages.message"="You can send and receive channel (group chats) and direct messages. From any message you can long press to see available actions like copy, reply, tapback and delete as well as delivery details."; +"tapback.exclamation"="Ponto de Exclamação"; +"tapback.question"="Ponto de Interrogação"; +"tapback.poop"="Cocó"; +"tapback.wave"="Adeus"; +"telemetry"="Telemetria (Sensores)"; +"telemetry.config"="Configuração Telemetria"; +"timeout"="Tempo Limite"; +"timestamp"="Carimbo de Data/Hora"; +"tip.bluetooth.connect.title"="Rádio Conectado"; +"tip.bluetooth.connect.message"="="Mostra informações para o rádio LoRa conectado via bluetooth. Você pode deslizar para a esquerda para desconectar o rádio e pressionar por um longo período para ver estatísticas ou iniciar a atividade ao vivo."; +"tip.channel.admin.title"="="Canal de Administração"; +"tip.channel.admin.message"="="Canal de administração detectado: Selecione um nó do menu suspenso para gerir dispositivos conectados ou remotos."; +"tip.channels.create.title"="="Gerir Canais"; +"tip.channels.create.message"="="A maioria dos dados na sua malha é enviada pelo canal principal. Você pode configurar canais secundários para criar grupos de mensagens adicionais protegidos por sua própria chave. [Channel config tips](https://meshtastic.org/docs/configuration/tips/)"; +"tip.channels.share.title"="="Compartilhando Canais Meshtastis"; +"tip.channels.share.message"="="Um código QR Meshtastic contém a configuração LoRa e os valores do canal necessários para os rádios se comunicarem. Você pode compartilhar uma configuração completa do canal usando a opção Substituir Canais; se você escolher Adicionar Canais, seus canais compartilhados serão adicionados aos canais no rádio receptor."; +"tip.messages.title"="Mensagens"; +"tip.messages.message"="="Você pode enviar e receber mensagens de canal (conversas em grupo) e mensagens diretas. De qualquer mensagem, você pode pressionar por um longo período para ver ações disponíveis como copiar, responder, tapback e excluir, bem como detalhes de entrega."; "twitter"="Twitter"; -"unknown"="Unknown"; -"unknown.age"="Unknown Age"; -"unset"="Unset"; -"update.firmware"="Update Your Firmware"; -"update.interval"="Update Interval"; -"uptime"="Uptime"; -"user"="User"; -"user.details"="User Details"; -"voltage"="Voltage"; -"waiting"="Waiting. . ."; -"appsettings.newNodeNotifications"="New Node Notifications"; +"unknown"="Desconhecido"; +"unknown.age"="Idade Desconhecido"; +"unset"="="Não Definido"; +"update.firmware"="Atualiza o Seu Firmware"; +"update.interval"="Intervalo de Atualização"; +"uptime"="Tempo No Ár"; +"user"="Utilizador"; +"user.details"="Dados do Utilizador"; +"voltage"="Tensão"; +"waiting"="À Espara. . ."; +"appsettings.newNodeNotifications"="Notificações de Nó Novo"; From de7c53cd89651c2b396f456ba01f3e7b1f98fbd1 Mon Sep 17 00:00:00 2001 From: Blake McAnally Date: Mon, 3 Jun 2024 22:53:55 -0500 Subject: [PATCH 05/32] Decompose NodeMapSwiftUI into computed properties to improve compile performance --- Meshtastic.xcodeproj/project.pbxproj | 4 + .../Nodes/Helpers/Map/NodeMapSwiftUI.swift | 339 ++++++++++-------- 2 files changed, 189 insertions(+), 154 deletions(-) diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index c0fe6473..eec33c62 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -1414,6 +1414,7 @@ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; + OTHER_SWIFT_FLAGS = "-Xfrontend -warn-long-expression-type-checking=200"; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -1471,6 +1472,7 @@ MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; + OTHER_SWIFT_FLAGS = "-Xfrontend -warn-long-expression-type-checking=200"; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; @@ -1568,6 +1570,7 @@ "@executable_path/../../Frameworks", ); MARKETING_VERSION = 2.3.10; + OTHER_SWIFT_FLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1601,6 +1604,7 @@ "@executable_path/../../Frameworks", ); MARKETING_VERSION = 2.3.10; + OTHER_SWIFT_FLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/Meshtastic/Views/Nodes/Helpers/Map/NodeMapSwiftUI.swift b/Meshtastic/Views/Nodes/Helpers/Map/NodeMapSwiftUI.swift index f8ee5165..27f6b7e6 100644 --- a/Meshtastic/Views/Nodes/Helpers/Map/NodeMapSwiftUI.swift +++ b/Meshtastic/Views/Nodes/Helpers/Map/NodeMapSwiftUI.swift @@ -35,172 +35,203 @@ struct NodeMapSwiftUI: View { @State private var mapRegion = MKCoordinateRegion.init() - @FetchRequest(sortDescriptors: [NSSortDescriptor(key: "name", ascending: false)], - predicate: NSPredicate( - format: "expire == nil || expire >= %@", Date() as NSDate - ), animation: .none) + @FetchRequest( + sortDescriptors: [NSSortDescriptor(key: "name", ascending: false)], + predicate: NSPredicate( + format: "expire == nil || expire >= %@", Date() as NSDate + ), + animation: .none + ) private var waypoints: FetchedResults - var body: some View { - var mostRecent = node.positions?.lastObject as? PositionEntity + private var title: String { + String((node.user?.shortName ?? "unknown".localized) + (" \(node.positions?.count ?? 0) points")) + } + + private var mostRecent: PositionEntity? { + node.positions?.lastObject as? PositionEntity + } - if node.hasPositions { - ZStack { - MapReader { _ in - Map(position: $position, bounds: MapCameraBounds(minimumDistance: 1, maximumDistance: .infinity), scope: mapScope) { - NodeMapContent(node: node) - } - .mapScope(mapScope) - .mapStyle(mapStyle) - .mapControls { - MapScaleView(scope: mapScope) - .mapControlVisibility(.visible) - if showUserLocation { - MapUserLocationButton(scope: mapScope) - .mapControlVisibility(.visible) - } - MapPitchToggle(scope: mapScope) - .mapControlVisibility(.visible) - MapCompass(scope: mapScope) - .mapControlVisibility(.visible) - } - .controlSize(.regular) - .overlay(alignment: .bottom) { - if scene != nil && isLookingAround { - LookAroundPreview(initialScene: scene) - .frame(height: UIDevice.current.userInterfaceIdiom == .phone ? 250 : 400) - .clipShape(RoundedRectangle(cornerRadius: 12)) - .padding(.horizontal, 20) - } - } - .overlay(alignment: .bottom) { - if !isLookingAround && isShowingAltitude { - PositionAltitudeChart(node: node) - .frame(height: UIDevice.current.userInterfaceIdiom == .phone ? 250 : 400) - .clipShape(RoundedRectangle(cornerRadius: 12)) - .padding(.horizontal, 20) - } - } - .sheet(isPresented: $isEditingSettings) { - MapSettingsForm(traffic: $showTraffic, pointsOfInterest: $showPointsOfInterest, mapLayer: $selectedMapLayer, meshMap: $isMeshMap) - .onChange(of: (selectedMapLayer)) { newMapLayer in - switch selectedMapLayer { - case .standard: - UserDefaults.mapLayer = newMapLayer - mapStyle = MapStyle.standard(elevation: .flat, pointsOfInterest: showPointsOfInterest ? .all : .excludingAll, showsTraffic: showTraffic) - case .hybrid: - UserDefaults.mapLayer = newMapLayer - mapStyle = MapStyle.hybrid(elevation: .flat, pointsOfInterest: showPointsOfInterest ? .all : .excludingAll, showsTraffic: showTraffic) - case .satellite: - UserDefaults.mapLayer = newMapLayer - mapStyle = MapStyle.imagery(elevation: .flat) - case .offline: - return - } - } - } - .onChange(of: node) { - isLookingAround = false + + + private var mapControls: some View { + HStack { + MapScaleView(scope: mapScope) + .mapControlVisibility(.visible) + if showUserLocation { + MapUserLocationButton(scope: mapScope) + .mapControlVisibility(.visible) + } + MapPitchToggle(scope: mapScope) + .mapControlVisibility(.visible) + MapCompass(scope: mapScope) + .mapControlVisibility(.visible) + } + } + + private var mapAccessoryControls: some View { + HStack { + Button(action: { + withAnimation { + isEditingSettings = !isEditingSettings + } + }) { + Image(systemName: isEditingSettings ? "info.circle.fill" : "info.circle") + .padding(.vertical, 5) + } + .tint(Color(UIColor.secondarySystemBackground)) + .foregroundColor(.accentColor) + .buttonStyle(.borderedProminent) + /// Look Around Button + if self.scene != nil { + Button(action: { + if isShowingAltitude { isShowingAltitude = false - mostRecent = node.positions?.lastObject as? PositionEntity - if node.positions?.count ?? 0 > 1 { - position = .automatic - } else { - position = .camera(MapCamera(centerCoordinate: mostRecent!.coordinate, distance: 8000, heading: 0, pitch: 60)) - } - if let mostRecent { - Task { - scene = try? await fetchScene(for: mostRecent.coordinate) - } - } } - .onAppear { - UIApplication.shared.isIdleTimerDisabled = true - switch selectedMapLayer { - case .standard: - mapStyle = MapStyle.standard(elevation: .flat, pointsOfInterest: showPointsOfInterest ? .all : .excludingAll, showsTraffic: showTraffic) - case .hybrid: - mapStyle = MapStyle.hybrid(elevation: .flat, pointsOfInterest: showPointsOfInterest ? .all : .excludingAll, showsTraffic: showTraffic) - case .satellite: - mapStyle = MapStyle.imagery(elevation: .flat) - case .offline: - mapStyle = MapStyle.hybrid(elevation: .flat, pointsOfInterest: showPointsOfInterest ? .all : .excludingAll, showsTraffic: showTraffic) - } - mostRecent = node.positions?.lastObject as? PositionEntity - if node.positions?.count ?? 0 > 1 { - position = .automatic - } else { - position = .camera(MapCamera(centerCoordinate: mostRecent!.coordinate, distance: 8000, heading: 0, pitch: 60)) - } - if self.scene == nil { - Task { - scene = try? await fetchScene(for: mostRecent!.coordinate) - } - } + isLookingAround = !isLookingAround + }) { + Image(systemName: isLookingAround ? "binoculars.fill" : "binoculars") + .padding(.vertical, 5) + } + .tint(Color(UIColor.secondarySystemBackground)) + .foregroundColor(.accentColor) + .buttonStyle(.borderedProminent) + } + /// Altitude Button + if node.positions?.count ?? 0 > 1 { + Button(action: { + if isLookingAround { + isLookingAround = false } - .safeAreaInset(edge: .bottom, alignment: .trailing) { - HStack { - Button(action: { - withAnimation { - isEditingSettings = !isEditingSettings - } - }) { - Image(systemName: isEditingSettings ? "info.circle.fill" : "info.circle") - .padding(.vertical, 5) - } - .tint(Color(UIColor.secondarySystemBackground)) - .foregroundColor(.accentColor) - .buttonStyle(.borderedProminent) - /// Look Around Button - if self.scene != nil { - Button(action: { - if isShowingAltitude { - isShowingAltitude = false - } - isLookingAround = !isLookingAround - }) { - Image(systemName: isLookingAround ? "binoculars.fill" : "binoculars") - .padding(.vertical, 5) - } - .tint(Color(UIColor.secondarySystemBackground)) - .foregroundColor(.accentColor) - .buttonStyle(.borderedProminent) - } - /// Altitude Button - if node.positions?.count ?? 0 > 1 { - Button(action: { - if isLookingAround { - isLookingAround = false - } - isShowingAltitude = !isShowingAltitude - }) { - Image(systemName: isShowingAltitude ? "mountain.2.fill" : "mountain.2") - .padding(.vertical, 5) - } - .tint(Color(UIColor.secondarySystemBackground)) - .foregroundColor(.accentColor) - .buttonStyle(.borderedProminent) - } - } - .controlSize(.regular) - .padding(5) + isShowingAltitude = !isShowingAltitude + }) { + Image(systemName: isShowingAltitude ? "mountain.2.fill" : "mountain.2") + .padding(.vertical, 5) + } + .tint(Color(UIColor.secondarySystemBackground)) + .foregroundColor(.accentColor) + .buttonStyle(.borderedProminent) + } + } + .controlSize(.regular) + .padding(5) + } + + private var connectedDeviceIndicator: some View { + ConnectedDevice( + bluetoothOn: bleManager.isSwitchedOn, + deviceConnected: bleManager.connectedPeripheral != nil, + name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?" + ) + } + + var body: some View { + if node.hasPositions { + Map( + position: $position, + bounds: MapCameraBounds( + minimumDistance: 1, + maximumDistance: .infinity + ), + scope: mapScope + ) { + NodeMapContent(node: node) + } + .mapScope(mapScope) + .mapStyle(mapStyle) + .mapControls { + mapControls + } + .controlSize(.regular) + .overlay(alignment: .bottom) { + if scene != nil, isLookingAround { + LookAroundPreview(initialScene: scene) + .frame(height: UIDevice.current.userInterfaceIdiom == .phone ? 250 : 400) + .clipShape(RoundedRectangle(cornerRadius: 12)) + .padding(.horizontal, 20) + } else if isShowingAltitude { + PositionAltitudeChart(node: node) + .frame(height: UIDevice.current.userInterfaceIdiom == .phone ? 250 : 400) + .clipShape(RoundedRectangle(cornerRadius: 12)) + .padding(.horizontal, 20) + } + } + .sheet(isPresented: $isEditingSettings) { + MapSettingsForm( + traffic: $showTraffic, + pointsOfInterest: $showPointsOfInterest, + mapLayer: $selectedMapLayer, + meshMap: $isMeshMap + ).onChange(of: (selectedMapLayer)) { newMapLayer in + switch selectedMapLayer { + case .standard: + UserDefaults.mapLayer = newMapLayer + mapStyle = MapStyle.standard(elevation: .flat, pointsOfInterest: showPointsOfInterest ? .all : .excludingAll, showsTraffic: showTraffic) + case .hybrid: + UserDefaults.mapLayer = newMapLayer + mapStyle = MapStyle.hybrid(elevation: .flat, pointsOfInterest: showPointsOfInterest ? .all : .excludingAll, showsTraffic: showTraffic) + case .satellite: + UserDefaults.mapLayer = newMapLayer + mapStyle = MapStyle.imagery(elevation: .flat) + case .offline: + return } - .onDisappear { - UIApplication.shared.isIdleTimerDisabled = false + } + } + .onChange(of: node) { + isLookingAround = false + isShowingAltitude = false + if node.positions?.count ?? 0 > 1 { + position = .automatic + } else if let coordinate = mostRecent?.coordinate { + position = .camera(MapCamera(centerCoordinate: coordinate, distance: 8000, heading: 0, pitch: 60)) + } + if let coordinate = mostRecent?.coordinate { + Task { + scene = try await fetchScene(for: coordinate) } - }} - .navigationBarTitle(String((node.user?.shortName ?? "unknown".localized) + (" \(node.positions?.count ?? 0) points")), displayMode: .inline) - .navigationBarItems(trailing: - ZStack { - ConnectedDevice( - bluetoothOn: bleManager.isSwitchedOn, - deviceConnected: bleManager.connectedPeripheral != nil, - name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?") - }) + } + } + .onAppear { + setupMapView() + } + .safeAreaInset(edge: .bottom, alignment: .trailing) { + mapAccessoryControls + } + .onDisappear { + UIApplication.shared.isIdleTimerDisabled = false + } + .navigationBarTitle(title, displayMode: .inline) + .navigationBarItems(trailing: connectedDeviceIndicator) } else { ContentUnavailableView("No Positions", systemImage: "mappin.slash") } } + + private func setupMapView() { + UIApplication.shared.isIdleTimerDisabled = true + switch selectedMapLayer { + case .standard: + mapStyle = MapStyle.standard(elevation: .flat, pointsOfInterest: showPointsOfInterest ? .all : .excludingAll, showsTraffic: showTraffic) + case .hybrid: + mapStyle = MapStyle.hybrid(elevation: .flat, pointsOfInterest: showPointsOfInterest ? .all : .excludingAll, showsTraffic: showTraffic) + case .satellite: + mapStyle = MapStyle.imagery(elevation: .flat) + case .offline: + mapStyle = MapStyle.hybrid(elevation: .flat, pointsOfInterest: showPointsOfInterest ? .all : .excludingAll, showsTraffic: showTraffic) + } + if node.positions?.count ?? 0 > 1 { + position = .automatic + } else if let coordinate = mostRecent?.coordinate { + position = .camera(MapCamera(centerCoordinate: coordinate, distance: 8000, heading: 0, pitch: 60)) + } + if scene == nil, let coordinate = mostRecent?.coordinate { + Task { + scene = try await fetchScene(for: coordinate) + } + } + } + /// Get the look around scene private func fetchScene(for coordinate: CLLocationCoordinate2D) async throws -> MKLookAroundScene? { let lookAroundScene = MKLookAroundSceneRequest(coordinate: coordinate) From b6797b01663412b0e85c541e90c69f8ce86ce52f Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 4 Jun 2024 17:30:58 -0700 Subject: [PATCH 06/32] Move logger --- Meshtastic.xcodeproj/project.pbxproj | 32 +++-- .../xcshareddata/swiftpm/Package.resolved | 132 ++++++++++++++++++ .../xcschemes/WidgetsExtension.xcscheme | 1 + .../{Helpers => Extensions}/Logger.swift | 7 + Meshtastic/Views/Settings/Settings.swift | 13 ++ 5 files changed, 177 insertions(+), 8 deletions(-) create mode 100644 Meshtastic.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved rename Meshtastic/{Helpers => Extensions}/Logger.swift (88%) diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index c0fe6473..9de45152 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -7,7 +7,6 @@ objects = { /* Begin PBXBuildFile section */ - 25183D462C0A6D97001E31D5 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25183D452C0A6D97001E31D5 /* Logger.swift */; }; 6DA39D8E2A92DC52007E311C /* MeshtasticAppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DA39D8D2A92DC52007E311C /* MeshtasticAppDelegate.swift */; }; 6DEDA55A2A957B8E00321D2E /* DetectionSensorLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DEDA5592A957B8E00321D2E /* DetectionSensorLog.swift */; }; 6DEDA55C2A9592F900321D2E /* MessageEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DEDA55B2A9592F900321D2E /* MessageEntityExtension.swift */; }; @@ -166,6 +165,9 @@ DDC94FCE29CF55310082EA6E /* RtttlConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC94FCD29CF55310082EA6E /* RtttlConfig.swift */; }; DDCDC6CB29481FCC004C1DDA /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = DDCDC6CD29481FCC004C1DDA /* Localizable.strings */; }; DDCE4E2C2869F92900BE9F8F /* UserConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDCE4E2B2869F92900BE9F8F /* UserConfig.swift */; }; + DDD28D3A2C0EC4CF0063CFA3 /* LogStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD28D392C0EC4CF0063CFA3 /* LogStore.swift */; }; + DDD28D3C2C0EC51D0063CFA3 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD28D3B2C0EC51D0063CFA3 /* Logger.swift */; }; + DDD28D402C0F82200063CFA3 /* AppLogs.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD28D3F2C0F82200063CFA3 /* AppLogs.swift */; }; DDD43FE32A78C8900083A3E9 /* MqttClientProxyManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD43FE22A78C8900083A3E9 /* MqttClientProxyManager.swift */; }; DDD6EEAF29BC024700383354 /* Firmware.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD6EEAE29BC024700383354 /* Firmware.swift */; }; DDD94A502845C8F5004A87A0 /* DateTimeText.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD94A4F2845C8F5004A87A0 /* DateTimeText.swift */; }; @@ -237,7 +239,6 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 25183D452C0A6D97001E31D5 /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; 258EE1262C0E833D0025A5FB /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 6DA39D8D2A92DC52007E311C /* MeshtasticAppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshtasticAppDelegate.swift; sourceTree = ""; }; 6DEDA5592A957B8E00321D2E /* DetectionSensorLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetectionSensorLog.swift; sourceTree = ""; }; @@ -430,6 +431,9 @@ DDCE4E2B2869F92900BE9F8F /* UserConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserConfig.swift; sourceTree = ""; }; DDD28D362C0CCCD10063CFA3 /* pt-PT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-PT"; path = "pt-PT.lproj/Localizable.strings"; sourceTree = ""; }; DDD28D372C0CD2670063CFA3 /* MeshtasticDataModelV 37.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 37.xcdatamodel"; sourceTree = ""; }; + DDD28D392C0EC4CF0063CFA3 /* LogStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogStore.swift; sourceTree = ""; }; + DDD28D3B2C0EC51D0063CFA3 /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; + DDD28D3F2C0F82200063CFA3 /* AppLogs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLogs.swift; sourceTree = ""; }; DDD43FE22A78C8900083A3E9 /* MqttClientProxyManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MqttClientProxyManager.swift; sourceTree = ""; }; DDD6EEAE29BC024700383354 /* Firmware.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Firmware.swift; sourceTree = ""; }; DDD94A4F2845C8F5004A87A0 /* DateTimeText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateTimeText.swift; sourceTree = ""; }; @@ -603,6 +607,7 @@ DDCE4E2B2869F92900BE9F8F /* UserConfig.swift */, DD61937A2863876A00E59241 /* Config */, DD1B8F3F2B35E2F10022AABC /* GPSStatus.swift */, + DDD28D3F2C0F82200063CFA3 /* AppLogs.swift */, ); path = Settings; sourceTree = ""; @@ -805,6 +810,7 @@ DD86D40D2881BDB300BAEB7A /* Export */, DDDB443E29F79A9400EE2349 /* Extensions */, DDC2E1A526CEB32B0042C5E4 /* Helpers */, + DDD28D382C0EC4A00063CFA3 /* Logging */, DDC2E18826CE24EE0042C5E4 /* Model */, DDC4D5662754996200A4208E /* Persistence */, DDAF8C5626ED07740058C060 /* Protobufs */, @@ -908,7 +914,6 @@ DD964FBC296E6B01007C176F /* EmojiOnlyTextField.swift */, DDDB443C29F6592F00EE2349 /* NetworkManager.swift */, DD3619142B1EF9F900C41C8C /* LocationsHandler.swift */, - 25183D452C0A6D97001E31D5 /* Logger.swift */, ); path = Helpers; sourceTree = ""; @@ -923,6 +928,14 @@ path = Persistence; sourceTree = ""; }; + DDD28D382C0EC4A00063CFA3 /* Logging */ = { + isa = PBXGroup; + children = ( + DDD28D392C0EC4CF0063CFA3 /* LogStore.swift */, + ); + path = Logging; + sourceTree = ""; + }; DDD43FE12A78C86B0083A3E9 /* Mqtt */ = { isa = PBXGroup; children = ( @@ -947,23 +960,24 @@ isa = PBXGroup; children = ( DD007BB12AA59B9A00F5FA12 /* CoreData */, + DDFFA7462B3A7F3C004730DB /* Bundle.swift */, DDDB444529F8A96500EE2349 /* Character.swift */, DDDB444929F8AA3A00EE2349 /* CLLocationCoordinate2D.swift */, DDDB444B29F8AAA600EE2349 /* Color.swift */, DDDB445329F8AD1600EE2349 /* Data.swift */, DDDB445129F8ACF900EE2349 /* Date.swift */, DDDB444129F8A88700EE2349 /* Double.swift */, + DDB75A0E2A05920E006ED576 /* FileManager.swift */, DDDB444329F8A8DD00EE2349 /* Float.swift */, DDDB444D29F8AB0E00EE2349 /* Int.swift */, + DDD28D3B2C0EC51D0063CFA3 /* Logger.swift */, + DD1933772B084F4200771CD5 /* Measurement.swift */, DDDB444729F8A9C900EE2349 /* String.swift */, + DDF45C362BC46A5A005ED5F2 /* TimeZone.swift */, DD77093E2AA1B146007A8BF0 /* UIColor.swift */, DDDB444F29F8AC9C00EE2349 /* UIImage.swift */, DDDB443F29F79AB000EE2349 /* UserDefaults.swift */, - DDB75A0E2A05920E006ED576 /* FileManager.swift */, DDB75A102A059258006ED576 /* Url.swift */, - DD1933772B084F4200771CD5 /* Measurement.swift */, - DDFFA7462B3A7F3C004730DB /* Bundle.swift */, - DDF45C362BC46A5A005ED5F2 /* TimeZone.swift */, ); path = Extensions; sourceTree = ""; @@ -1128,6 +1142,7 @@ DD836AE726F6B38600ABCC23 /* Connect.swift in Sources */, DD0E20FD2B87090400F2D100 /* clientonly.pb.swift in Sources */, D93069082B81DF040066FBC8 /* SaveConfigButton.swift in Sources */, + DDD28D402C0F82200063CFA3 /* AppLogs.swift in Sources */, DD5E523F298F5A9E00D21B61 /* AirQualityIndex.swift in Sources */, DD964FBF296E76EF007C176F /* WaypointFormMapKit.swift in Sources */, DD3501892852FC3B000FC853 /* Settings.swift in Sources */, @@ -1219,7 +1234,6 @@ D93068D32B8129510066FBC8 /* MessageContextMenuItems.swift in Sources */, DD0E20FE2B87090400F2D100 /* paxcount.pb.swift in Sources */, DD5E520A298EE33B00D21B61 /* channel.pb.swift in Sources */, - 25183D462C0A6D97001E31D5 /* Logger.swift in Sources */, DD8EBF43285058FA00426DCA /* DisplayConfig.swift in Sources */, DD964FC42974767D007C176F /* MapViewFitExtension.swift in Sources */, DD47E3D626F17ED900029299 /* CircleText.swift in Sources */, @@ -1251,6 +1265,7 @@ DD86D4112881D16900BAEB7A /* WriteCsvFile.swift in Sources */, DDDB445029F8AC9C00EE2349 /* UIImage.swift in Sources */, DD86D40F2881BE4C00BAEB7A /* CsvDocument.swift in Sources */, + DDD28D3A2C0EC4CF0063CFA3 /* LogStore.swift in Sources */, DDB75A142A0593E2006ED576 /* OfflineTileManager.swift in Sources */, DDB75A1E2A0B0CD0006ED576 /* LoRaSignalStrengthIndicator.swift in Sources */, DDA6B2E928419CF2003E8C16 /* MeshPackets.swift in Sources */, @@ -1262,6 +1277,7 @@ DD4975A52B147BA90026544E /* AmbientLightingConfig.swift in Sources */, D93068D92B81509C0066FBC8 /* TapbackResponses.swift in Sources */, DD86D40A287F04F100BAEB7A /* InvalidVersion.swift in Sources */, + DDD28D3C2C0EC51D0063CFA3 /* Logger.swift in Sources */, DDD94A502845C8F5004A87A0 /* DateTimeText.swift in Sources */, DD5E5212298EE33B00D21B61 /* apponly.pb.swift in Sources */, DDB6ABE228B13FB500384BA1 /* PositionConfigEnums.swift in Sources */, diff --git a/Meshtastic.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Meshtastic.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 00000000..0a15919c --- /dev/null +++ b/Meshtastic.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,132 @@ +{ + "originHash" : "32ea1ad7873163554215310b8eea710c94c63f855b5b01c0b790e7b537747ceb", + "pins" : [ + { + "identity" : "cocoamqtt", + "kind" : "remoteSourceControl", + "location" : "https://github.com/emqx/CocoaMQTT", + "state" : { + "revision" : "85387a2478551ad84f39be8a3c8587d34dd2bcf5", + "version" : "2.1.5" + } + }, + { + "identity" : "collectionconcurrencykit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/JohnSundell/CollectionConcurrencyKit.git", + "state" : { + "revision" : "b4f23e24b5a1bff301efc5e70871083ca029ff95", + "version" : "0.2.0" + } + }, + { + "identity" : "cryptoswift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/krzyzanowskim/CryptoSwift.git", + "state" : { + "revision" : "c9c3df6ab812de32bae61fc0cd1bf6d45170ebf0", + "version" : "1.8.2" + } + }, + { + "identity" : "mqttcocoaasyncsocket", + "kind" : "remoteSourceControl", + "location" : "https://github.com/leeway1208/MqttCocoaAsyncSocket", + "state" : { + "revision" : "ce3e18607fd01079495f86ff6195d8a3ca469f73", + "version" : "1.0.8" + } + }, + { + "identity" : "sourcekitten", + "kind" : "remoteSourceControl", + "location" : "https://github.com/jpsim/SourceKitten.git", + "state" : { + "revision" : "fd4df99170f5e9d7cf9aa8312aa8506e0e7a44e7", + "version" : "0.35.0" + } + }, + { + "identity" : "sqlite.swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/stephencelis/SQLite.swift.git", + "state" : { + "revision" : "7a2e3cd27de56f6d396e84f63beefd0267b55ccb", + "version" : "0.14.1" + } + }, + { + "identity" : "starscream", + "kind" : "remoteSourceControl", + "location" : "https://github.com/daltoniam/Starscream.git", + "state" : { + "revision" : "a063fda2b8145a231953c20e7a646be254365396", + "version" : "3.1.2" + } + }, + { + "identity" : "swift-argument-parser", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-argument-parser.git", + "state" : { + "revision" : "0fbc8848e389af3bb55c182bc19ca9d5dc2f255b", + "version" : "1.4.0" + } + }, + { + "identity" : "swift-protobuf", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-protobuf.git", + "state" : { + "revision" : "ce20dc083ee485524b802669890291c0d8090170", + "version" : "1.22.1" + } + }, + { + "identity" : "swift-syntax", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-syntax.git", + "state" : { + "revision" : "303e5c5c36d6a558407d364878df131c3546fad8", + "version" : "510.0.2" + } + }, + { + "identity" : "swiftlint", + "kind" : "remoteSourceControl", + "location" : "https://github.com/realm/SwiftLint", + "state" : { + "revision" : "b515723b16eba33f15c4677ee65f3fef2ce8c255", + "version" : "0.55.1" + } + }, + { + "identity" : "swiftytexttable", + "kind" : "remoteSourceControl", + "location" : "https://github.com/scottrhoyt/SwiftyTextTable.git", + "state" : { + "revision" : "c6df6cf533d120716bff38f8ff9885e1ce2a4ac3", + "version" : "0.9.0" + } + }, + { + "identity" : "swxmlhash", + "kind" : "remoteSourceControl", + "location" : "https://github.com/drmohundro/SWXMLHash.git", + "state" : { + "revision" : "a853604c9e9a83ad9954c7e3d2a565273982471f", + "version" : "7.0.2" + } + }, + { + "identity" : "yams", + "kind" : "remoteSourceControl", + "location" : "https://github.com/jpsim/Yams.git", + "state" : { + "revision" : "9234124cff5e22e178988c18d8b95a8ae8007f76", + "version" : "5.1.2" + } + } + ], + "version" : 3 +} diff --git a/Meshtastic.xcodeproj/xcshareddata/xcschemes/WidgetsExtension.xcscheme b/Meshtastic.xcodeproj/xcshareddata/xcschemes/WidgetsExtension.xcscheme index 880339bc..decd8381 100644 --- a/Meshtastic.xcodeproj/xcshareddata/xcschemes/WidgetsExtension.xcscheme +++ b/Meshtastic.xcodeproj/xcshareddata/xcschemes/WidgetsExtension.xcscheme @@ -89,6 +89,7 @@ savedToolIdentifier = "" useCustomWorkingDirectory = "NO" debugDocumentVersioning = "YES" + askForAppToLaunch = "Yes" launchAutomaticallySubstyle = "2"> diff --git a/Meshtastic/Helpers/Logger.swift b/Meshtastic/Extensions/Logger.swift similarity index 88% rename from Meshtastic/Helpers/Logger.swift rename to Meshtastic/Extensions/Logger.swift index bf9ad575..e5c29a95 100644 --- a/Meshtastic/Helpers/Logger.swift +++ b/Meshtastic/Extensions/Logger.swift @@ -1,3 +1,10 @@ +// +// Logger.swift +// Meshtastic +// +// Copyright(c) Garth Vander Houwen 6/3/24. +// + import OSLog extension Logger { diff --git a/Meshtastic/Views/Settings/Settings.swift b/Meshtastic/Views/Settings/Settings.swift index f2a38030..86c83e3d 100644 --- a/Meshtastic/Views/Settings/Settings.swift +++ b/Meshtastic/Views/Settings/Settings.swift @@ -47,6 +47,7 @@ struct Settings: View { case telemetryConfig case meshLog case adminMessageLog + case appLog case about } var body: some View { @@ -423,6 +424,18 @@ struct Settings: View { } } .tag(SettingsSidebar.adminMessageLog) +#if DEBUG + NavigationLink { + // AppLogs() + } label: { + Label { + Text("app.log") + } icon: { + Image(systemName: "building.columns") + } + } + .tag(SettingsSidebar.appLog) +#endif } Section(header: Text("Firmware")) { NavigationLink { From 1a297246be669b7e3e39e6a696a95e7f074ec24d Mon Sep 17 00:00:00 2001 From: Blake McAnally Date: Tue, 4 Jun 2024 21:58:40 -0500 Subject: [PATCH 07/32] Fix the project file, and fix a bad merge in the portugese strings file --- Meshtastic.xcodeproj/project.pbxproj | 16 -- .../xcschemes/WidgetsExtension.xcscheme | 1 - pt-PT.lproj/Localizable.strings | 226 +++++++++--------- 3 files changed, 113 insertions(+), 130 deletions(-) diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 9de45152..47cce0cd 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -165,9 +165,7 @@ DDC94FCE29CF55310082EA6E /* RtttlConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC94FCD29CF55310082EA6E /* RtttlConfig.swift */; }; DDCDC6CB29481FCC004C1DDA /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = DDCDC6CD29481FCC004C1DDA /* Localizable.strings */; }; DDCE4E2C2869F92900BE9F8F /* UserConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDCE4E2B2869F92900BE9F8F /* UserConfig.swift */; }; - DDD28D3A2C0EC4CF0063CFA3 /* LogStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD28D392C0EC4CF0063CFA3 /* LogStore.swift */; }; DDD28D3C2C0EC51D0063CFA3 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD28D3B2C0EC51D0063CFA3 /* Logger.swift */; }; - DDD28D402C0F82200063CFA3 /* AppLogs.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD28D3F2C0F82200063CFA3 /* AppLogs.swift */; }; DDD43FE32A78C8900083A3E9 /* MqttClientProxyManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD43FE22A78C8900083A3E9 /* MqttClientProxyManager.swift */; }; DDD6EEAF29BC024700383354 /* Firmware.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD6EEAE29BC024700383354 /* Firmware.swift */; }; DDD94A502845C8F5004A87A0 /* DateTimeText.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD94A4F2845C8F5004A87A0 /* DateTimeText.swift */; }; @@ -431,9 +429,7 @@ DDCE4E2B2869F92900BE9F8F /* UserConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserConfig.swift; sourceTree = ""; }; DDD28D362C0CCCD10063CFA3 /* pt-PT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-PT"; path = "pt-PT.lproj/Localizable.strings"; sourceTree = ""; }; DDD28D372C0CD2670063CFA3 /* MeshtasticDataModelV 37.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 37.xcdatamodel"; sourceTree = ""; }; - DDD28D392C0EC4CF0063CFA3 /* LogStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogStore.swift; sourceTree = ""; }; DDD28D3B2C0EC51D0063CFA3 /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; - DDD28D3F2C0F82200063CFA3 /* AppLogs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLogs.swift; sourceTree = ""; }; DDD43FE22A78C8900083A3E9 /* MqttClientProxyManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MqttClientProxyManager.swift; sourceTree = ""; }; DDD6EEAE29BC024700383354 /* Firmware.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Firmware.swift; sourceTree = ""; }; DDD94A4F2845C8F5004A87A0 /* DateTimeText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateTimeText.swift; sourceTree = ""; }; @@ -607,7 +603,6 @@ DDCE4E2B2869F92900BE9F8F /* UserConfig.swift */, DD61937A2863876A00E59241 /* Config */, DD1B8F3F2B35E2F10022AABC /* GPSStatus.swift */, - DDD28D3F2C0F82200063CFA3 /* AppLogs.swift */, ); path = Settings; sourceTree = ""; @@ -810,7 +805,6 @@ DD86D40D2881BDB300BAEB7A /* Export */, DDDB443E29F79A9400EE2349 /* Extensions */, DDC2E1A526CEB32B0042C5E4 /* Helpers */, - DDD28D382C0EC4A00063CFA3 /* Logging */, DDC2E18826CE24EE0042C5E4 /* Model */, DDC4D5662754996200A4208E /* Persistence */, DDAF8C5626ED07740058C060 /* Protobufs */, @@ -928,14 +922,6 @@ path = Persistence; sourceTree = ""; }; - DDD28D382C0EC4A00063CFA3 /* Logging */ = { - isa = PBXGroup; - children = ( - DDD28D392C0EC4CF0063CFA3 /* LogStore.swift */, - ); - path = Logging; - sourceTree = ""; - }; DDD43FE12A78C86B0083A3E9 /* Mqtt */ = { isa = PBXGroup; children = ( @@ -1142,7 +1128,6 @@ DD836AE726F6B38600ABCC23 /* Connect.swift in Sources */, DD0E20FD2B87090400F2D100 /* clientonly.pb.swift in Sources */, D93069082B81DF040066FBC8 /* SaveConfigButton.swift in Sources */, - DDD28D402C0F82200063CFA3 /* AppLogs.swift in Sources */, DD5E523F298F5A9E00D21B61 /* AirQualityIndex.swift in Sources */, DD964FBF296E76EF007C176F /* WaypointFormMapKit.swift in Sources */, DD3501892852FC3B000FC853 /* Settings.swift in Sources */, @@ -1265,7 +1250,6 @@ DD86D4112881D16900BAEB7A /* WriteCsvFile.swift in Sources */, DDDB445029F8AC9C00EE2349 /* UIImage.swift in Sources */, DD86D40F2881BE4C00BAEB7A /* CsvDocument.swift in Sources */, - DDD28D3A2C0EC4CF0063CFA3 /* LogStore.swift in Sources */, DDB75A142A0593E2006ED576 /* OfflineTileManager.swift in Sources */, DDB75A1E2A0B0CD0006ED576 /* LoRaSignalStrengthIndicator.swift in Sources */, DDA6B2E928419CF2003E8C16 /* MeshPackets.swift in Sources */, diff --git a/Meshtastic.xcodeproj/xcshareddata/xcschemes/WidgetsExtension.xcscheme b/Meshtastic.xcodeproj/xcshareddata/xcschemes/WidgetsExtension.xcscheme index decd8381..880339bc 100644 --- a/Meshtastic.xcodeproj/xcshareddata/xcschemes/WidgetsExtension.xcscheme +++ b/Meshtastic.xcodeproj/xcshareddata/xcschemes/WidgetsExtension.xcscheme @@ -89,7 +89,6 @@ savedToolIdentifier = "" useCustomWorkingDirectory = "NO" debugDocumentVersioning = "YES" - askForAppToLaunch = "Yes" launchAutomaticallySubstyle = "2"> diff --git a/pt-PT.lproj/Localizable.strings b/pt-PT.lproj/Localizable.strings index f5454497..599955bc 100644 --- a/pt-PT.lproj/Localizable.strings +++ b/pt-PT.lproj/Localizable.strings @@ -1,4 +1,4 @@ - /* +/* Localizable.strings Meshtastic @@ -65,20 +65,20 @@ "config.power.shutdown.on.power.loss"="Desligar em caso de Perda de Energia"; "config.power.shutdown.after.secs"="Após"; "config.power.wait.bluetooth.secs"="Desligar o Bluetooth Após"; -"config.ringtone"="="Toque RTTTL"; -"config.ringtone.title"="="Configuração de Toque"; -"config.ringtone.label"="="Idioma de Transferência de Toque"; -"config.ringtone.description"="="Idioma de Transferência de Toque (RTTTL) Sequência de Toque usada por campainhas suportadas em notificações externas."; -"config.module.paxcounter.settings"="="Contador de PAX"; -"config.module.paxcounter.title"="="Configuração do Contador de PAX"; -"config.module.paxcounter.enabled.description"="="Quando ativado, o módulo de Contador de PAX conta o número de pessoas que passam usando Wi-Fi e Bluetooth. Tanto o Wi-Fi quanto o Bluetooth devem estar desativados para que o contador de PAX funcione."; -"config.module.paxcounter.updateinterval"="="Intervalo de Atualização"; -"config.module.paxcounter.updateinterval.description"="="Com que frequência podemos enviar uma mensagem para a malha quando as pessoas são detectadas."; -"config.save.confirm"="="Após salvar os valores de configuração, o nó reiniciará"; -"communicating"="="Comunicando com dispositivo. ."; -"connected.radio"="="Rádio Conectado"; +"config.ringtone"="Toque RTTTL"; +"config.ringtone.title"="Configuração de Toque"; +"config.ringtone.label"="Idioma de Transferência de Toque"; +"config.ringtone.description"="Idioma de Transferência de Toque (RTTTL) Sequência de Toque usada por campainhas suportadas em notificações externas."; +"config.module.paxcounter.settings"="Contador de PAX"; +"config.module.paxcounter.title"="Configuração do Contador de PAX"; +"config.module.paxcounter.enabled.description"="Quando ativado, o módulo de Contador de PAX conta o número de pessoas que passam usando Wi-Fi e Bluetooth. Tanto o Wi-Fi quanto o Bluetooth devem estar desativados para que o contador de PAX funcione."; +"config.module.paxcounter.updateinterval"="Intervalo de Atualização"; +"config.module.paxcounter.updateinterval.description"="Com que frequência podemos enviar uma mensagem para a malha quando as pessoas são detectadas."; +"config.save.confirm"="Após salvar os valores de configuração, o nó reiniciará"; +"communicating"="Comunicando com dispositivo. ."; +"connected.radio"="Rádio Conectado"; "connected"="Bluetooth Connectado"; -"connecting"="="Conectando . ."; +"connecting"="Conectando . ."; "contacts"="Contactos"; "contacts %@"="Contactos (%@)"; "copy"="Copiar"; @@ -86,24 +86,24 @@ "default"="Padrão"; "delete"="Apagar"; "detection.sensor"="Sensor de Detecção"; -"detection.sensor.config"="="Configuração do Sensor de Detecção"; +"detection.sensor.config"="Configuração do Sensor de Detecção"; "detection.sensor.log"="Log Sensor de Detecção"; -"device"="="Dispositivo"; -"device.config"="="Configuração do Dispositivo"; +"device"="Dispositivo"; +"device.config"="Configuração do Dispositivo"; "device.configuration"="Configuração do Dispositivo"; -"device.metrics.delete"="="Apagar todas as métricas do dispositivo?"; +"device.metrics.delete"="Apagar todas as métricas do dispositivo?"; "device.metrics.log"="Log g de Métricas do Dispositivo"; -"device.role.client"="="Dispositivo conectado ao App ou independente para mensagens."; -"device.role.clientmute"="="Dispositivo que não encaminha pacotes de outros dispositivos."; -"device.role.clienthidden"="="Dispositivo que apenas transmite conforme necessário em modo furtivo ou economia de energia."; +"device.role.client"="Dispositivo conectado ao App ou independente para mensagens."; +"device.role.clientmute"="Dispositivo que não encaminha pacotes de outros dispositivos."; +"device.role.clienthidden"="Dispositivo que apenas transmite conforme necessário em modo furtivo ou economia de energia."; "device.role.tracker"="Transmite pacotes de posição GPS como prioridade."; -"device.role.lostandfound"="="Transmite a localização como mensagem para o canal padrão regularmente para auxiliar na recuperação do dispositivo."; -"device.role.sensor"="="="Transmite pacotes de telemetria como prioridade."; +"device.role.lostandfound"="Transmite a localização como mensagem para o canal padrão regularmente para auxiliar na recuperação do dispositivo."; +"device.role.sensor"="Transmite pacotes de telemetria como prioridade."; "device.role.tak"="Otimizado para comunicação do sistema ATAK, reduz transmissões rotineiras."; -"device.role.taktracker"="="="Permite transmissões automáticas de TAK PLI e reduz transmissões rotineiras."; -"device.role.repeater"="="Nó de infraestrutura para ampliar a cobertura da rede transmitindo mensagens com sobrecarga mínima. Não visível na lista de Nós."; -"device.role.router"="="Nó de infraestrutura para ampliar a cobertura da rede transmitindo mensagens. Visível na lista de Nós."; -"device.role.routerclient"="="Combinação de ROTEADOR e CLIENTE. Não para dispositivos móveis."; +"device.role.taktracker"="Permite transmissões automáticas de TAK PLI e reduz transmissões rotineiras."; +"device.role.repeater"="Nó de infraestrutura para ampliar a cobertura da rede transmitindo mensagens com sobrecarga mínima. Não visível na lista de Nós."; +"device.role.router"="Nó de infraestrutura para ampliar a cobertura da rede transmitindo mensagens. Visível na lista de Nós."; +"device.role.routerclient"="Combinação de ROTEADOR e CLIENTE. Não para dispositivos móveis."; "direct.messages"="Mensagens Directas"; "dismiss.keyboard"="Dispensar"; "display"="Icrã"; @@ -119,25 +119,25 @@ "external.notification.config"="Configuração de Notificação Externa"; "finish"="Terminar"; "firmware.version"="Versão do Firmware"; -"firmware.version.unsupported"=" "Versão de Firmware não suportada detetada, impossível conectar ao dispositivo."; +"firmware.version.unsupported"="Versão de Firmware não suportada detetada, impossível conectar ao dispositivo."; "gas"="Gas"; -"gas.resistance"=" "Resistência ao Gas"; -"generate.qr.code"=" "Gerar Código QR"; -"gpsformat.dec"=" "Formato de Graus Decimais"; -"gpsformat.dms"=" "Graus Minutos Segundos"; +"gas.resistance"="Resistência ao Gas"; +"generate.qr.code"="Gerar Código QR"; +"gpsformat.dec"="Formato de Graus Decimais"; +"gpsformat.dms"="Graus Minutos Segundos"; "gpsformat.utm"="Universal Transverse Mercator"; -"gpsformat.mgrs"=" "Sistema de Referência de Grelha Militar"; -"gpsformat.olc"=" "Código de Localização Aberto (também conhecido como Plus Codes)"; -"gpsformat.osgr"=" "Referência de Grelha da Ordnance Survey"; -"gpsmode.disabled"=" "Desativado"; -"gpsmode.enabled"=" "Ativado"; +"gpsformat.mgrs"="Sistema de Referência de Grelha Militar"; +"gpsformat.olc"="Código de Localização Aberto (também conhecido como Plus Codes)"; +"gpsformat.osgr"="Referência de Grelha da Ordnance Survey"; +"gpsmode.disabled"="Desativado"; +"gpsmode.enabled"="Ativado"; "gpsmode.notPresent"="Não Presente"; "heard"="Ouvido"; "heard.last"="Último Ouvido"; "hybrid"="Híbrido"; "hybrid.flyover"="Híbrido o de Sobrevoo"; "include"="Incluir"; -"incomplete"="Incompleto”; +"incomplete"="Incompleto"; "inputevent.none"="Nenhum"; "inputevent.up"="Para Cima"; "inputevent.down"="Para Baixo"; @@ -181,57 +181,57 @@ "lora.config"="Configuração LoRa"; "map"="Mapa do Mesh"; "map.type"="Tipo Padrão"; -"map.centering"=" "Modo de Centralização"; +"map.centering"="Modo de Centralização"; "map.tiles.delete"="Apagar Todas as Imagens da Mapa"; -"map.recentering"=" "Re-centralização Automática"; -"map.use.legacy"=" "Utilizar Mapa do Mesh Antigo"; -"map.usertrackingmode"=" "Modo de Rastreamento do Utilizador"; -"map.usertrackingmode.follow"=" "Seguir"; -"map.usertrackingmode.followwithheading"=" "Seguir com Direção"; +"map.recentering"="Re-centralização Automática"; +"map.use.legacy"="Utilizar Mapa do Mesh Antigo"; +"map.usertrackingmode"="Modo de Rastreamento do Utilizador"; +"map.usertrackingmode.follow"="Seguir"; +"map.usertrackingmode.followwithheading"="Seguir com Direção"; "map.usertrackingmode.none"="Nenhum"; -"mesh.live.activity"=" "Atividade Ao Vivo do Mesh"; +"mesh.live.activity"="Atividade Ao Vivo do Mesh"; "mesh.log"="Log do Mesh"; -"mesh.log.ambientlighting.config %@"=" "Configuração do módulo de Iluminação Ambiente recebida: %@"; -"mesh.log.bluetooth.config %@"=" "Configuração Bluetooth recebida: %@"; -"mesh.log.cannedmessage.config %@"=" "Configuração do módulo de Mensagens Padrão recebida: %@"; -"mesh.log.cannedmessages.messages.get %@"=" "Mensagens Padrão solicitadas para o módulo de mensagens para o nó: %@"; -"mesh.log.cannedmessages.messages.received %@"=" "Mensagens Padrão recebidas para: %@"; -"mesh.log.channel.sent %@ %d"=" "Um Canal Enviado para: %@ Índice do Canal %d"; -"mesh.log.channel.received %d %@"=" "Canal %d recebido de: %@"; -"mesh.log.device.config %@"=" "Configuração do dispositivo recebida: %@"; -"mesh.log.display.config %@"=" "Configuração do icrãn recebida: %@"; -"mesh.log.devicemetadata %@"=" "Solicitando os Metadados do Dispositivo para %@"; -"mesh.log.device.metadata.received %@"=" "Os Metadados do dispositivo recebidos de: %@"; -"mesh.log.detectionsensor.config %@"=" "Configuração do módulo de sensor de detecção recebida: %@"; -"mesh.log.externalnotification.config %@"=" "Configuração do módulo de notificação externa recebida: %@"; -"mesh.log.lora.config %@"=" "Configuração LoRa recebida: %@"; +"mesh.log.ambientlighting.config %@"="Configuração do módulo de Iluminação Ambiente recebida: %@"; +"mesh.log.bluetooth.config %@"="Configuração Bluetooth recebida: %@"; +"mesh.log.cannedmessage.config %@"="Configuração do módulo de Mensagens Padrão recebida: %@"; +"mesh.log.cannedmessages.messages.get %@"="Mensagens Padrão solicitadas para o módulo de mensagens para o nó: %@"; +"mesh.log.cannedmessages.messages.received %@"="Mensagens Padrão recebidas para: %@"; +"mesh.log.channel.sent %@ %d"="Um Canal Enviado para: %@ Índice do Canal %d"; +"mesh.log.channel.received %d %@"="Canal %d recebido de: %@"; +"mesh.log.device.config %@"="Configuração do dispositivo recebida: %@"; +"mesh.log.display.config %@"="Configuração do icrãn recebida: %@"; +"mesh.log.devicemetadata %@"="Solicitando os Metadados do Dispositivo para %@"; +"mesh.log.device.metadata.received %@"="Os Metadados do dispositivo recebidos de: %@"; +"mesh.log.detectionsensor.config %@"="Configuração do módulo de sensor de detecção recebida: %@"; +"mesh.log.externalnotification.config %@"="Configuração do módulo de notificação externa recebida: %@"; +"mesh.log.lora.config %@"="Configuração LoRa recebida: %@"; "mesh.log.lora.config.sent %@"="Configuração do LoRa Enviado para: %@"; -"mesh.log.mqtt.config %@"=" "Configuração do módulo MQTT recebida: %@"; +"mesh.log.mqtt.config %@"="Configuração do módulo MQTT recebida: %@"; "mesh.log.myinfo %@"="MyInfo recebido: %@"; -"mesh.log.network.config %@"=" "Configuração de rede recebida: %@"; -"mesh.log.nodeinfo.received %@"=" "Informações do nó recebidas para: %@"; -"mesh.log.paxcounter %@"=" "Mensagem do Contador PAX recebida de: %@"; -"mesh.log.paxcounter.config %@"=" "Configuração do Contador PAX recebida: %@"; -"mesh.log.position.config %@"=" "Configuração de posição recebida: %@"; -"mesh.log.position.received %@"=" "Pacote de posição recebido do nó: %@"; -"mesh.log.power.config %@"=" "Configuração de energia recebida: %@"; -"mesh.log.rangetest.config %@"=" "Configuração do módulo de teste de alcance recebida: %@"; -"mesh.log.ringtone.config %@"=" "Configuração de toque RTTTL recebida: %@"; -"mesh.log.routing.message %@ %@"=" "Roteamento recebido para RequestID: %@ Estado de Ack: %@"; -"mesh.log.serial.config %@"=" "Configuração do módulo serial recebida: %@"; -"mesh.log.sharelocation %@"=" "Enviado um Pacote de Posição do GPS do dispositivo Apple para o nó: %@"; -"mesh.log.storeforward.config %@"=" "Configuração do módulo Store & Forward recebida: %@"; -"mesh.log.telemetry.config %@"=" "Configuração do módulo de telemetria recebida: %@"; -"mesh.log.telemetry.received %@"=" "Telemetria recebida para: %@"; -"mesh.log.textmessage.received"=" "Mensagem recebida do App de mensagem de texto."; -"mesh.log.textmessage.send.failed %@"=" "Falha no envio da mensagem, não conectado corretamente a %@"; -"mesh.log.textmessage.sent %@ %@ %@"=" "Mensagem enviada %@ de %@ para %@"; -"mesh.log.traceroute.received.direct %@"=" "Solicitação de Rastreamento enviada para o nó: %@" foi recebida diretamente."; -"mesh.log.traceroute.received.route %@"=" "Solicitação de Rastreamento retornada: %@"; -"mesh.log.traceroute.sent %@"=" "Enviei uma solicitação de Rastreamento para o nó: %@"; -"mesh.log.wantconfig %@"=" "Emitindo Configuração Desejada para %@"; -"mesh.log.waypoint.sent %@"=" "Enviado um Pacote de Ponto de Referência de: %@"; -"mesh.log.waypoint.received %@"=" "Pacote de Ponto de Referência recebido do nó: %@"; +"mesh.log.network.config %@"="Configuração de rede recebida: %@"; +"mesh.log.nodeinfo.received %@"="Informações do nó recebidas para: %@"; +"mesh.log.paxcounter %@"="Mensagem do Contador PAX recebida de: %@"; +"mesh.log.paxcounter.config %@"="Configuração do Contador PAX recebida: %@"; +"mesh.log.position.config %@"="Configuração de posição recebida: %@"; +"mesh.log.position.received %@"="Pacote de posição recebido do nó: %@"; +"mesh.log.power.config %@"="Configuração de energia recebida: %@"; +"mesh.log.rangetest.config %@"="Configuração do módulo de teste de alcance recebida: %@"; +"mesh.log.ringtone.config %@"="Configuração de toque RTTTL recebida: %@"; +"mesh.log.routing.message %@ %@"="Roteamento recebido para RequestID: %@ Estado de Ack: %@"; +"mesh.log.serial.config %@"="Configuração do módulo serial recebida: %@"; +"mesh.log.sharelocation %@"="Enviado um Pacote de Posição do GPS do dispositivo Apple para o nó: %@"; +"mesh.log.storeforward.config %@"="Configuração do módulo Store & Forward recebida: %@"; +"mesh.log.telemetry.config %@"="Configuração do módulo de telemetria recebida: %@"; +"mesh.log.telemetry.received %@"="Telemetria recebida para: %@"; +"mesh.log.textmessage.received"="Mensagem recebida do App de mensagem de texto."; +"mesh.log.textmessage.send.failed %@"="Falha no envio da mensagem, não conectado corretamente a %@"; +"mesh.log.textmessage.sent %@ %@ %@"="Mensagem enviada %@ de %@ para %@"; +"mesh.log.traceroute.received.direct %@"="Solicitação de Rastreamento enviada para o nó: %@ foi recebida diretamente."; +"mesh.log.traceroute.received.route %@"="Solicitação de Rastreamento retornada: %@"; +"mesh.log.traceroute.sent %@"="Enviei uma solicitação de Rastreamento para o nó: %@"; +"mesh.log.wantconfig %@"="Emitindo Configuração Desejada para %@"; +"mesh.log.waypoint.sent %@"="Enviado um Pacote de Ponto de Referência de: %@"; +"mesh.log.waypoint.received %@"="Pacote de Ponto de Referência recebido do nó: %@"; "message"="Mensagem"; "message.details"="Dados de Mensagem"; "messages"="Mensagens"; @@ -240,9 +240,9 @@ "mqtt"="MQTT"; "mqtt.connect"="Conectar ao MQTT"; "mqtt.config"="Configuração MQTT"; -"mqtt.clientproxy"=" "Proxy do Cliente MQTT"; -"mqtt.disconnect"=" "Desconectar do MQTT"; -"mqtt.username"=" "Nome de Utilizador"; +"mqtt.clientproxy"="Proxy do Cliente MQTT"; +"mqtt.disconnect"="Desconectar do MQTT"; +"mqtt.username"="Nome de Utilizador"; "name"="Nome"; "network"="Rede"; "network.config"="Configuração de Rede"; @@ -251,29 +251,29 @@ "nodelist.filter.distance %@"="até %@ de distância"; "save.config %@"="Salvar Configuração para %@"; "no.nodes"="Nenhum Nó Meshtastic Encontrado"; -"not.connected"=" "Nenhum dispositivo conectado"; -"numbers.punctuation"=" "Números e Pontuação"; +"not.connected"="Nenhum dispositivo conectado"; +"numbers.punctuation"="Números e Pontuação"; "off"="Desligado"; "offline"="Offline"; -"on.boot"=" "No arranque"; +"on.boot"="No arranque"; "options"="Opções"; "password"="Senha"; "pause"="Pausa"; "paxcounter.ble"="BLE"; -"paxcounter.delete"=" "Apagar todos os dados de pax?"; +"paxcounter.delete"="Apagar todos os dados de pax?"; "paxcounter.wifi"="WiFi"; -"paxcounter.content.unavailable"=" "Nenhum Log do Contador PAX Disponível"; -"paxcounter.log"=" "Log do Contador PAX"; +"paxcounter.content.unavailable"="Nenhum Log do Contador PAX Disponível"; +"paxcounter.log"="Log do Contador PAX"; "paxcounter.total"="Total de PAX"; "phone.gps"="GPS do Telefone"; -"phone.gps.interval.description"=" "Com que frequência seu telefone enviará sua localização para o dispositivo, as atualizações de localização no mesh são geridas pelo dispositivo."; +"phone.gps.interval.description"="Com que frequência seu telefone enviará sua localização para o dispositivo, as atualizações de localização no mesh são geridas pelo dispositivo."; "position"="Posição"; "position.config"="Configuração de Posição"; "position.precision %@"="Dentro de %@"; "preferred.radio"="Rádio Preferido"; "radio.configuration"="Configuração de Rádio"; "range.test"="Teste de Alcance"; -"range.test.blocked"=" "Bloquear Teste de Alcance"; +"range.test.blocked"="Bloquear Teste de Alcance"; "range.test.config"="Configuração do teste de Alcance"; "reply"="Responder"; "reboot"="Reiniciar"; @@ -292,7 +292,7 @@ "routes"="Rotas"; "routes.activitytype.walking"="Caminhada"; "routes.activitytype.hiking"="Caminhada na Montanha"; -"routes.activitytype.biking"=" "Passeio de Bicicleta"; +"routes.activitytype.biking"="Passeio de Bicicleta"; "routes.activitytype.driving"="Conduzir"; "routes.activitytype.overlanding"="Overlanding"; "routes.activitytype.skiing"="Esqui"; @@ -302,17 +302,17 @@ "routes.activitytype.filename.driving"="Conduzir"; "routes.activitytype.filename.overlanding"="Caminhar overland"; "routes.activitytype.filename.skiing"="Passeio de esqui"; -"routing.acknowledged"=" "Reconhecido"; +"routing.acknowledged"="Reconhecido"; "routing.noroute"="Sem Rota"; -"routing.gotnak"=" "Recebido um reconhecimento negativo"; -"routing.timeout"=" "Tempo Esgotado"; +"routing.gotnak"="Recebido um reconhecimento negativo"; +"routing.timeout"="Tempo Esgotado"; "routing.nointerface"="Sem Interface"; -"routing.maxretransmit"=" "Máximo de Retransmissão Alcançado"; +"routing.maxretransmit"="Máximo de Retransmissão Alcançado"; "routing.nochannel"="Sem Canal"; "routing.toolarge"="O pacote é grande de mais"; "routing.noresponse"="Sem Resposta"; "routing.dutycyclelimit"="O limite do Regional Duty Cycle foi abrangido"; -"routing.badRequest"="="Pedido Ruim"; +"routing.badRequest"="Pedido Ruim"; "routing.notauthorized"="Não Autorizado"; "satellite"="Satéllite"; "satellite.flyover"="Passagem de Satélite"; @@ -336,13 +336,13 @@ "standard"="Padrão"; "standard.muted"="Padrão Silenciado"; "start"="Iniciar"; -"storeforward"="="Armazenar e Encaminhar"; +"storeforward"="Armazenar e Encaminhar"; "storeforward.config"="Configuração de Armazenar e Encaminhar"; -"storeforward.heartbeat"="="Enviar Batimento Cardíaco"; +"storeforward.heartbeat"="Enviar Batimento Cardíaco"; "ssid"="SSID"; "tapback"="Resposta Tapback"; "tapback.heart"="Coração"; -"tapback.thumbsup"="="Polegar para Cima"; +"tapback.thumbsup"="Polegar para Cima"; "tapback.thumbsdown"="Polegar para Baixo"; "tapback.haha"="HaHa"; "tapback.exclamation"="Ponto de Exclamação"; @@ -354,19 +354,19 @@ "timeout"="Tempo Limite"; "timestamp"="Carimbo de Data/Hora"; "tip.bluetooth.connect.title"="Rádio Conectado"; -"tip.bluetooth.connect.message"="="Mostra informações para o rádio LoRa conectado via bluetooth. Você pode deslizar para a esquerda para desconectar o rádio e pressionar por um longo período para ver estatísticas ou iniciar a atividade ao vivo."; -"tip.channel.admin.title"="="Canal de Administração"; -"tip.channel.admin.message"="="Canal de administração detectado: Selecione um nó do menu suspenso para gerir dispositivos conectados ou remotos."; -"tip.channels.create.title"="="Gerir Canais"; -"tip.channels.create.message"="="A maioria dos dados na sua malha é enviada pelo canal principal. Você pode configurar canais secundários para criar grupos de mensagens adicionais protegidos por sua própria chave. [Channel config tips](https://meshtastic.org/docs/configuration/tips/)"; -"tip.channels.share.title"="="Compartilhando Canais Meshtastis"; -"tip.channels.share.message"="="Um código QR Meshtastic contém a configuração LoRa e os valores do canal necessários para os rádios se comunicarem. Você pode compartilhar uma configuração completa do canal usando a opção Substituir Canais; se você escolher Adicionar Canais, seus canais compartilhados serão adicionados aos canais no rádio receptor."; +"tip.bluetooth.connect.message"="Mostra informações para o rádio LoRa conectado via bluetooth. Você pode deslizar para a esquerda para desconectar o rádio e pressionar por um longo período para ver estatísticas ou iniciar a atividade ao vivo."; +"tip.channel.admin.title"="Canal de Administração"; +"tip.channel.admin.message"="Canal de administração detectado: Selecione um nó do menu suspenso para gerir dispositivos conectados ou remotos."; +"tip.channels.create.title"="Gerir Canais"; +"tip.channels.create.message"="A maioria dos dados na sua malha é enviada pelo canal principal. Você pode configurar canais secundários para criar grupos de mensagens adicionais protegidos por sua própria chave. [Channel config tips](https://meshtastic.org/docs/configuration/tips/)"; +"tip.channels.share.title"="Compartilhando Canais Meshtastis"; +"tip.channels.share.message"="Um código QR Meshtastic contém a configuração LoRa e os valores do canal necessários para os rádios se comunicarem. Você pode compartilhar uma configuração completa do canal usando a opção Substituir Canais; se você escolher Adicionar Canais, seus canais compartilhados serão adicionados aos canais no rádio receptor."; "tip.messages.title"="Mensagens"; -"tip.messages.message"="="Você pode enviar e receber mensagens de canal (conversas em grupo) e mensagens diretas. De qualquer mensagem, você pode pressionar por um longo período para ver ações disponíveis como copiar, responder, tapback e excluir, bem como detalhes de entrega."; +"tip.messages.message"="Você pode enviar e receber mensagens de canal (conversas em grupo) e mensagens diretas. De qualquer mensagem, você pode pressionar por um longo período para ver ações disponíveis como copiar, responder, tapback e excluir, bem como detalhes de entrega."; "twitter"="Twitter"; "unknown"="Desconhecido"; "unknown.age"="Idade Desconhecido"; -"unset"="="Não Definido"; +"unset"="Não Definido"; "update.firmware"="Atualiza o Seu Firmware"; "update.interval"="Intervalo de Atualização"; "uptime"="Tempo No Ár"; From 408f3324589f41a62da9bf87d1968dca9e01b272 Mon Sep 17 00:00:00 2001 From: Blake McAnally Date: Tue, 4 Jun 2024 22:28:57 -0500 Subject: [PATCH 08/32] Fix misc warnings --- Meshtastic/Export/LogDocument.swift | 6 ++--- Meshtastic/Helpers/MeshLogger.swift | 2 +- Meshtastic/Views/Messages/Messages.swift | 1 - .../Map/MapContent/MeshMapContent.swift | 2 +- Meshtastic/Views/Settings/Routes.swift | 23 ++++++++++--------- 5 files changed, 16 insertions(+), 18 deletions(-) diff --git a/Meshtastic/Export/LogDocument.swift b/Meshtastic/Export/LogDocument.swift index 0732d189..fb098f07 100644 --- a/Meshtastic/Export/LogDocument.swift +++ b/Meshtastic/Export/LogDocument.swift @@ -11,12 +11,10 @@ struct LogDocument: FileDocument { } init(configuration: ReadConfiguration) throws { - guard let data = configuration.file.regularFileContents, - let string = String(data: data, encoding: .utf8) - else { + guard let data = configuration.file.regularFileContents else { throw CocoaError(.fileReadCorruptFile) } - logFile = string + logFile = String(decoding: data, as: UTF8.self) } func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper { diff --git a/Meshtastic/Helpers/MeshLogger.swift b/Meshtastic/Helpers/MeshLogger.swift index 25dbc8f1..a706b45e 100644 --- a/Meshtastic/Helpers/MeshLogger.swift +++ b/Meshtastic/Helpers/MeshLogger.swift @@ -32,7 +32,7 @@ class MeshLogger { fileHandle.closeFile() } else { try data.write(to: logFile, options: .atomicWrite) - let log = String(data: data, encoding: .utf8) ?? "unknown".localized + let log = String(decoding: data, as: UTF8.self) Logger.mesh.notice("\(log)") } } catch { diff --git a/Meshtastic/Views/Messages/Messages.swift b/Meshtastic/Views/Messages/Messages.swift index 13ecbfe7..00042799 100644 --- a/Meshtastic/Views/Messages/Messages.swift +++ b/Meshtastic/Views/Messages/Messages.swift @@ -73,7 +73,6 @@ struct Messages: View { if let urlComponent = URLComponents(string: newPath ?? "") { let queryItems = urlComponent.queryItems - let messageId = queryItems?.first(where: { $0.name == "messageId" })?.value let channel = queryItems?.first(where: { $0.name == "channel" })?.value if let channel { diff --git a/Meshtastic/Views/Nodes/Helpers/Map/MapContent/MeshMapContent.swift b/Meshtastic/Views/Nodes/Helpers/Map/MapContent/MeshMapContent.swift index 2c943be9..527a896e 100644 --- a/Meshtastic/Views/Nodes/Helpers/Map/MapContent/MeshMapContent.swift +++ b/Meshtastic/Views/Nodes/Helpers/Map/MapContent/MeshMapContent.swift @@ -76,7 +76,7 @@ struct MeshMapContent: MapContent { } } } - .onTapGesture { location in + .onTapGesture { _ in selectedPosition = (selectedPosition == position ? nil : position) } } diff --git a/Meshtastic/Views/Settings/Routes.swift b/Meshtastic/Views/Settings/Routes.swift index cc70de3b..35e4a894 100644 --- a/Meshtastic/Views/Settings/Routes.swift +++ b/Meshtastic/Views/Settings/Routes.swift @@ -58,21 +58,22 @@ struct Routes: View { } do { - guard let fileContent = String(data: try Data(contentsOf: selectedFile), encoding: .utf8) else { return } + let data = try Data(contentsOf: selectedFile) + let fileContent = String(decoding: data, as: UTF8.self) let routeName = selectedFile.lastPathComponent.dropLast(4) let lines = fileContent.components(separatedBy: "\n") - let headers = lines.first?.components(separatedBy: ",") - var latIndex = -1 - var longIndex = -1 - for index in headers!.indices { - Logger.services.debug("\(index): \( headers![index])") - if headers![index].trimmingCharacters(in: .whitespaces) == "Latitude" { + guard let headers = lines.first?.components(separatedBy: ",") else { return } + var latIndex: Int? + var longIndex: Int? + for index in headers.indices { + Logger.services.debug("\(index): \(headers[index])") + if headers[index].trimmingCharacters(in: .whitespaces) == "Latitude" { latIndex = index - } else if headers![index].trimmingCharacters(in: .whitespaces) == "Longitude" { + } else if headers[index].trimmingCharacters(in: .whitespaces) == "Longitude" { longIndex = index } } - if latIndex >= 0 && longIndex >= 0 { + if let latIndex, let longIndex { let newRoute = RouteEntity(context: context) newRoute.name = String(routeName) newRoute.id = Int32.random(in: Int32(Int8.max) ... Int32.max) @@ -83,8 +84,8 @@ struct Routes: View { lines.dropFirst().forEach { line in let data = line.components(separatedBy: ",") if data.count > 1 { - let latitude = latIndex >= 0 ? data[latIndex].trimmingCharacters(in: .whitespaces) : "0" - let longitude = longIndex >= 0 ? data[longIndex].trimmingCharacters(in: .whitespaces) : "0" + let latitude = data[latIndex].trimmingCharacters(in: .whitespaces) + let longitude = data[longIndex].trimmingCharacters(in: .whitespaces) let loc = LocationEntity(context: context) loc.latitudeI = Int32((Double(latitude) ?? 0) * 1e7) loc.longitudeI = Int32((Double(longitude) ?? 0) * 1e7) From ef6068321ddf4e1c3437fb0438de27cf909cf95c Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 4 Jun 2024 22:41:45 -0700 Subject: [PATCH 09/32] Simple debug log --- Meshtastic.xcodeproj/project.pbxproj | 4 ++ .../xcschemes/WidgetsExtension.xcscheme | 1 + Meshtastic/Extensions/Logger.swift | 41 ++++++++++++++ Meshtastic/Views/Settings/AppLog.swift | 54 +++++++++++++++++++ Meshtastic/Views/Settings/Settings.swift | 4 +- 5 files changed, 102 insertions(+), 2 deletions(-) create mode 100644 Meshtastic/Views/Settings/AppLog.swift diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 47cce0cd..47259f78 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -207,6 +207,7 @@ DDF45C342BC1A48E005ED5F2 /* MQTTIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF45C332BC1A48E005ED5F2 /* MQTTIcon.swift */; }; DDF45C372BC46A5A005ED5F2 /* TimeZone.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF45C362BC46A5A005ED5F2 /* TimeZone.swift */; }; DDF6B2482A9AEBF500BA6931 /* StoreForwardConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF6B2472A9AEBF500BA6931 /* StoreForwardConfig.swift */; }; + DDF8E1F42C10125B0019C87E /* AppLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF8E1F32C10125B0019C87E /* AppLog.swift */; }; DDF924CA26FBB953009FE055 /* ConnectedDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF924C926FBB953009FE055 /* ConnectedDevice.swift */; }; DDFEB3BB29900C1200EE7472 /* CurrentConditionsCompact.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDFEB3BA29900C1200EE7472 /* CurrentConditionsCompact.swift */; }; DDFFA7472B3A7F3C004730DB /* Bundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDFFA7462B3A7F3C004730DB /* Bundle.swift */; }; @@ -481,6 +482,7 @@ DDF6B2462A9AEB9E00BA6931 /* MeshtasticDataModelV17.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV17.xcdatamodel; sourceTree = ""; }; DDF6B2472A9AEBF500BA6931 /* StoreForwardConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreForwardConfig.swift; sourceTree = ""; }; DDF6B24B2A9C2FC800BA6931 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Localizable.strings; sourceTree = ""; }; + DDF8E1F32C10125B0019C87E /* AppLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLog.swift; sourceTree = ""; }; DDF924C926FBB953009FE055 /* ConnectedDevice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectedDevice.swift; sourceTree = ""; }; DDFEB3BA29900C1200EE7472 /* CurrentConditionsCompact.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentConditionsCompact.swift; sourceTree = ""; }; DDFFA7462B3A7F3C004730DB /* Bundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bundle.swift; sourceTree = ""; }; @@ -590,6 +592,7 @@ DD93800C2BA74CE3008BEC06 /* Channels */, DD97E96728EFE9A00056DDA4 /* About.swift */, DD0F791A28713C8A00A6FDAD /* AdminMessageList.swift */, + DDF8E1F32C10125B0019C87E /* AppLog.swift */, DD4A911D2708C65400501B7E /* AppSettings.swift */, DDAB580C2B0DAA9E00147258 /* Routes.swift */, DDE9659B2B1C3B6A00531070 /* RouteRecorder.swift */, @@ -1237,6 +1240,7 @@ DD8ED9C8289CE4B900B3B0AB /* RoutingError.swift in Sources */, DD5E5202298EE33B00D21B61 /* admin.pb.swift in Sources */, DDC1B81A2AB5377B00C71E39 /* MessagesTips.swift in Sources */, + DDF8E1F42C10125B0019C87E /* AppLog.swift in Sources */, DD964FC62975DBFD007C176F /* QueryCoreData.swift in Sources */, DDB75A112A059258006ED576 /* Url.swift in Sources */, DDAD49ED2AFB39DC00B4425D /* MeshMap.swift in Sources */, diff --git a/Meshtastic.xcodeproj/xcshareddata/xcschemes/WidgetsExtension.xcscheme b/Meshtastic.xcodeproj/xcshareddata/xcschemes/WidgetsExtension.xcscheme index 880339bc..decd8381 100644 --- a/Meshtastic.xcodeproj/xcshareddata/xcschemes/WidgetsExtension.xcscheme +++ b/Meshtastic.xcodeproj/xcshareddata/xcschemes/WidgetsExtension.xcscheme @@ -89,6 +89,7 @@ savedToolIdentifier = "" useCustomWorkingDirectory = "NO" debugDocumentVersioning = "YES" + askForAppToLaunch = "Yes" launchAutomaticallySubstyle = "2"> diff --git a/Meshtastic/Extensions/Logger.swift b/Meshtastic/Extensions/Logger.swift index e5c29a95..74dcdb4a 100644 --- a/Meshtastic/Extensions/Logger.swift +++ b/Meshtastic/Extensions/Logger.swift @@ -23,4 +23,45 @@ extension Logger { /// All logs related to tracking and analytics. static let statistics = Logger(subsystem: subsystem, category: "📈 Stats") + + /// Fetch from the logstore + static public func fetch(since date: Date, predicateFormat: String) async throws -> [String] { + + let store = try OSLogStore(scope: .currentProcessIdentifier) + let position = store.position(date: date) + let predicate = NSPredicate(format: predicateFormat) + let entries = try store.getEntries(at: position, matching: predicate) + + var logs: [String] = [] + for entry in entries { + + try Task.checkCancellation() + + if let log = entry as? OSLogEntryLog { + logs.append(""" + \(entry.date.formatted(.dateTime.hour(.twoDigits(amPM: .omitted)).minute().second().secondFraction(.fractional(3)))) \(log.level.description) \ + \(log.category) \(entry.composedMessage)\n + """) + } else { + logs.append("\(entry.date): \(entry.composedMessage)\n") + } + } + + if logs.isEmpty { logs = ["Nothing found"] } + return logs + } +} + +extension OSLogEntryLog.Level { + fileprivate var description: String { + switch self { + case .undefined: "undefined" + case .debug: "Debug" + case .info: "Info" + case .notice: "Notice" + case .error: "Error" + case .fault: "Fault" + @unknown default: "default" + } + } } diff --git a/Meshtastic/Views/Settings/AppLog.swift b/Meshtastic/Views/Settings/AppLog.swift new file mode 100644 index 00000000..7ec291a7 --- /dev/null +++ b/Meshtastic/Views/Settings/AppLog.swift @@ -0,0 +1,54 @@ +// +// AppLog.swift +// Meshtastic +// +// Created by Garth Vander Houwen on 6/4/24. +// + +import SwiftUI +import OSLog + +struct AppLog: View { + @State private var text = "Loading..." + + var body: some View { + ScrollView { + Text(text) + .textSelection(.enabled) + .fontDesign(.monospaced) + .font(.caption2) + .padding() + } + .task { + text = await fetchLogs() + } + } +} + +extension AppLog { + static private let template = NSPredicate(format: + "(subsystem BEGINSWITH $PREFIX) || ((subsystem IN $SYSTEM) && ((messageType == error) || (messageType == fault)))") + + @MainActor + private func fetchLogs() async -> String { + let calendar = Calendar.current + guard let dayAgo = calendar.date(byAdding: .day, + value: -1, to: Date.now) else { + return "Invalid calendar" + } + + do { + let predicate = AppLog.template.withSubstitutionVariables( + [ + "PREFIX": "gvh.MeshtasticClient", + "SYSTEM": ["com.apple.coredata"] + ]) + + let logs = try await Logger.fetch(since: dayAgo, + predicateFormat: predicate.predicateFormat) + return logs.joined() + } catch { + return error.localizedDescription + } + } +} diff --git a/Meshtastic/Views/Settings/Settings.swift b/Meshtastic/Views/Settings/Settings.swift index 86c83e3d..dda4f5ba 100644 --- a/Meshtastic/Views/Settings/Settings.swift +++ b/Meshtastic/Views/Settings/Settings.swift @@ -426,10 +426,10 @@ struct Settings: View { .tag(SettingsSidebar.adminMessageLog) #if DEBUG NavigationLink { - // AppLogs() + AppLog() } label: { Label { - Text("app.log") + Text("Debug Logs") } icon: { Image(systemName: "building.columns") } From 6fc475da7662777b3ab33df4138f463c3c85cdc1 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Wed, 5 Jun 2024 08:44:46 -0700 Subject: [PATCH 10/32] debug App Logs view --- Meshtastic/Extensions/Logger.swift | 17 ++-- Meshtastic/Helpers/LocationsHandler.swift | 6 +- .../Nodes/Helpers/Map/PositionPopover.swift | 3 +- Meshtastic/Views/Settings/AppLog.swift | 85 +++++++++++-------- Meshtastic/Views/Settings/Settings.swift | 18 ++-- 5 files changed, 73 insertions(+), 56 deletions(-) diff --git a/Meshtastic/Extensions/Logger.swift b/Meshtastic/Extensions/Logger.swift index 74dcdb4a..925da350 100644 --- a/Meshtastic/Extensions/Logger.swift +++ b/Meshtastic/Extensions/Logger.swift @@ -25,29 +25,30 @@ extension Logger { static let statistics = Logger(subsystem: subsystem, category: "📈 Stats") /// Fetch from the logstore - static public func fetch(since date: Date, predicateFormat: String) async throws -> [String] { + static public func fetch(since date: Date, predicateFormat: String) async throws -> [OSLogEntryLog] { let store = try OSLogStore(scope: .currentProcessIdentifier) let position = store.position(date: date) let predicate = NSPredicate(format: predicateFormat) let entries = try store.getEntries(at: position, matching: predicate) - var logs: [String] = [] + var logs: [OSLogEntryLog] = [] for entry in entries { try Task.checkCancellation() if let log = entry as? OSLogEntryLog { - logs.append(""" - \(entry.date.formatted(.dateTime.hour(.twoDigits(amPM: .omitted)).minute().second().secondFraction(.fractional(3)))) \(log.level.description) \ - \(log.category) \(entry.composedMessage)\n - """) + logs.append(log) +// logs.append(""" +// \(entry.date.formatted(.dateTime.hour(.twoDigits(amPM: .omitted)).minute().second().secondFraction(.fractional(3)))) \(log.level.description) \ +// \(log.category) \(entry.composedMessage)\n +// """) } else { - logs.append("\(entry.date): \(entry.composedMessage)\n") + // logs.append("\(entry.date): \(entry.composedMessage)\n") } } - if logs.isEmpty { logs = ["Nothing found"] } + if logs.isEmpty { logs = [] } return logs } } diff --git a/Meshtastic/Helpers/LocationsHandler.swift b/Meshtastic/Helpers/LocationsHandler.swift index 32fc88d9..3f9cda69 100644 --- a/Meshtastic/Helpers/LocationsHandler.swift +++ b/Meshtastic/Helpers/LocationsHandler.swift @@ -85,15 +85,15 @@ import OSLog if smartPostion { let age = -location.timestamp.timeIntervalSinceNow if age > 10 { - Logger.services.warning("📍 Bad Location \(self.count): Too Old \(age) seconds ago \(location)") + Logger.services.warning("📍 Bad Location \(self.count): Too Old \(age) seconds ago \(location, privacy: .private)") return false } if location.horizontalAccuracy < 0 { - Logger.services.warning("📍 Bad Location \(self.count): Horizontal Accuracy: \(location.horizontalAccuracy) \(location)") + Logger.services.warning("📍 Bad Location \(self.count): Horizontal Accuracy: \(location.horizontalAccuracy) \(location, privacy: .private)") return false } if location.horizontalAccuracy > 5 { - Logger.services.warning("📍 Bad Location \(self.count): Horizontal Accuracy: \(location.horizontalAccuracy) \(location)") + Logger.services.warning("📍 Bad Location \(self.count): Horizontal Accuracy: \(location.horizontalAccuracy) \(location, privacy: .private)") return false } } diff --git a/Meshtastic/Views/Nodes/Helpers/Map/PositionPopover.swift b/Meshtastic/Views/Nodes/Helpers/Map/PositionPopover.swift index 8cfcb612..48e71904 100644 --- a/Meshtastic/Views/Nodes/Helpers/Map/PositionPopover.swift +++ b/Meshtastic/Views/Nodes/Helpers/Map/PositionPopover.swift @@ -74,7 +74,8 @@ struct PositionPopover: View { .padding(.bottom, 5) /// Altitude Label { - Text("Altitude: \(distanceFormatter.string(fromDistance: Double(position.altitude)))") + let altitude = Measurement(value: Double(position.altitude), unit: UnitLength.meters) + Text("Altitude: \(altitude.formatted())") .foregroundColor(.primary) } icon: { Image(systemName: "mountain.2.fill") diff --git a/Meshtastic/Views/Settings/AppLog.swift b/Meshtastic/Views/Settings/AppLog.swift index 7ec291a7..e697b86a 100644 --- a/Meshtastic/Views/Settings/AppLog.swift +++ b/Meshtastic/Views/Settings/AppLog.swift @@ -8,47 +8,60 @@ import SwiftUI import OSLog +@available(iOS 17.4, *) struct AppLog: View { - @State private var text = "Loading..." - + + @State private var logs: [OSLogEntryLog] = [] + @State private var sortOrder = [KeyPathComparator(\OSLogEntryLog.date)] + @State private var selection = Set() + private var idiom : UIUserInterfaceIdiom { UIDevice.current.userInterfaceIdiom } + var body: some View { - ScrollView { - Text(text) - .textSelection(.enabled) - .fontDesign(.monospaced) - .font(.caption2) - .padding() + + Table(logs, selection: $selection, sortOrder: $sortOrder) { + if idiom != .phone { + TableColumn("Date", value: \.date) { value in + Text("\(value.date, format: .dateTime)") + } + .width(min: 150, max: 200) + TableColumn("Category", value: \.category) + .width(min: 100, max: 125) + } + TableColumn("Message", value: \.composedMessage) + .width(ideal: 200, max: .infinity) + } + .onChange(of: sortOrder) { _, sortOrder in + logs.sort(using: sortOrder) } .task { - text = await fetchLogs() + logs = await fetchLogs() + } + .presentationCompactAdaptation(.fullScreenCover) + } +} + +@available(iOS 17.4, *) +extension AppLog { + static private let template = NSPredicate(format: "(subsystem BEGINSWITH $PREFIX) || ((subsystem IN $SYSTEM) && ((messageType == error) || (messageType == fault)))") + + @MainActor + private func fetchLogs() async -> [OSLogEntryLog] { + let calendar = Calendar.current + guard let dayAgo = calendar.date(byAdding: .day, value: -1, to: Date.now) else { + return [] + } + do { + let predicate = AppLog.template.withSubstitutionVariables( + [ + "PREFIX": "gvh.MeshtasticClient", + "SYSTEM": ["com.apple.coredata"] + ]) + let logs = try await Logger.fetch(since: dayAgo, predicateFormat: predicate.predicateFormat) + return logs + } catch { + return [] } } } -extension AppLog { - static private let template = NSPredicate(format: - "(subsystem BEGINSWITH $PREFIX) || ((subsystem IN $SYSTEM) && ((messageType == error) || (messageType == fault)))") - - @MainActor - private func fetchLogs() async -> String { - let calendar = Calendar.current - guard let dayAgo = calendar.date(byAdding: .day, - value: -1, to: Date.now) else { - return "Invalid calendar" - } - - do { - let predicate = AppLog.template.withSubstitutionVariables( - [ - "PREFIX": "gvh.MeshtasticClient", - "SYSTEM": ["com.apple.coredata"] - ]) - - let logs = try await Logger.fetch(since: dayAgo, - predicateFormat: predicate.predicateFormat) - return logs.joined() - } catch { - return error.localizedDescription - } - } -} +extension OSLogEntry: Identifiable { } diff --git a/Meshtastic/Views/Settings/Settings.swift b/Meshtastic/Views/Settings/Settings.swift index dda4f5ba..115c66c2 100644 --- a/Meshtastic/Views/Settings/Settings.swift +++ b/Meshtastic/Views/Settings/Settings.swift @@ -425,16 +425,18 @@ struct Settings: View { } .tag(SettingsSidebar.adminMessageLog) #if DEBUG - NavigationLink { - AppLog() - } label: { - Label { - Text("Debug Logs") - } icon: { - Image(systemName: "building.columns") + if #available (iOS 17.4, *) { + NavigationLink { + AppLog() + } label: { + Label { + Text("Debug Logs") + } icon: { + Image(systemName: "building.columns") + } } + .tag(SettingsSidebar.appLog) } - .tag(SettingsSidebar.appLog) #endif } Section(header: Text("Firmware")) { From 290a19a02227031d25d61e459678c1e61846288f Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Wed, 5 Jun 2024 09:25:38 -0700 Subject: [PATCH 11/32] Try and fix xcode cloud --- Meshtastic.xcodeproj/project.pbxproj | 21 +++++++++++++++++++++ Meshtastic/Views/Settings/AppLog.swift | 3 ++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 877fc7e2..c9cdea9f 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -1005,6 +1005,7 @@ DDC2E15126CE248E0042C5E4 /* Frameworks */, DDC2E15226CE248E0042C5E4 /* Resources */, DDDE5A0829AF163F00490C6C /* Embed Foundation Extensions */, + DDF8E1F52C10C84D0019C87E /* ShellScript */, ); buildRules = ( ); @@ -1117,6 +1118,26 @@ }; /* End PBXResourcesBuildPhase section */ +/* Begin PBXShellScriptBuildPhase section */ + DDF8E1F52C10C84D0019C87E /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "defaults write com.apple.dt.Xcode IDESkipPackagePluginFingerprintValidatation -bool YES\n"; + }; +/* End PBXShellScriptBuildPhase section */ + /* Begin PBXSourcesBuildPhase section */ DDC2E15026CE248E0042C5E4 /* Sources */ = { isa = PBXSourcesBuildPhase; diff --git a/Meshtastic/Views/Settings/AppLog.swift b/Meshtastic/Views/Settings/AppLog.swift index e697b86a..bb8fd77e 100644 --- a/Meshtastic/Views/Settings/AppLog.swift +++ b/Meshtastic/Views/Settings/AppLog.swift @@ -14,7 +14,7 @@ struct AppLog: View { @State private var logs: [OSLogEntryLog] = [] @State private var sortOrder = [KeyPathComparator(\OSLogEntryLog.date)] @State private var selection = Set() - private var idiom : UIUserInterfaceIdiom { UIDevice.current.userInterfaceIdiom } + private var idiom: UIUserInterfaceIdiom { UIDevice.current.userInterfaceIdiom } var body: some View { @@ -37,6 +37,7 @@ struct AppLog: View { logs = await fetchLogs() } .presentationCompactAdaptation(.fullScreenCover) + .navigationTitle("Debug Logs") } } From 345d6c4d39cb152d0700c2cf499836d37a5ef520 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Wed, 5 Jun 2024 10:23:15 -0700 Subject: [PATCH 12/32] Improve log date --- Meshtastic/Views/Settings/AppLog.swift | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Meshtastic/Views/Settings/AppLog.swift b/Meshtastic/Views/Settings/AppLog.swift index bb8fd77e..af6923e4 100644 --- a/Meshtastic/Views/Settings/AppLog.swift +++ b/Meshtastic/Views/Settings/AppLog.swift @@ -15,13 +15,21 @@ struct AppLog: View { @State private var sortOrder = [KeyPathComparator(\OSLogEntryLog.date)] @State private var selection = Set() private var idiom: UIUserInterfaceIdiom { UIDevice.current.userInterfaceIdiom } + let dateFormatStyle = Date.FormatStyle() + .year(.twoDigits) + .month(.defaultDigits) + .day(.defaultDigits) + .hour(.twoDigits(amPM: .omitted)) + .minute() + .second() + .secondFraction(.fractional(3)) var body: some View { Table(logs, selection: $selection, sortOrder: $sortOrder) { if idiom != .phone { TableColumn("Date", value: \.date) { value in - Text("\(value.date, format: .dateTime)") + Text(value.date.formatted(dateFormatStyle)) } .width(min: 150, max: 200) TableColumn("Category", value: \.category) @@ -36,7 +44,6 @@ struct AppLog: View { .task { logs = await fetchLogs() } - .presentationCompactAdaptation(.fullScreenCover) .navigationTitle("Debug Logs") } } From 5b064e5260bc412752f483a64b0b582a21d36d74 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Wed, 5 Jun 2024 10:24:15 -0700 Subject: [PATCH 13/32] run xcode script --- Meshtastic.xcodeproj/project.pbxproj | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index c9cdea9f..6f09c99e 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -1121,7 +1121,8 @@ /* Begin PBXShellScriptBuildPhase section */ DDF8E1F52C10C84D0019C87E /* ShellScript */ = { isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; + alwaysOutOfDate = 1; + buildActionMask = 8; files = ( ); inputFileListPaths = ( @@ -1132,7 +1133,7 @@ ); outputPaths = ( ); - runOnlyForDeploymentPostprocessing = 0; + runOnlyForDeploymentPostprocessing = 1; shellPath = /bin/sh; shellScript = "defaults write com.apple.dt.Xcode IDESkipPackagePluginFingerprintValidatation -bool YES\n"; }; From 69de3807119dc3f90f54fa374957968b17bcd049 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Wed, 5 Jun 2024 10:28:37 -0700 Subject: [PATCH 14/32] remove swiftlint so xcode cloud builds --- Meshtastic.xcodeproj/project.pbxproj | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 6f09c99e..02f8944b 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -1010,7 +1010,6 @@ buildRules = ( ); dependencies = ( - 258EE1232C0E81DC0025A5FB /* PBXTargetDependency */, DDDE5A0229AF163E00490C6C /* PBXTargetDependency */, ); name = Meshtastic; @@ -1347,10 +1346,6 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ - 258EE1232C0E81DC0025A5FB /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - productRef = 258EE1222C0E81DC0025A5FB /* SwiftLintBuildToolPlugin */; - }; 258EE1252C0E826E0025A5FB /* PBXTargetDependency */ = { isa = PBXTargetDependency; productRef = 258EE1242C0E826E0025A5FB /* SwiftLintBuildToolPlugin */; @@ -1710,11 +1705,6 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - 258EE1222C0E81DC0025A5FB /* SwiftLintBuildToolPlugin */ = { - isa = XCSwiftPackageProductDependency; - package = 258EE1212C0E81AE0025A5FB /* XCRemoteSwiftPackageReference "SwiftLint" */; - productName = "plugin:SwiftLintBuildToolPlugin"; - }; 258EE1242C0E826E0025A5FB /* SwiftLintBuildToolPlugin */ = { isa = XCSwiftPackageProductDependency; package = 258EE1212C0E81AE0025A5FB /* XCRemoteSwiftPackageReference "SwiftLint" */; From 2b9a4bcdbcd9f201a601646d58c6cc84c856ed5b Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Wed, 5 Jun 2024 10:35:03 -0700 Subject: [PATCH 15/32] Dont run swiftlint build plugin --- Meshtastic.xcodeproj/project.pbxproj | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 02f8944b..13ea755d 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -1010,6 +1010,7 @@ buildRules = ( ); dependencies = ( + DDF8E1F72C10D7E00019C87E /* PBXTargetDependency */, DDDE5A0229AF163E00490C6C /* PBXTargetDependency */, ); name = Meshtastic; @@ -1356,6 +1357,10 @@ target = DDDE59F329AF163D00490C6C /* WidgetsExtension */; targetProxy = DDDE5A0129AF163E00490C6C /* PBXContainerItemProxy */; }; + DDF8E1F72C10D7E00019C87E /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + productRef = DDF8E1F62C10D7E00019C87E /* SwiftLintBuildToolPlugin */; + }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ @@ -1725,6 +1730,11 @@ package = DD5394FA276993AD00AD86B1 /* XCRemoteSwiftPackageReference "swift-protobuf" */; productName = SwiftProtobuf; }; + DDF8E1F62C10D7E00019C87E /* SwiftLintBuildToolPlugin */ = { + isa = XCSwiftPackageProductDependency; + package = 258EE1212C0E81AE0025A5FB /* XCRemoteSwiftPackageReference "SwiftLint" */; + productName = "plugin:SwiftLintBuildToolPlugin"; + }; /* End XCSwiftPackageProductDependency section */ /* Begin XCVersionGroup section */ From 544bbd3621c24189f02f54262e9c8dd89cde9093 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Wed, 5 Jun 2024 20:46:24 -0700 Subject: [PATCH 16/32] Debug log updates --- Meshtastic.xcodeproj/project.pbxproj | 4 + Meshtastic/Extensions/Logger.swift | 22 ++- Meshtastic/Helpers/BLEManager.swift | 8 +- Meshtastic/MeshtasticAppDelegate.swift | 2 +- Meshtastic/Views/Helpers/LogDetail.swift | 129 ++++++++++++++++++ .../Nodes/Helpers/Map/PositionPopover.swift | 1 + Meshtastic/Views/Settings/AppLog.swift | 35 +++-- Meshtastic/Views/Settings/Settings.swift | 2 +- 8 files changed, 174 insertions(+), 29 deletions(-) create mode 100644 Meshtastic/Views/Helpers/LogDetail.swift diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 13ea755d..59a1dd10 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -208,6 +208,7 @@ DDF45C372BC46A5A005ED5F2 /* TimeZone.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF45C362BC46A5A005ED5F2 /* TimeZone.swift */; }; DDF6B2482A9AEBF500BA6931 /* StoreForwardConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF6B2472A9AEBF500BA6931 /* StoreForwardConfig.swift */; }; DDF8E1F42C10125B0019C87E /* AppLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF8E1F32C10125B0019C87E /* AppLog.swift */; }; + DDF8E1F92C115CCE0019C87E /* LogDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF8E1F82C115CCE0019C87E /* LogDetail.swift */; }; DDF924CA26FBB953009FE055 /* ConnectedDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF924C926FBB953009FE055 /* ConnectedDevice.swift */; }; DDFEB3BB29900C1200EE7472 /* CurrentConditionsCompact.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDFEB3BA29900C1200EE7472 /* CurrentConditionsCompact.swift */; }; DDFFA7472B3A7F3C004730DB /* Bundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDFFA7462B3A7F3C004730DB /* Bundle.swift */; }; @@ -483,6 +484,7 @@ DDF6B2472A9AEBF500BA6931 /* StoreForwardConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreForwardConfig.swift; sourceTree = ""; }; DDF6B24B2A9C2FC800BA6931 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Localizable.strings; sourceTree = ""; }; DDF8E1F32C10125B0019C87E /* AppLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLog.swift; sourceTree = ""; }; + DDF8E1F82C115CCE0019C87E /* LogDetail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogDetail.swift; sourceTree = ""; }; DDF924C926FBB953009FE055 /* ConnectedDevice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectedDevice.swift; sourceTree = ""; }; DDFEB3BA29900C1200EE7472 /* CurrentConditionsCompact.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentConditionsCompact.swift; sourceTree = ""; }; DDFFA7462B3A7F3C004730DB /* Bundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bundle.swift; sourceTree = ""; }; @@ -894,6 +896,7 @@ DDB75A1D2A0B0CD0006ED576 /* LoRaSignalStrengthIndicator.swift */, DDB75A202A12B954006ED576 /* LoRaSignalStrength.swift */, DDB75A222A13CDA9006ED576 /* BatteryLevelCompact.swift */, + DDF8E1F82C115CCE0019C87E /* LogDetail.swift */, ); path = Helpers; sourceTree = ""; @@ -1197,6 +1200,7 @@ DD5E5208298EE33B00D21B61 /* rtttl.pb.swift in Sources */, DD6193792863875F00E59241 /* SerialConfig.swift in Sources */, DDDB263F2AABEE20003AFCB7 /* NodeList.swift in Sources */, + DDF8E1F92C115CCE0019C87E /* LogDetail.swift in Sources */, DDA0B6B2294CDC55001356EC /* Channels.swift in Sources */, DDE9659C2B1C3B6A00531070 /* RouteRecorder.swift in Sources */, B399E8A42B6F486400E4488E /* RetryButton.swift in Sources */, diff --git a/Meshtastic/Extensions/Logger.swift b/Meshtastic/Extensions/Logger.swift index 925da350..b15b0beb 100644 --- a/Meshtastic/Extensions/Logger.swift +++ b/Meshtastic/Extensions/Logger.swift @@ -37,14 +37,8 @@ extension Logger { try Task.checkCancellation() - if let log = entry as? OSLogEntryLog { - logs.append(log) -// logs.append(""" -// \(entry.date.formatted(.dateTime.hour(.twoDigits(amPM: .omitted)).minute().second().secondFraction(.fractional(3)))) \(log.level.description) \ -// \(log.category) \(entry.composedMessage)\n -// """) - } else { - // logs.append("\(entry.date): \(entry.composedMessage)\n") + if let log = entry as? OSLogEntryLog { + logs.append(log) } } @@ -54,14 +48,14 @@ extension Logger { } extension OSLogEntryLog.Level { - fileprivate var description: String { + var description: String { switch self { case .undefined: "undefined" - case .debug: "Debug" - case .info: "Info" - case .notice: "Notice" - case .error: "Error" - case .fault: "Fault" + case .debug: "🐛 Debug" + case .info: "ℹ️ Info" + case .notice: "⚠️ Notice" + case .error: "🚨 Error" + case .fault: "💥 Fault" @unknown default: "default" } } diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 5885e375..42acd0a0 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -130,7 +130,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate let context = ["name": "\(peripheral.name ?? "Unknown")"] timeoutTimer = Timer.scheduledTimer(timeInterval: 1.5, target: self, selector: #selector(timeoutTimerFired), userInfo: context, repeats: true) RunLoop.current.add(timeoutTimer!, forMode: .common) - Logger.services.info("ℹ️ BLE Connecting: \(peripheral.name ?? "Unknown")") + Logger.services.info("ℹ️ BLE Connecting: \(peripheral.name ?? "Unknown", privacy: .public)") } // Disconnect Connected Peripheral @@ -259,12 +259,12 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate manager.schedule() } lastConnectionError = "🚨 \(e.localizedDescription)" - Logger.services.error("🚨 BLE Disconnected: \(peripheral.name ?? "Unknown") Error Code: \(errorCode) Error: \(e.localizedDescription)") + Logger.services.error("🚫 BLE Disconnected: \(peripheral.name ?? "Unknown") Error Code: \(errorCode) Error: \(e.localizedDescription)") } } else { // Disconnected without error which indicates user intent to disconnect // Happens when swiping to disconnect - Logger.services.info("ℹ️ BLE Disconnected: \(peripheral.name ?? "Unknown"): User Initiated Disconnect") + Logger.services.info("🚫 BLE Disconnected: \(peripheral.name ?? "Unknown"): User Initiated Disconnect") } // Start a scan so the disconnected peripheral is moved to the peripherals[] if it is awake self.startScanning() @@ -3021,7 +3021,7 @@ extension BLEManager: CBCentralManagerDelegate { if self.automaticallyReconnect && peripheral.identifier.uuidString == UserDefaults.standard.object(forKey: "preferredPeripheralId") as? String ?? "" { self.connectTo(peripheral: peripheral) - Logger.services.info("BLE Reconnecting to prefered peripheral: \(peripheral.name ?? "Unknown")") + Logger.services.info("🔄 BLE Reconnecting to prefered peripheral: \(peripheral.name ?? "Unknown")") } let name = advertisementData[CBAdvertisementDataLocalNameKey] as? String let device = Peripheral(id: peripheral.identifier.uuidString, num: 0, name: name ?? "Unknown", shortName: "?", longName: name ?? "Unknown", firmwareVersion: "Unknown", rssi: RSSI.intValue, lastUpdate: Date(), peripheral: peripheral) diff --git a/Meshtastic/MeshtasticAppDelegate.swift b/Meshtastic/MeshtasticAppDelegate.swift index 1bfa73ed..cc5dc397 100644 --- a/Meshtastic/MeshtasticAppDelegate.swift +++ b/Meshtastic/MeshtasticAppDelegate.swift @@ -10,7 +10,7 @@ import OSLog class MeshtasticAppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate, ObservableObject { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool { - Logger.services.info("🚀 Meshtstic Apple App launched!") + Logger.services.info("🚀 Meshtastic Apple App launched!") // Default User Default Values UserDefaults.standard.register(defaults: ["meshMapRecentering": true]) UserDefaults.standard.register(defaults: ["meshMapShowNodeHistory": true]) diff --git a/Meshtastic/Views/Helpers/LogDetail.swift b/Meshtastic/Views/Helpers/LogDetail.swift new file mode 100644 index 00000000..085211d1 --- /dev/null +++ b/Meshtastic/Views/Helpers/LogDetail.swift @@ -0,0 +1,129 @@ +// +// LogDetail.swift +// Meshtastic +// +// Copyright(c) Garth Vander Houwen 6/5/24. +// + +import SwiftUI +import MapKit +import OSLog + +@available(iOS 17.0, macOS 14.0, *) +struct LogDetail: View { + + @Environment(\.dismiss) private var dismiss + var log: OSLogEntryLog + + var body: some View { + + VStack { + HStack { + Text("OS Log Entry Details") + .font(.largeTitle) + } + Divider() + HStack(alignment: .top) { + VStack(alignment: .leading) { + List { + /// Time + Label { + Text("time".localized + ":") + .font(.title) + LastHeardText(lastHeard: log.date) + .font(.title) + } icon: { + Image(systemName: "timer") + .symbolRenderingMode(.hierarchical) + .font(.title) + .frame(width: 35) + } + .padding(.bottom, 5) + /// Subsystem + Label { + Text("subsystem".localized + ":") + .font(.title) + Text(log.subsystem) + .font(.title) + } icon: { + Image(systemName: "gear") + .symbolRenderingMode(.hierarchical) + .font(.title) + .frame(width: 35) + } + .padding(.bottom, 5) + /// Process + Label { + Text("process".localized + ":") + .font(.title) + Text(log.process) + .font(.title) + } icon: { + Image(systemName: "tag") + .symbolRenderingMode(.hierarchical) + .font(.title) + .frame(width: 35) + } + .padding(.bottom, 5) + /// Category + Label { + Text("category".localized + ":") + .font(.title) + Text(log.category) + .font(.title) + } icon: { + Image(systemName: "rectangle.3.group") + .symbolRenderingMode(.hierarchical) + .font(.title) + .frame(width: 35) + } + .padding(.bottom, 5) + /// Level + Label { + Text("level".localized + ":") + .font(.title) + Text(log.level.description) + .font(.title) + } icon: { + Image(systemName: "shield") + .symbolRenderingMode(.hierarchical) + .font(.title) + .frame(width: 35) + } + .padding(.bottom, 5) + /// message + Label { + Text("message".localized + ":") + .font(.title) + Text(log.composedMessage) + .font(.title) + } icon: { + Image(systemName: "text.bubble") + .symbolRenderingMode(.hierarchical) + .font(.title) + .frame(width: 35) + } + .padding(.bottom, 5) + } + .listStyle(.plain) + } + Spacer() + } + .padding(.top) +#if targetEnvironment(macCatalyst) + Spacer() + Button { + dismiss() + } label: { + Label("close", systemImage: "xmark") + } + .buttonStyle(.bordered) + .buttonBorderShape(.capsule) + .controlSize(.large) + .padding(.bottom) +#endif + } + .presentationDetents([.fraction(0.65), .fraction(0.75), .fraction(0.85)]) + .presentationDragIndicator(.visible) + } +} diff --git a/Meshtastic/Views/Nodes/Helpers/Map/PositionPopover.swift b/Meshtastic/Views/Nodes/Helpers/Map/PositionPopover.swift index 48e71904..737020a7 100644 --- a/Meshtastic/Views/Nodes/Helpers/Map/PositionPopover.swift +++ b/Meshtastic/Views/Nodes/Helpers/Map/PositionPopover.swift @@ -18,6 +18,7 @@ struct PositionPopover: View { var popover: Bool = true let distanceFormatter = MKDistanceFormatter() var delay: Double = 0 + @State private var scale: CGFloat = 0.5 var body: some View { // Node Color from node.num diff --git a/Meshtastic/Views/Settings/AppLog.swift b/Meshtastic/Views/Settings/AppLog.swift index af6923e4..de925061 100644 --- a/Meshtastic/Views/Settings/AppLog.swift +++ b/Meshtastic/Views/Settings/AppLog.swift @@ -13,12 +13,11 @@ struct AppLog: View { @State private var logs: [OSLogEntryLog] = [] @State private var sortOrder = [KeyPathComparator(\OSLogEntryLog.date)] - @State private var selection = Set() + @State private var selection: OSLogEntry.ID? + @State private var selectedLog: OSLogEntryLog? + @State private var presentingErrorDetails: Bool = false private var idiom: UIUserInterfaceIdiom { UIDevice.current.userInterfaceIdiom } - let dateFormatStyle = Date.FormatStyle() - .year(.twoDigits) - .month(.defaultDigits) - .day(.defaultDigits) + private let dateFormatStyle = Date.FormatStyle() .hour(.twoDigits(amPM: .omitted)) .minute() .second() @@ -28,23 +27,41 @@ struct AppLog: View { Table(logs, selection: $selection, sortOrder: $sortOrder) { if idiom != .phone { - TableColumn("Date", value: \.date) { value in + TableColumn("Time", value: \.date) { value in Text(value.date.formatted(dateFormatStyle)) } - .width(min: 150, max: 200) + .width(min: 100, max: 125) TableColumn("Category", value: \.category) .width(min: 100, max: 125) + TableColumn("Level") { value in + Text(value.level.description) + } + .width(min: 50, max: 100) } TableColumn("Message", value: \.composedMessage) .width(ideal: 200, max: .infinity) + } .onChange(of: sortOrder) { _, sortOrder in - logs.sort(using: sortOrder) + withAnimation { + logs.sort(using: sortOrder) + } + } + .onChange(of: selection) { newSelection in + presentingErrorDetails = true + let log = logs.first { + $0.id == newSelection + } + selectedLog = log + } + .sheet(item: $selectedLog) { log in + LogDetail(log: log) + .padding() } .task { logs = await fetchLogs() } - .navigationTitle("Debug Logs") + .navigationBarTitle("Debug Logs", displayMode: .inline) } } diff --git a/Meshtastic/Views/Settings/Settings.swift b/Meshtastic/Views/Settings/Settings.swift index 115c66c2..77ca47ad 100644 --- a/Meshtastic/Views/Settings/Settings.swift +++ b/Meshtastic/Views/Settings/Settings.swift @@ -432,7 +432,7 @@ struct Settings: View { Label { Text("Debug Logs") } icon: { - Image(systemName: "building.columns") + Image(systemName: "ladybug") } } .tag(SettingsSidebar.appLog) From d972ba5ef52b58cf392447aa27988d46a9d7102f Mon Sep 17 00:00:00 2001 From: Blake McAnally Date: Thu, 6 Jun 2024 00:35:55 -0500 Subject: [PATCH 17/32] Ensure that viaMqtt is set when creating a new node info entity --- Meshtastic/Helpers/MeshPackets.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index 3250334b..18748059 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -272,6 +272,7 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje newNode.channel = Int32(nodeInfo.channel) newNode.favorite = nodeInfo.isFavorite newNode.hopsAway = Int32(nodeInfo.hopsAway) + newNode.viaMqtt = nodeInfo.viaMqtt if nodeInfo.hasDeviceMetrics { let telemetry = TelemetryEntity(context: context) @@ -350,6 +351,7 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje fetchedNode[0].channel = Int32(nodeInfo.channel) fetchedNode[0].favorite = nodeInfo.isFavorite fetchedNode[0].hopsAway = Int32(nodeInfo.hopsAway) + fetchedNode[0].viaMqtt = nodeInfo.viaMqtt if nodeInfo.hasUser { if fetchedNode[0].user == nil { From 22447cb54dffd93c5b2117bcb55e82a0edb01acb Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Wed, 5 Jun 2024 23:56:28 -0700 Subject: [PATCH 18/32] Log export to CSV --- Meshtastic.xcodeproj/project.pbxproj | 4 ++-- Meshtastic/Export/WriteCsvFile.swift | 22 +++++++++++++++++++ Meshtastic/Views/Settings/AppLog.swift | 27 ++++++++++++++++++++++++ Meshtastic/Views/Settings/Settings.swift | 2 -- 4 files changed, 51 insertions(+), 4 deletions(-) diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 59a1dd10..25d6cac0 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -1522,7 +1522,7 @@ CODE_SIGN_ENTITLEMENTS = Meshtastic/Meshtastic.entitlements; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 959; DEVELOPMENT_ASSET_PATHS = "\"Meshtastic/Preview Content\""; DEVELOPMENT_TEAM = GCH7VS5Y9R; ENABLE_PREVIEWS = YES; @@ -1556,7 +1556,7 @@ CODE_SIGN_ENTITLEMENTS = Meshtastic/Meshtastic.entitlements; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 959; DEVELOPMENT_ASSET_PATHS = "\"Meshtastic/Preview Content\""; DEVELOPMENT_TEAM = GCH7VS5Y9R; ENABLE_PREVIEWS = YES; diff --git a/Meshtastic/Export/WriteCsvFile.swift b/Meshtastic/Export/WriteCsvFile.swift index 521dc937..56776554 100644 --- a/Meshtastic/Export/WriteCsvFile.swift +++ b/Meshtastic/Export/WriteCsvFile.swift @@ -6,6 +6,7 @@ // import SwiftUI +import OSLog func telemetryToCsvFile(telemetry: [TelemetryEntity], metricsType: Int) -> String { var csvString: String = "" @@ -64,6 +65,27 @@ func detectionsToCsv(detections: [MessageEntity]) -> String { return csvString } +func logToCsvFile(log: [OSLogEntryLog]) -> 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: "") + // Create PAX Header + csvString = "Process, Category, Level, Message, \("timestamp".localized)" + for l in log { + csvString += "\n" + csvString += String(l.process) + csvString += ", " + csvString += String(l.category) + csvString += ", " + csvString += String(l.level.description) + csvString += ", " + csvString += String(l.composedMessage) + csvString += ", " + csvString += l.date.formattedDate(format: dateFormatString) + } + return csvString +} + func paxToCsvFile(pax: [PaxCounterEntity]) -> String { var csvString: String = "" let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMddjmma", options: 0, locale: Locale.current) diff --git a/Meshtastic/Views/Settings/AppLog.swift b/Meshtastic/Views/Settings/AppLog.swift index de925061..7203b0ba 100644 --- a/Meshtastic/Views/Settings/AppLog.swift +++ b/Meshtastic/Views/Settings/AppLog.swift @@ -16,6 +16,8 @@ struct AppLog: View { @State private var selection: OSLogEntry.ID? @State private var selectedLog: OSLogEntryLog? @State private var presentingErrorDetails: Bool = false + @State var isExporting = false + @State var exportString = "" private var idiom: UIUserInterfaceIdiom { UIDevice.current.userInterfaceIdiom } private let dateFormatStyle = Date.FormatStyle() .hour(.twoDigits(amPM: .omitted)) @@ -61,7 +63,32 @@ struct AppLog: View { .task { logs = await fetchLogs() } + .fileExporter( + isPresented: $isExporting, + document: CsvDocument(emptyCsv: exportString), + contentType: .commaSeparatedText, + defaultFilename: String("Meshtastic Application Logs"), + onCompletion: { result in + switch result { + case .success: + self.isExporting = false + Logger.services.info("Application log download succeeded.") + case .failure(let error): + Logger.services.error("Application log download failed: \(error.localizedDescription)") + } + } + ) .navigationBarTitle("Debug Logs", displayMode: .inline) + .toolbar { + ToolbarItem(placement: .navigationBarTrailing) { + Button(action: { + exportString = logToCsvFile(log: logs) + isExporting = true + }) { + Image(systemName: "square.and.arrow.down") + } + } + } } } diff --git a/Meshtastic/Views/Settings/Settings.swift b/Meshtastic/Views/Settings/Settings.swift index 77ca47ad..55a77b65 100644 --- a/Meshtastic/Views/Settings/Settings.swift +++ b/Meshtastic/Views/Settings/Settings.swift @@ -424,7 +424,6 @@ struct Settings: View { } } .tag(SettingsSidebar.adminMessageLog) -#if DEBUG if #available (iOS 17.4, *) { NavigationLink { AppLog() @@ -437,7 +436,6 @@ struct Settings: View { } .tag(SettingsSidebar.appLog) } -#endif } Section(header: Text("Firmware")) { NavigationLink { From d5884988b19ba89ca6e3c6722c798d0285434185 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Thu, 6 Jun 2024 08:12:09 -0700 Subject: [PATCH 19/32] Adjust font size for phones on log view --- Meshtastic.xcodeproj/project.pbxproj | 10 ------- Meshtastic/Views/Helpers/LogDetail.swift | 37 ++++++++++++------------ 2 files changed, 19 insertions(+), 28 deletions(-) diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 25d6cac0..a56dad4e 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -1013,7 +1013,6 @@ buildRules = ( ); dependencies = ( - DDF8E1F72C10D7E00019C87E /* PBXTargetDependency */, DDDE5A0229AF163E00490C6C /* PBXTargetDependency */, ); name = Meshtastic; @@ -1361,10 +1360,6 @@ target = DDDE59F329AF163D00490C6C /* WidgetsExtension */; targetProxy = DDDE5A0129AF163E00490C6C /* PBXContainerItemProxy */; }; - DDF8E1F72C10D7E00019C87E /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - productRef = DDF8E1F62C10D7E00019C87E /* SwiftLintBuildToolPlugin */; - }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ @@ -1734,11 +1729,6 @@ package = DD5394FA276993AD00AD86B1 /* XCRemoteSwiftPackageReference "swift-protobuf" */; productName = SwiftProtobuf; }; - DDF8E1F62C10D7E00019C87E /* SwiftLintBuildToolPlugin */ = { - isa = XCSwiftPackageProductDependency; - package = 258EE1212C0E81AE0025A5FB /* XCRemoteSwiftPackageReference "SwiftLint" */; - productName = "plugin:SwiftLintBuildToolPlugin"; - }; /* End XCSwiftPackageProductDependency section */ /* Begin XCVersionGroup section */ diff --git a/Meshtastic/Views/Helpers/LogDetail.swift b/Meshtastic/Views/Helpers/LogDetail.swift index 085211d1..570ac52b 100644 --- a/Meshtastic/Views/Helpers/LogDetail.swift +++ b/Meshtastic/Views/Helpers/LogDetail.swift @@ -13,6 +13,7 @@ import OSLog struct LogDetail: View { @Environment(\.dismiss) private var dismiss + private var idiom: UIUserInterfaceIdiom { UIDevice.current.userInterfaceIdiom } var log: OSLogEntryLog var body: some View { @@ -29,78 +30,78 @@ struct LogDetail: View { /// Time Label { Text("time".localized + ":") - .font(.title) + .font(idiom == .phone ? .callout : .title) LastHeardText(lastHeard: log.date) - .font(.title) + .font(idiom == .phone ? .callout : .title) } icon: { Image(systemName: "timer") .symbolRenderingMode(.hierarchical) - .font(.title) + .font(idiom == .phone ? .callout : .title) .frame(width: 35) } .padding(.bottom, 5) /// Subsystem Label { Text("subsystem".localized + ":") - .font(.title) + .font(idiom == .phone ? .callout : .title) Text(log.subsystem) - .font(.title) + .font(idiom == .phone ? .callout : .title) } icon: { Image(systemName: "gear") .symbolRenderingMode(.hierarchical) - .font(.title) + .font(idiom == .phone ? .callout : .title) .frame(width: 35) } .padding(.bottom, 5) /// Process Label { Text("process".localized + ":") - .font(.title) + .font(idiom == .phone ? .callout : .title) Text(log.process) - .font(.title) + .font(idiom == .phone ? .callout : .title) } icon: { Image(systemName: "tag") .symbolRenderingMode(.hierarchical) - .font(.title) + .font(idiom == .phone ? .callout : .title) .frame(width: 35) } .padding(.bottom, 5) /// Category Label { Text("category".localized + ":") - .font(.title) + .font(idiom == .phone ? .callout : .title) Text(log.category) - .font(.title) + .font(idiom == .phone ? .callout : .title) } icon: { Image(systemName: "rectangle.3.group") .symbolRenderingMode(.hierarchical) - .font(.title) + .font(idiom == .phone ? .callout : .title) .frame(width: 35) } .padding(.bottom, 5) /// Level Label { Text("level".localized + ":") - .font(.title) + .font(idiom == .phone ? .callout : .title) Text(log.level.description) - .font(.title) + .font(idiom == .phone ? .callout : .title) } icon: { Image(systemName: "shield") .symbolRenderingMode(.hierarchical) - .font(.title) + .font(idiom == .phone ? .callout : .title) .frame(width: 35) } .padding(.bottom, 5) /// message Label { Text("message".localized + ":") - .font(.title) + .font(idiom == .phone ? .callout : .title) Text(log.composedMessage) - .font(.title) + .font(idiom == .phone ? .callout : .title) } icon: { Image(systemName: "text.bubble") .symbolRenderingMode(.hierarchical) - .font(.title) + .font(idiom == .phone ? .callout : .title) .frame(width: 35) } .padding(.bottom, 5) From d12c755eb9fd08a9389c4b0ba77e139fbee2e62e Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Thu, 6 Jun 2024 08:19:11 -0700 Subject: [PATCH 20/32] Adjust log timing --- Meshtastic/Extensions/Logger.swift | 4 ++-- Meshtastic/Views/Settings/AppLog.swift | 6 +----- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/Meshtastic/Extensions/Logger.swift b/Meshtastic/Extensions/Logger.swift index b15b0beb..62c3ff56 100644 --- a/Meshtastic/Extensions/Logger.swift +++ b/Meshtastic/Extensions/Logger.swift @@ -25,10 +25,10 @@ extension Logger { static let statistics = Logger(subsystem: subsystem, category: "📈 Stats") /// Fetch from the logstore - static public func fetch(since date: Date, predicateFormat: String) async throws -> [OSLogEntryLog] { + static public func fetch(predicateFormat: String) async throws -> [OSLogEntryLog] { let store = try OSLogStore(scope: .currentProcessIdentifier) - let position = store.position(date: date) + let position = store.position(timeIntervalSinceLatestBoot: 0) let predicate = NSPredicate(format: predicateFormat) let entries = try store.getEntries(at: position, matching: predicate) diff --git a/Meshtastic/Views/Settings/AppLog.swift b/Meshtastic/Views/Settings/AppLog.swift index 7203b0ba..df6ab9bd 100644 --- a/Meshtastic/Views/Settings/AppLog.swift +++ b/Meshtastic/Views/Settings/AppLog.swift @@ -98,17 +98,13 @@ extension AppLog { @MainActor private func fetchLogs() async -> [OSLogEntryLog] { - let calendar = Calendar.current - guard let dayAgo = calendar.date(byAdding: .day, value: -1, to: Date.now) else { - return [] - } do { let predicate = AppLog.template.withSubstitutionVariables( [ "PREFIX": "gvh.MeshtasticClient", "SYSTEM": ["com.apple.coredata"] ]) - let logs = try await Logger.fetch(since: dayAgo, predicateFormat: predicate.predicateFormat) + let logs = try await Logger.fetch(predicateFormat: predicate.predicateFormat) return logs } catch { return [] From a3956f0ff8f599ee46f90ef0193715caaa3928ac Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Thu, 6 Jun 2024 08:32:24 -0700 Subject: [PATCH 21/32] Remove swiftlint from widgets --- Meshtastic.xcodeproj/project.pbxproj | 10 ---------- Meshtastic/Views/Settings/AppLog.swift | 9 ++++----- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index a56dad4e..40d646aa 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -1036,7 +1036,6 @@ buildRules = ( ); dependencies = ( - 258EE1252C0E826E0025A5FB /* PBXTargetDependency */, ); name = WidgetsExtension; productName = WidgetsExtension; @@ -1350,10 +1349,6 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ - 258EE1252C0E826E0025A5FB /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - productRef = 258EE1242C0E826E0025A5FB /* SwiftLintBuildToolPlugin */; - }; DDDE5A0229AF163E00490C6C /* PBXTargetDependency */ = { isa = PBXTargetDependency; platformFilter = ios; @@ -1709,11 +1704,6 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - 258EE1242C0E826E0025A5FB /* SwiftLintBuildToolPlugin */ = { - isa = XCSwiftPackageProductDependency; - package = 258EE1212C0E81AE0025A5FB /* XCRemoteSwiftPackageReference "SwiftLint" */; - productName = "plugin:SwiftLintBuildToolPlugin"; - }; C9697FA427933B8C00250207 /* SQLite */ = { isa = XCSwiftPackageProductDependency; package = C9697FA327933B8C00250207 /* XCRemoteSwiftPackageReference "SQLite.swift" */; diff --git a/Meshtastic/Views/Settings/AppLog.swift b/Meshtastic/Views/Settings/AppLog.swift index df6ab9bd..8f8db623 100644 --- a/Meshtastic/Views/Settings/AppLog.swift +++ b/Meshtastic/Views/Settings/AppLog.swift @@ -99,11 +99,10 @@ extension AppLog { @MainActor private func fetchLogs() async -> [OSLogEntryLog] { do { - let predicate = AppLog.template.withSubstitutionVariables( - [ - "PREFIX": "gvh.MeshtasticClient", - "SYSTEM": ["com.apple.coredata"] - ]) + let predicate = NSPredicate(format: "subsystem IN %@", [ + "com.apple.coredata", + "gvh.MeshtasticClient" + ]) let logs = try await Logger.fetch(predicateFormat: predicate.predicateFormat) return logs } catch { From 871edf2eafb25ef1585ef8274a4affdab8a9d3f1 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Thu, 6 Jun 2024 09:09:01 -0700 Subject: [PATCH 22/32] Log details sheet cleanup --- Meshtastic/Views/Helpers/LogDetail.swift | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Meshtastic/Views/Helpers/LogDetail.swift b/Meshtastic/Views/Helpers/LogDetail.swift index 570ac52b..45f81017 100644 --- a/Meshtastic/Views/Helpers/LogDetail.swift +++ b/Meshtastic/Views/Helpers/LogDetail.swift @@ -40,6 +40,7 @@ struct LogDetail: View { .frame(width: 35) } .padding(.bottom, 5) + .listRowSeparator(.visible) /// Subsystem Label { Text("subsystem".localized + ":") @@ -53,6 +54,7 @@ struct LogDetail: View { .frame(width: 35) } .padding(.bottom, 5) + .listRowSeparator(.visible) /// Process Label { Text("process".localized + ":") @@ -66,6 +68,7 @@ struct LogDetail: View { .frame(width: 35) } .padding(.bottom, 5) + .listRowSeparator(.visible) /// Category Label { Text("category".localized + ":") @@ -79,6 +82,7 @@ struct LogDetail: View { .frame(width: 35) } .padding(.bottom, 5) + .listRowSeparator(.visible) /// Level Label { Text("level".localized + ":") @@ -92,19 +96,22 @@ struct LogDetail: View { .frame(width: 35) } .padding(.bottom, 5) + .listRowSeparator(.visible) /// message Label { Text("message".localized + ":") .font(idiom == .phone ? .callout : .title) - Text(log.composedMessage) - .font(idiom == .phone ? .callout : .title) + } icon: { Image(systemName: "text.bubble") .symbolRenderingMode(.hierarchical) .font(idiom == .phone ? .callout : .title) .frame(width: 35) } - .padding(.bottom, 5) + .listRowSeparator(.hidden) + Text(log.composedMessage) + .font(idiom == .phone ? .callout : .title) + .padding(.bottom, 5) } .listStyle(.plain) } From d9b677fa6d145f8168e44520bd4e28e3fca2b8b9 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Thu, 6 Jun 2024 09:58:15 -0700 Subject: [PATCH 23/32] localized log strings --- Meshtastic/Views/Helpers/LogDetail.swift | 14 +++++++------- Meshtastic/Views/Settings/AppLog.swift | 8 ++++---- de.lproj/Localizable.strings | 6 ++++++ en.lproj/Localizable.strings | 6 ++++++ fr.lproj/Localizable.strings | 6 ++++++ he.lproj/Localizable.strings | 6 ++++++ pl.lproj/Localizable.strings | 6 ++++++ pt-PT.lproj/Localizable.strings | 6 ++++++ se.lproj/Localizable.strings | 6 ++++++ zh-Hans.lproj/Localizable.strings | 6 ++++++ zh-Hant-TW.lproj/Localizable.strings | 6 ++++++ 11 files changed, 65 insertions(+), 11 deletions(-) diff --git a/Meshtastic/Views/Helpers/LogDetail.swift b/Meshtastic/Views/Helpers/LogDetail.swift index 45f81017..079d00f2 100644 --- a/Meshtastic/Views/Helpers/LogDetail.swift +++ b/Meshtastic/Views/Helpers/LogDetail.swift @@ -29,7 +29,7 @@ struct LogDetail: View { List { /// Time Label { - Text("time".localized + ":") + Text("log.time".localized + ":") .font(idiom == .phone ? .callout : .title) LastHeardText(lastHeard: log.date) .font(idiom == .phone ? .callout : .title) @@ -43,7 +43,7 @@ struct LogDetail: View { .listRowSeparator(.visible) /// Subsystem Label { - Text("subsystem".localized + ":") + Text("log.subsystem".localized + ":") .font(idiom == .phone ? .callout : .title) Text(log.subsystem) .font(idiom == .phone ? .callout : .title) @@ -57,7 +57,7 @@ struct LogDetail: View { .listRowSeparator(.visible) /// Process Label { - Text("process".localized + ":") + Text("log.process".localized + ":") .font(idiom == .phone ? .callout : .title) Text(log.process) .font(idiom == .phone ? .callout : .title) @@ -71,7 +71,7 @@ struct LogDetail: View { .listRowSeparator(.visible) /// Category Label { - Text("category".localized + ":") + Text("log.category".localized + ":") .font(idiom == .phone ? .callout : .title) Text(log.category) .font(idiom == .phone ? .callout : .title) @@ -85,12 +85,12 @@ struct LogDetail: View { .listRowSeparator(.visible) /// Level Label { - Text("level".localized + ":") + Text("log.level".localized + ":") .font(idiom == .phone ? .callout : .title) Text(log.level.description) .font(idiom == .phone ? .callout : .title) } icon: { - Image(systemName: "shield") + Image(systemName: "stairs") .symbolRenderingMode(.hierarchical) .font(idiom == .phone ? .callout : .title) .frame(width: 35) @@ -99,7 +99,7 @@ struct LogDetail: View { .listRowSeparator(.visible) /// message Label { - Text("message".localized + ":") + Text("log.message".localized + ":") .font(idiom == .phone ? .callout : .title) } icon: { diff --git a/Meshtastic/Views/Settings/AppLog.swift b/Meshtastic/Views/Settings/AppLog.swift index 8f8db623..6eaff0a3 100644 --- a/Meshtastic/Views/Settings/AppLog.swift +++ b/Meshtastic/Views/Settings/AppLog.swift @@ -29,18 +29,18 @@ struct AppLog: View { Table(logs, selection: $selection, sortOrder: $sortOrder) { if idiom != .phone { - TableColumn("Time", value: \.date) { value in + TableColumn("log.time", value: \.date) { value in Text(value.date.formatted(dateFormatStyle)) } .width(min: 100, max: 125) - TableColumn("Category", value: \.category) + TableColumn("log.category", value: \.category) .width(min: 100, max: 125) - TableColumn("Level") { value in + TableColumn("log.level") { value in Text(value.level.description) } .width(min: 50, max: 100) } - TableColumn("Message", value: \.composedMessage) + TableColumn("log.message", value: \.composedMessage) .width(ideal: 200, max: .infinity) } diff --git a/de.lproj/Localizable.strings b/de.lproj/Localizable.strings index 511ecdaa..83fe06d3 100644 --- a/de.lproj/Localizable.strings +++ b/de.lproj/Localizable.strings @@ -173,6 +173,12 @@ "interval.seventytwo.hours"="Seventy Two Hours"; "keyboard.type"="Keyboard Typ"; "logging"="Logging"; +"log.time"="Time"; +"log.subsystem"="Subsystem"; +"log.process"="Process"; +"log.category"="Category"; +"log.level"="Level"; +"log.message"="Message"; "lora"="LoRa"; "lora.config"="LoRa Einstellungen"; "map"="Mesh Karte"; diff --git a/en.lproj/Localizable.strings b/en.lproj/Localizable.strings index 4c507879..fb3af6f6 100644 --- a/en.lproj/Localizable.strings +++ b/en.lproj/Localizable.strings @@ -177,6 +177,12 @@ "interval.seventytwo.hours"="Seventy Two Hours"; "keyboard.type"="Keyboard Type"; "logging"="Logging"; +"log.time"="Time"; +"log.subsystem"="Subsystem"; +"log.process"="Process"; +"log.category"="Category"; +"log.level"="Level"; +"log.message"="Message"; "lora"="LoRa"; "lora.config"="LoRa Config"; "map"="Mesh Map"; diff --git a/fr.lproj/Localizable.strings b/fr.lproj/Localizable.strings index 785a2012..3c517ac3 100644 --- a/fr.lproj/Localizable.strings +++ b/fr.lproj/Localizable.strings @@ -154,6 +154,12 @@ "interval.seventytwo.hours"="Soixante douze heures"; "keyboard.type"="Type de clavier"; "logging"="Enregistrement"; +"log.time"="Time"; +"log.subsystem"="Subsystem"; +"log.process"="Process"; +"log.category"="Category"; +"log.level"="Level"; +"log.message"="Message"; "lora"="LoRa"; "lora.config"="Configuration LoRa"; "map"="Carte de maillage"; diff --git a/he.lproj/Localizable.strings b/he.lproj/Localizable.strings index 154c2391..fd844980 100644 --- a/he.lproj/Localizable.strings +++ b/he.lproj/Localizable.strings @@ -177,6 +177,12 @@ "interval.seventytwo.hours"="שבעים ושתיים שעות"; "keyboard.type"="סוג מקלדת"; "logging"="רישום"; +"log.time"="Time"; +"log.subsystem"="Subsystem"; +"log.process"="Process"; +"log.category"="Category"; +"log.level"="Level"; +"log.message"="Message"; "lora"="לורה"; "lora.config"="הגדרות לורה"; "map"="מפת מש"; diff --git a/pl.lproj/Localizable.strings b/pl.lproj/Localizable.strings index eb8372b0..e09d745b 100644 --- a/pl.lproj/Localizable.strings +++ b/pl.lproj/Localizable.strings @@ -175,6 +175,12 @@ "interval.seventytwo.hours"="Siedemdziesiąt Dwie Godziny"; "keyboard.type"="Typ Klawiatury"; "logging"="Rejestracja"; +"log.time"="Time"; +"log.subsystem"="Subsystem"; +"log.process"="Process"; +"log.category"="Category"; +"log.level"="Level"; +"log.message"="Message"; "lora"="LoRa"; "lora.config"="Konfiguracja LoRa"; "map"="Mapa Sieci"; diff --git a/pt-PT.lproj/Localizable.strings b/pt-PT.lproj/Localizable.strings index 599955bc..f5131a0c 100644 --- a/pt-PT.lproj/Localizable.strings +++ b/pt-PT.lproj/Localizable.strings @@ -177,6 +177,12 @@ "interval.seventytwo.hours"="Setenta e Duas Horas"; "keyboard.type"="Tipo de Teclado"; "logging"="Registo"; +"log.time"="Time"; +"log.subsystem"="Subsystem"; +"log.process"="Process"; +"log.category"="Category"; +"log.level"="Level"; +"log.message"="Message"; "lora"="LoRa"; "lora.config"="Configuração LoRa"; "map"="Mapa do Mesh"; diff --git a/se.lproj/Localizable.strings b/se.lproj/Localizable.strings index e45ca2fe..81d00170 100644 --- a/se.lproj/Localizable.strings +++ b/se.lproj/Localizable.strings @@ -177,6 +177,12 @@ "interval.seventytwo.hours"="Sjuttiotvå Timmar"; "keyboard.type"="Tangentbordstyp"; "logging"="Loggning"; +"log.time"="Time"; +"log.subsystem"="Subsystem"; +"log.process"="Process"; +"log.category"="Category"; +"log.level"="Level"; +"log.message"="Message"; "lora"="LoRa"; "lora.config"="LoRa Konfiguration"; "map"="Mesh Karta"; diff --git a/zh-Hans.lproj/Localizable.strings b/zh-Hans.lproj/Localizable.strings index 5606f44b..3a241f3f 100644 --- a/zh-Hans.lproj/Localizable.strings +++ b/zh-Hans.lproj/Localizable.strings @@ -173,6 +173,12 @@ "interval.eventytwo.hours"="七十二小时"; "keyboard.type"="键盘类型"; "logging"="加载中"; +"log.time"="Time"; +"log.subsystem"="Subsystem"; +"log.process"="Process"; +"log.category"="Category"; +"log.level"="Level"; +"log.message"="Message"; "lora"="LoRa"; "lora.config"="LoRa 配置"; "map"="Mesh 地图"; diff --git a/zh-Hant-TW.lproj/Localizable.strings b/zh-Hant-TW.lproj/Localizable.strings index 1665f916..fdc02df2 100644 --- a/zh-Hant-TW.lproj/Localizable.strings +++ b/zh-Hant-TW.lproj/Localizable.strings @@ -173,6 +173,12 @@ "interval.eventytwo.hours"="七十二小時"; "keyboard.type"="鍵盤類型"; "logging"="加載中"; +"log.time"="Time"; +"log.subsystem"="Subsystem"; +"log.process"="Process"; +"log.category"="Category"; +"log.level"="Level"; +"log.message"="Message"; "lora"="LoRa"; "lora.config"="LoRa 設定"; "map"="Mesh 地圖"; From b31998be6c457af970766603ef5c8222306752b9 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Thu, 6 Jun 2024 12:37:40 -0700 Subject: [PATCH 24/32] Search the logs, add a couple of stats logs --- Meshtastic/Extensions/Logger.swift | 2 +- Meshtastic/Helpers/MeshPackets.swift | 3 +++ Meshtastic/Views/Settings/AppLog.swift | 19 +++++++++++++++++-- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/Meshtastic/Extensions/Logger.swift b/Meshtastic/Extensions/Logger.swift index 62c3ff56..4ed1f673 100644 --- a/Meshtastic/Extensions/Logger.swift +++ b/Meshtastic/Extensions/Logger.swift @@ -22,7 +22,7 @@ extension Logger { static let services = Logger(subsystem: subsystem, category: "🍏 Services") /// All logs related to tracking and analytics. - static let statistics = Logger(subsystem: subsystem, category: "📈 Stats") + static let statistics = Logger(subsystem: subsystem, category: "📊 Stats") /// Fetch from the logstore static public func fetch(predicateFormat: String) async throws -> [OSLogEntryLog] { diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index 18748059..c6c9e583 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -621,6 +621,8 @@ func routingPacket (packet: MeshPacket, connectedNodeNum: Int64, context: NSMana if routingMessage.errorReason == Routing.Error.none { fetchedMessage![0].receivedACK = true + } else { + Logger.statistics.error("❗ Routing Error: \(routingErrorString) for a text message packet from Node: \(packet.from)") } fetchedMessage![0].ackSNR = packet.rxSnr fetchedMessage![0].ackTimestamp = Int32(truncatingIfNeeded: packet.rxTime) @@ -685,6 +687,7 @@ func telemetryPacket(packet: MeshPacket, connectedNode: Int64, context: NSManage telemetry.voltage = telemetryMessage.deviceMetrics.voltage telemetry.uptimeSeconds = Int32(telemetryMessage.deviceMetrics.uptimeSeconds) telemetry.metricsType = 0 + Logger.statistics.info("📈 Channel Utilization: \(telemetryMessage.deviceMetrics.channelUtilization) Airtime: \(telemetryMessage.deviceMetrics.airUtilTx) for Node: \(packet.from)") } else if telemetryMessage.variant == Telemetry.OneOf_Variant.environmentMetrics(telemetryMessage.environmentMetrics) { // Environment Metrics telemetry.barometricPressure = telemetryMessage.environmentMetrics.barometricPressure diff --git a/Meshtastic/Views/Settings/AppLog.swift b/Meshtastic/Views/Settings/AppLog.swift index 6eaff0a3..4d9347ec 100644 --- a/Meshtastic/Views/Settings/AppLog.swift +++ b/Meshtastic/Views/Settings/AppLog.swift @@ -16,6 +16,7 @@ struct AppLog: View { @State private var selection: OSLogEntry.ID? @State private var selectedLog: OSLogEntryLog? @State private var presentingErrorDetails: Bool = false + @State private var searchTerm = "" @State var isExporting = false @State var exportString = "" private var idiom: UIUserInterfaceIdiom { UIDevice.current.userInterfaceIdiom } @@ -24,10 +25,18 @@ struct AppLog: View { .minute() .second() .secondFraction(.fractional(3)) + + private var searchResults: [OSLogEntryLog] { + if searchTerm.isEmpty { + return logs.filter { _ in true } + } else { + return logs.filter { $0.composedMessage.lowercased().contains(searchTerm.lowercased) } + } + } var body: some View { - Table(logs, selection: $selection, sortOrder: $sortOrder) { + Table(searchResults, selection: $selection, sortOrder: $sortOrder) { if idiom != .phone { TableColumn("log.time", value: \.date) { value in Text(value.date.formatted(dateFormatStyle)) @@ -44,6 +53,7 @@ struct AppLog: View { .width(ideal: 200, max: .infinity) } + .searchable(text: $searchTerm, prompt: "Search") .onChange(of: sortOrder) { _, sortOrder in withAnimation { logs.sort(using: sortOrder) @@ -56,7 +66,7 @@ struct AppLog: View { } selectedLog = log } - .sheet(item: $selectedLog) { log in + .sheet(item: $selectedLog, onDismiss: didDismiss) { log in LogDetail(log: log) .padding() } @@ -90,6 +100,11 @@ struct AppLog: View { } } } + + func didDismiss() { + selection = nil + selectedLog = nil + } } @available(iOS 17.4, *) From e287ad6b78b34ef166187f2b6c0a09e684baca8a Mon Sep 17 00:00:00 2001 From: Blake McAnally Date: Thu, 6 Jun 2024 15:09:18 -0500 Subject: [PATCH 25/32] Refactor how mesh packets are persisted to the data model --- Meshtastic.xcodeproj/project.pbxproj | 12 + .../CoreData/PositionEntityExtension.swift | 17 +- .../CoreData/UserEntityExtension.swift | 37 ++- .../Protobufs/NodeInfoExtensions.swift | 11 + Meshtastic/Helpers/MeshPackets.swift | 243 +++++++----------- Meshtastic/Persistence/UpdateCoreData.swift | 28 +- 6 files changed, 161 insertions(+), 187 deletions(-) create mode 100644 Meshtastic/Extensions/Protobufs/NodeInfoExtensions.swift diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 40d646aa..3e446152 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 25A978592C124FA70003AAE7 /* NodeInfoExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25A978572C124FA70003AAE7 /* NodeInfoExtensions.swift */; }; 6DA39D8E2A92DC52007E311C /* MeshtasticAppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DA39D8D2A92DC52007E311C /* MeshtasticAppDelegate.swift */; }; 6DEDA55A2A957B8E00321D2E /* DetectionSensorLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DEDA5592A957B8E00321D2E /* DetectionSensorLog.swift */; }; 6DEDA55C2A9592F900321D2E /* MessageEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DEDA55B2A9592F900321D2E /* MessageEntityExtension.swift */; }; @@ -240,6 +241,7 @@ /* Begin PBXFileReference section */ 258EE1262C0E833D0025A5FB /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; + 25A978572C124FA70003AAE7 /* NodeInfoExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NodeInfoExtensions.swift; sourceTree = ""; }; 6DA39D8D2A92DC52007E311C /* MeshtasticAppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshtasticAppDelegate.swift; sourceTree = ""; }; 6DEDA5592A957B8E00321D2E /* DetectionSensorLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetectionSensorLog.swift; sourceTree = ""; }; 6DEDA55B2A9592F900321D2E /* MessageEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageEntityExtension.swift; sourceTree = ""; }; @@ -513,6 +515,14 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 25A978582C124FA70003AAE7 /* Protobufs */ = { + isa = PBXGroup; + children = ( + 25A978572C124FA70003AAE7 /* NodeInfoExtensions.swift */, + ); + path = Protobufs; + sourceTree = ""; + }; C9483F6B2773016700998F6B /* MapKitMap */ = { isa = PBXGroup; children = ( @@ -951,6 +961,7 @@ DDDB443E29F79A9400EE2349 /* Extensions */ = { isa = PBXGroup; children = ( + 25A978582C124FA70003AAE7 /* Protobufs */, DD007BB12AA59B9A00F5FA12 /* CoreData */, DDFFA7462B3A7F3C004730DB /* Bundle.swift */, DDDB444529F8A96500EE2349 /* Character.swift */, @@ -1314,6 +1325,7 @@ DD0F791B28713C8A00A6FDAD /* AdminMessageList.swift in Sources */, DD3CC6BC28E366DF00FA9159 /* Meshtastic.xcdatamodeld in Sources */, DDC4C9FF2A8D982900CE201C /* DetectionSensorConfig.swift in Sources */, + 25A978592C124FA70003AAE7 /* NodeInfoExtensions.swift in Sources */, D9C983A22B79D1A600BDBE6A /* RequestPositionButton.swift in Sources */, DDDB26442AAC0206003AFCB7 /* NodeDetail.swift in Sources */, DD5E5210298EE33B00D21B61 /* telemetry.pb.swift in Sources */, diff --git a/Meshtastic/Extensions/CoreData/PositionEntityExtension.swift b/Meshtastic/Extensions/CoreData/PositionEntityExtension.swift index 0c634779..37261ed6 100644 --- a/Meshtastic/Extensions/CoreData/PositionEntityExtension.swift +++ b/Meshtastic/Extensions/CoreData/PositionEntityExtension.swift @@ -11,7 +11,22 @@ import MapKit import SwiftUI extension PositionEntity { - + convenience init( + context: NSManagedObjectContext, + nodeInfo: NodeInfo + ) { + self.init(context: context) + self.latest = true + self.seqNo = Int32(nodeInfo.position.seqNumber) + self.latitudeI = nodeInfo.position.latitudeI + self.longitudeI = nodeInfo.position.longitudeI + self.altitude = nodeInfo.position.altitude + self.satsInView = Int32(nodeInfo.position.satsInView) + self.speed = Int32(nodeInfo.position.groundSpeed) + self.heading = Int32(nodeInfo.position.groundTrack) + self.time = Date(timeIntervalSince1970: TimeInterval(Int64(nodeInfo.position.time))) + } + static func allPositionsFetchRequest() -> NSFetchRequest { let request: NSFetchRequest = PositionEntity.fetchRequest() request.fetchLimit = 1000 diff --git a/Meshtastic/Extensions/CoreData/UserEntityExtension.swift b/Meshtastic/Extensions/CoreData/UserEntityExtension.swift index a8adeaaf..886f90ca 100644 --- a/Meshtastic/Extensions/CoreData/UserEntityExtension.swift +++ b/Meshtastic/Extensions/CoreData/UserEntityExtension.swift @@ -9,6 +9,31 @@ import Foundation import CoreData extension UserEntity { + convenience init( + context: NSManagedObjectContext, + user: User, + num: Int + ) { + self.init(context: context) + self.userId = user.id + self.num = Int64(num) + self.longName = user.longName + self.shortName = user.shortName + self.hwModel = String(describing: user.hwModel).uppercased() + self.isLicensed = user.isLicensed + self.role = Int32(user.role.rawValue) + } + + convenience init(context: NSManagedObjectContext, num: Int) { + self.init(context: context) + self.num = Int64(num) + let userId = String(format: "!%2X", num) + self.userId = userId + let last4 = String(userId.suffix(4)) + self.longName = "Meshtastic \(last4)" + self.shortName = last4 + self.hwModel = "UNSET" + } var messageList: [MessageEntity] { self.value(forKey: "allMessages") as? [MessageEntity] ?? [MessageEntity]() @@ -27,15 +52,3 @@ extension UserEntity { return unreadMessages.count } } - -public func createUser(num: Int64, context: NSManagedObjectContext) -> UserEntity { - let newUser = UserEntity(context: context) - newUser.num = Int64(num) - let userId = String(format: "%2X", num) - newUser.userId = "!\(userId)" - let last4 = String(userId.suffix(4)) - newUser.longName = "Meshtastic \(last4)" - newUser.shortName = last4 - newUser.hwModel = "UNSET" - return newUser -} diff --git a/Meshtastic/Extensions/Protobufs/NodeInfoExtensions.swift b/Meshtastic/Extensions/Protobufs/NodeInfoExtensions.swift new file mode 100644 index 00000000..ff20c78a --- /dev/null +++ b/Meshtastic/Extensions/Protobufs/NodeInfoExtensions.swift @@ -0,0 +1,11 @@ +import Foundation + +extension NodeInfo { + var isValidPosition: Bool { + hasPosition && + position.longitudeI != 0 && + position.latitudeI != 0 && + position.latitudeI != 373346000 && + position.longitudeI != -1220090000 + } +} diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index 18748059..5611ead7 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -251,182 +251,111 @@ func deviceMetadataPacket (metadata: DeviceMetadata, fromNum: Int64, context: NS func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObjectContext) -> NodeInfoEntity? { - let logString = String.localizedStringWithFormat("mesh.log.nodeinfo.received %@".localized, String(nodeInfo.num)) + let logString = String.localizedStringWithFormat( + "mesh.log.nodeinfo.received %@ %@".localized, + String(nodeInfo.num), + String(nodeInfo.viaMqtt) + ) MeshLogger.log("📟 \(logString)") - guard nodeInfo.num > 0 else { return nil } + guard nodeInfo.num > 0 else { + Logger.data.error("nodeInfo \(nodeInfo.num) invalid") + return nil + } let fetchNodeInfoRequest: NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeInfo.num)) do { - guard let fetchedNode = try context.fetch(fetchNodeInfoRequest) as? [NodeInfoEntity] else { + guard let fetchedNodes = try context.fetch(fetchNodeInfoRequest) as? [NodeInfoEntity] else { return nil } - // Not Found Insert - if fetchedNode.isEmpty && nodeInfo.num > 0 { - - let newNode = NodeInfoEntity(context: context) - newNode.id = Int64(nodeInfo.num) - newNode.num = Int64(nodeInfo.num) - newNode.channel = Int32(nodeInfo.channel) - newNode.favorite = nodeInfo.isFavorite - newNode.hopsAway = Int32(nodeInfo.hopsAway) - newNode.viaMqtt = nodeInfo.viaMqtt - - if nodeInfo.hasDeviceMetrics { - let telemetry = TelemetryEntity(context: context) - telemetry.batteryLevel = Int32(nodeInfo.deviceMetrics.batteryLevel) - telemetry.voltage = nodeInfo.deviceMetrics.voltage - telemetry.channelUtilization = nodeInfo.deviceMetrics.channelUtilization - telemetry.airUtilTx = nodeInfo.deviceMetrics.airUtilTx - var newTelemetries = [TelemetryEntity]() - newTelemetries.append(telemetry) - newNode.telemetries? = NSOrderedSet(array: newTelemetries) + let node: NodeInfoEntity + if let update = fetchedNodes.first { + node = update + } else { + node = NodeInfoEntity(context: context) + } + + node.id = Int64(nodeInfo.num) + node.num = Int64(nodeInfo.num) + node.channel = Int32(nodeInfo.channel) + node.favorite = nodeInfo.isFavorite + node.hopsAway = Int32(nodeInfo.hopsAway) + node.viaMqtt = nodeInfo.viaMqtt + + if nodeInfo.hasDeviceMetrics { + let newTelemetry = TelemetryEntity(context: context) + newTelemetry.batteryLevel = Int32(nodeInfo.deviceMetrics.batteryLevel) + newTelemetry.voltage = nodeInfo.deviceMetrics.voltage + newTelemetry.channelUtilization = nodeInfo.deviceMetrics.channelUtilization + newTelemetry.airUtilTx = nodeInfo.deviceMetrics.airUtilTx + + var telemetries: [TelemetryEntity] + if let tele = node.telemetries?.array as? [TelemetryEntity] { + telemetries = tele + telemetries.append(newTelemetry) + } else { + telemetries = [newTelemetry] } - - newNode.lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(nodeInfo.lastHeard))) - newNode.snr = nodeInfo.snr - if nodeInfo.hasUser { - - let newUser = UserEntity(context: context) - newUser.userId = nodeInfo.user.id - newUser.num = Int64(nodeInfo.num) - newUser.longName = nodeInfo.user.longName - newUser.shortName = nodeInfo.user.shortName - newUser.hwModel = String(describing: nodeInfo.user.hwModel).uppercased() - newUser.isLicensed = nodeInfo.user.isLicensed - newUser.role = Int32(nodeInfo.user.role.rawValue) - newNode.user = newUser - } else if nodeInfo.num > Int16.max { - let newUser = createUser(num: Int64(nodeInfo.num), context: context) - newNode.user = newUser + node.telemetries = NSOrderedSet(array: telemetries) + } + + + node.lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(nodeInfo.lastHeard))) + node.snr = nodeInfo.snr + + // User + var user: UserEntity? + if nodeInfo.hasUser { + user = UserEntity( + context: context, + user: nodeInfo.user, + num: Int(nodeInfo.num) + ) + } else if nodeInfo.num > Int16.max { + user = UserEntity( + context: context, + num: Int(nodeInfo.num) + ) + } + node.user = user + + // Position + if nodeInfo.isValidPosition { + let position = PositionEntity( + context: context, + nodeInfo: nodeInfo + ) + + if let positions = node.positions?.mutableCopy() as? NSMutableOrderedSet { + positions.add(position) + node.positions = positions + } else { + node.positions = NSOrderedSet(object: position) } + } - if (nodeInfo.position.longitudeI != 0 && nodeInfo.position.latitudeI != 0) && (nodeInfo.position.latitudeI != 373346000 && nodeInfo.position.longitudeI != -1220090000) { - let position = PositionEntity(context: context) - position.latest = true - position.seqNo = Int32(nodeInfo.position.seqNumber) - position.latitudeI = nodeInfo.position.latitudeI - position.longitudeI = nodeInfo.position.longitudeI - position.altitude = nodeInfo.position.altitude - position.satsInView = Int32(nodeInfo.position.satsInView) - position.speed = Int32(nodeInfo.position.groundSpeed) - position.heading = Int32(nodeInfo.position.groundTrack) - position.time = Date(timeIntervalSince1970: TimeInterval(Int64(nodeInfo.position.time))) - var newPostions = [PositionEntity]() - 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 { - guard let fetchedMyInfo = try context.fetch(fetchMyInfoRequest) as? [MyInfoEntity] else { - return nil - } - if fetchedMyInfo.count > 0 { - newNode.myInfo = fetchedMyInfo[0] - } + // MyInfo + do { + let fetchMyInfoRequest = MyInfoEntity.fetchRequest() + fetchMyInfoRequest.predicate = NSPredicate( + format: "myNodeNum == %lld", Int64(nodeInfo.num) + ) + let fetchedMyInfo = try context.fetch(fetchMyInfoRequest) + if let myInfo = fetchedMyInfo.first { + node.myInfo = myInfo do { try context.save() Logger.data.info("💾 Saved a new Node Info For: \(String(nodeInfo.num))") - return newNode + return node } catch { context.rollback() - let nsError = error as NSError - Logger.data.error("Error Saving Core Data NodeInfoEntity: \(nsError)") - } - } catch { - Logger.data.error("Fetch MyInfo Error") - } - } else if 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(nodeInfo.channel) - fetchedNode[0].favorite = nodeInfo.isFavorite - fetchedNode[0].hopsAway = Int32(nodeInfo.hopsAway) - fetchedNode[0].viaMqtt = nodeInfo.viaMqtt - - if nodeInfo.hasUser { - if fetchedNode[0].user == nil { - fetchedNode[0].user = UserEntity(context: context) - } - fetchedNode[0].user!.userId = nodeInfo.user.id - fetchedNode[0].user!.num = Int64(nodeInfo.num) - fetchedNode[0].user!.numString = String(nodeInfo.num) - fetchedNode[0].user!.longName = nodeInfo.user.longName - fetchedNode[0].user!.shortName = nodeInfo.user.shortName - fetchedNode[0].user!.isLicensed = nodeInfo.user.isLicensed - fetchedNode[0].user!.role = Int32(nodeInfo.user.role.rawValue) - fetchedNode[0].user!.hwModel = String(describing: nodeInfo.user.hwModel).uppercased() - } else { - if fetchedNode[0].user == nil && nodeInfo.num > Int16.max { - - let newUser = createUser(num: Int64(nodeInfo.num), context: context) - fetchedNode[0].user = newUser + Logger.data.error("Error Saving Core Data NodeInfoEntity: \(error.localizedDescription)") } } - - if nodeInfo.hasDeviceMetrics { - - let newTelemetry = TelemetryEntity(context: context) - newTelemetry.batteryLevel = Int32(nodeInfo.deviceMetrics.batteryLevel) - newTelemetry.voltage = nodeInfo.deviceMetrics.voltage - newTelemetry.channelUtilization = nodeInfo.deviceMetrics.channelUtilization - newTelemetry.airUtilTx = nodeInfo.deviceMetrics.airUtilTx - guard let mutableTelemetries = fetchedNode[0].telemetries!.mutableCopy() as? NSMutableOrderedSet else { - return nil - } - 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 - position.altitude = nodeInfo.position.altitude - position.satsInView = Int32(nodeInfo.position.satsInView) - position.time = Date(timeIntervalSince1970: TimeInterval(Int64(nodeInfo.position.time))) - guard let mutablePositions = fetchedNode[0].positions!.mutableCopy() as? NSMutableOrderedSet else { - return nil - } - 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 { - guard let fetchedMyInfo = try context.fetch(fetchMyInfoRequest) as? [MyInfoEntity] else { - return nil - } - if fetchedMyInfo.count > 0 { - fetchedNode[0].myInfo = fetchedMyInfo[0] - } - do { - try context.save() - Logger.data.info("💾 NodeInfo saved for \(nodeInfo.num)") - return fetchedNode[0] - } catch { - context.rollback() - let nsError = error as NSError - Logger.data.error("Error Saving Core Data NodeInfoEntity: \(nsError)") - } - } catch { - Logger.data.error("Fetch MyInfo Error") - } + } catch { + Logger.data.error("Fetch MyInfo Error: \(error.localizedDescription)") } } catch { Logger.data.error("Fetch NodeInfoEntity Error") diff --git a/Meshtastic/Persistence/UpdateCoreData.swift b/Meshtastic/Persistence/UpdateCoreData.swift index 1ef04ff7..9472fd05 100644 --- a/Meshtastic/Persistence/UpdateCoreData.swift +++ b/Meshtastic/Persistence/UpdateCoreData.swift @@ -166,20 +166,14 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext) if let newUserMessage = try? User(serializedData: packet.decoded.payload) { - if newUserMessage.id.isEmpty { - if packet.from > Int16.max { - let newUser = createUser(num: Int64(packet.from), context: context) - newNode.user = newUser - } + if newUserMessage.id.isEmpty, packet.from > Int16.max { + newNode.user = UserEntity(context: context, num: Int(packet.from)) } else { - - let newUser = UserEntity(context: context) - newUser.userId = newUserMessage.id - newUser.num = Int64(packet.from) - newUser.longName = newUserMessage.longName - newUser.shortName = newUserMessage.shortName - newUser.role = Int32(newUserMessage.role.rawValue) - newUser.hwModel = String(describing: newUserMessage.hwModel).uppercased() + let newUser = UserEntity( + context: context, + user: newUserMessage, + num: Int(packet.from) + ) newNode.user = newUser if UserDefaults.newNodeNotifications { @@ -199,13 +193,13 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext) } } else { if packet.from > Int16.max { - let newUser = createUser(num: Int64(packet.from), context: context) + let newUser = UserEntity(context: context, num: Int(packet.from)) fetchedNode[0].user = newUser } } if newNode.user == nil && packet.from > Int16.max { - newNode.user = createUser(num: Int64(packet.from), context: context) + newNode.user = UserEntity(context: context, num: Int(packet.from)) } let myInfoEntity = MyInfoEntity(context: context) @@ -265,8 +259,8 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext) fetchedNode[0].hopsAway = Int32(packet.hopStart - packet.hopLimit) } if fetchedNode[0].user == nil { - let newUser = createUser(num: Int64(packet.from), context: context) - fetchedNode[0].user! = newUser + let newUser = UserEntity(context: context, num: Int(packet.from)) + fetchedNode[0].user = newUser } do { try context.save() From 1c7c1020906cc7bc9b1a3b98ddf5632cbdb8da76 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Thu, 6 Jun 2024 14:06:39 -0700 Subject: [PATCH 26/32] Dont show export button when there are no logs to export, use content unavailable view for a loading indicator. --- Meshtastic/Views/Settings/AppLog.swift | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/Meshtastic/Views/Settings/AppLog.swift b/Meshtastic/Views/Settings/AppLog.swift index 4d9347ec..3188f306 100644 --- a/Meshtastic/Views/Settings/AppLog.swift +++ b/Meshtastic/Views/Settings/AppLog.swift @@ -54,6 +54,12 @@ struct AppLog: View { } .searchable(text: $searchTerm, prompt: "Search") + .disabled(selection != nil) + .overlay { + if logs.isEmpty { + ContentUnavailableView("Getting Logs . . .", systemImage: "scroll") + } + } .onChange(of: sortOrder) { _, sortOrder in withAnimation { logs.sort(using: sortOrder) @@ -90,12 +96,14 @@ struct AppLog: View { ) .navigationBarTitle("Debug Logs", displayMode: .inline) .toolbar { - ToolbarItem(placement: .navigationBarTrailing) { - Button(action: { - exportString = logToCsvFile(log: logs) - isExporting = true - }) { - Image(systemName: "square.and.arrow.down") + if !logs.isEmpty { + ToolbarItem(placement: .navigationBarTrailing) { + Button(action: { + exportString = logToCsvFile(log: logs) + isExporting = true + }) { + Image(systemName: "square.and.arrow.down") + } } } } From c43cd4338d1cbb2b49237ab55d5c535a7e0fe825 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Thu, 6 Jun 2024 15:45:54 -0700 Subject: [PATCH 27/32] LIttle logging cleanup --- Meshtastic/Extensions/Logger.swift | 2 +- Meshtastic/Helpers/BLEManager.swift | 14 +++++++------- Meshtastic/Views/Settings/AppLog.swift | 2 +- Meshtastic/Views/Settings/Settings.swift | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Meshtastic/Extensions/Logger.swift b/Meshtastic/Extensions/Logger.swift index 4ed1f673..930d47ef 100644 --- a/Meshtastic/Extensions/Logger.swift +++ b/Meshtastic/Extensions/Logger.swift @@ -51,7 +51,7 @@ extension OSLogEntryLog.Level { var description: String { switch self { case .undefined: "undefined" - case .debug: "🐛 Debug" + case .debug: "🩺 Debug" case .info: "ℹ️ Info" case .notice: "⚠️ Notice" case .error: "🚨 Error" diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 42acd0a0..fb2c5f29 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -62,7 +62,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate func startScanning() { if isSwitchedOn { centralManager.scanForPeripherals(withServices: [meshtasticServiceCBUUID], options: [CBCentralManagerScanOptionAllowDuplicatesKey: false]) - Logger.services.info("✅ Scanning Started") + Logger.services.info("🟢 Started Scanning for BLE Devices") } } @@ -70,7 +70,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate func stopScanning() { if centralManager.isScanning { centralManager.stopScan() - Logger.services.info("🛑 Stopped Scanning") + Logger.services.info("🛑 Stopped Scanning for BLE Devices") } } @@ -492,7 +492,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) { - Logger.services.error("💥 didUpdateNotificationStateFor error: \(error?.localizedDescription ?? "Unknown")") + Logger.services.error("💥 BLE didUpdateNotificationStateFor error: \(error?.localizedDescription ?? "Unknown")") } // MARK: Data Read / Update Characteristic Event @@ -500,14 +500,13 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate if let error { - Logger.services.error("🚫 didUpdateValueFor Characteristic error \(error.localizedDescription)") + Logger.services.error("🚫 BLE didUpdateValueFor Characteristic error \(error.localizedDescription)") let errorCode = (error as NSError).code if errorCode == 5 || errorCode == 15 { // BLE PIN connection errors // 5 CBATTErrorDomain Code=5 "Authentication is insufficient." // 15 CBATTErrorDomain Code=15 "Encryption is insufficient." lastConnectionError = "🚨" + String.localizedStringWithFormat("ble.errorcode.pin %@".localized, error.localizedDescription) - Logger.services.error("💥 \(error.localizedDescription) Please try connecting again and check the PIN carefully.") self.disconnectPeripheral(reconnect: false) } return @@ -803,7 +802,8 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } case FROMNUM_UUID: - Logger.services.info("🗞️ BLE (Notify) characteristic, value will be read next") + return + // Logger.services.info("🗞️ BLE (Notify) characteristic, value will be read next") default: Logger.services.error("Unhandled Characteristic UUID: \(characteristic.uuid)") } @@ -3013,7 +3013,7 @@ extension BLEManager: CBCentralManagerDelegate { default: status = "default" } - Logger.services.debug("📜 BLEManager status: \(status)") + Logger.services.debug("📜 BLE status: \(status)") } // Called each time a peripheral is discovered diff --git a/Meshtastic/Views/Settings/AppLog.swift b/Meshtastic/Views/Settings/AppLog.swift index 3188f306..ffb83514 100644 --- a/Meshtastic/Views/Settings/AppLog.swift +++ b/Meshtastic/Views/Settings/AppLog.swift @@ -94,7 +94,7 @@ struct AppLog: View { } } ) - .navigationBarTitle("Debug Logs", displayMode: .inline) + .navigationBarTitle("Debug Logs\(logs.isEmpty ? "" : " (\(logs.count))")", displayMode: .inline) .toolbar { if !logs.isEmpty { ToolbarItem(placement: .navigationBarTrailing) { diff --git a/Meshtastic/Views/Settings/Settings.swift b/Meshtastic/Views/Settings/Settings.swift index 55a77b65..45a14230 100644 --- a/Meshtastic/Views/Settings/Settings.swift +++ b/Meshtastic/Views/Settings/Settings.swift @@ -431,7 +431,7 @@ struct Settings: View { Label { Text("Debug Logs") } icon: { - Image(systemName: "ladybug") + Image(systemName: "stethoscope") } } .tag(SettingsSidebar.appLog) From 893fdcd11f3c7b4c4cf1ca5b6e2c3274b8213718 Mon Sep 17 00:00:00 2001 From: Blake McAnally Date: Thu, 6 Jun 2024 21:45:43 -0500 Subject: [PATCH 28/32] Factor out more CoreData entity creation into convenience initializers --- Meshtastic.xcodeproj/project.pbxproj | 96 ++++--- .../CoreData/ChannelEntityExtension.swift | 41 +++ .../DeviceMetadataEntityExtension.swift | 24 ++ ...nalNotificationConfigEntityExtension.swift | 43 +++ .../CoreData/LocationEntityExtension.swift | 16 ++ .../CoreData/MQTTConfigEntityExtension.swift | 37 +++ .../CoreData/NodeInfoEntityExtension.swift | 26 +- .../RangeTestConfigEntityExtension.swift | 19 ++ .../SerialConfigEntityExtension.swift | 27 ++ .../StoreForwardConfigEntityExtension.swift | 23 ++ .../CoreData/WaypointEntityExtension.swift | 13 + Meshtastic/Helpers/BLEManager.swift | 4 +- Meshtastic/Helpers/MeshPackets.swift | 66 ++--- Meshtastic/Persistence/UpdateCoreData.swift | 262 ++++++------------ .../MapKitMap/Custom/MapViewSwiftUI.swift | 6 +- Meshtastic/Views/Nodes/MeshMap.swift | 13 +- Meshtastic/Views/Settings/Channels.swift | 24 +- Meshtastic/Views/Settings/RouteRecorder.swift | 39 ++- Widgets/Info.plist | 2 + 19 files changed, 470 insertions(+), 311 deletions(-) create mode 100644 Meshtastic/Extensions/CoreData/DeviceMetadataEntityExtension.swift create mode 100644 Meshtastic/Extensions/CoreData/ExternalNotificationConfigEntityExtension.swift create mode 100644 Meshtastic/Extensions/CoreData/MQTTConfigEntityExtension.swift create mode 100644 Meshtastic/Extensions/CoreData/RangeTestConfigEntityExtension.swift create mode 100644 Meshtastic/Extensions/CoreData/SerialConfigEntityExtension.swift create mode 100644 Meshtastic/Extensions/CoreData/StoreForwardConfigEntityExtension.swift diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 3e446152..9a194160 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -8,9 +8,23 @@ /* Begin PBXBuildFile section */ 25A978592C124FA70003AAE7 /* NodeInfoExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25A978572C124FA70003AAE7 /* NodeInfoExtensions.swift */; }; + 25A978A92C12BD3F0003AAE7 /* WaypointEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25A9789A2C12BD3E0003AAE7 /* WaypointEntityExtension.swift */; }; + 25A978AA2C12BD3F0003AAE7 /* ChannelEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25A9789B2C12BD3E0003AAE7 /* ChannelEntityExtension.swift */; }; + 25A978AB2C12BD3F0003AAE7 /* MessageEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25A9789C2C12BD3E0003AAE7 /* MessageEntityExtension.swift */; }; + 25A978AC2C12BD3F0003AAE7 /* TraceRouteEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25A9789D2C12BD3E0003AAE7 /* TraceRouteEntityExtension.swift */; }; + 25A978AD2C12BD3F0003AAE7 /* LocationEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25A9789E2C12BD3E0003AAE7 /* LocationEntityExtension.swift */; }; + 25A978AE2C12BD3F0003AAE7 /* RangeTestConfigEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25A9789F2C12BD3E0003AAE7 /* RangeTestConfigEntityExtension.swift */; }; + 25A978AF2C12BD3F0003AAE7 /* MQTTConfigEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25A978A02C12BD3E0003AAE7 /* MQTTConfigEntityExtension.swift */; }; + 25A978B02C12BD3F0003AAE7 /* ExternalNotificationConfigEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25A978A12C12BD3E0003AAE7 /* ExternalNotificationConfigEntityExtension.swift */; }; + 25A978B12C12BD3F0003AAE7 /* StoreForwardConfigEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25A978A22C12BD3E0003AAE7 /* StoreForwardConfigEntityExtension.swift */; }; + 25A978B22C12BD3F0003AAE7 /* SerialConfigEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25A978A32C12BD3E0003AAE7 /* SerialConfigEntityExtension.swift */; }; + 25A978B32C12BD3F0003AAE7 /* DeviceMetadataEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25A978A42C12BD3F0003AAE7 /* DeviceMetadataEntityExtension.swift */; }; + 25A978B42C12BD3F0003AAE7 /* UserEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25A978A52C12BD3F0003AAE7 /* UserEntityExtension.swift */; }; + 25A978B52C12BD3F0003AAE7 /* MyInfoEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25A978A62C12BD3F0003AAE7 /* MyInfoEntityExtension.swift */; }; + 25A978B62C12BD3F0003AAE7 /* PositionEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25A978A72C12BD3F0003AAE7 /* PositionEntityExtension.swift */; }; + 25A978B72C12BD3F0003AAE7 /* NodeInfoEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25A978A82C12BD3F0003AAE7 /* NodeInfoEntityExtension.swift */; }; 6DA39D8E2A92DC52007E311C /* MeshtasticAppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DA39D8D2A92DC52007E311C /* MeshtasticAppDelegate.swift */; }; 6DEDA55A2A957B8E00321D2E /* DetectionSensorLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DEDA5592A957B8E00321D2E /* DetectionSensorLog.swift */; }; - 6DEDA55C2A9592F900321D2E /* MessageEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DEDA55B2A9592F900321D2E /* MessageEntityExtension.swift */; }; B399E8A42B6F486400E4488E /* RetryButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = B399E8A32B6F486400E4488E /* RetryButton.swift */; }; B3E905B12B71F7F300654D07 /* TextMessageField.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3E905B02B71F7F300654D07 /* TextMessageField.swift */; }; C9697F9D279336B700250207 /* LocalMBTileOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9697F9C279336B700250207 /* LocalMBTileOverlay.swift */; }; @@ -26,8 +40,6 @@ D9C9839D2B79CFD700BDBE6A /* TextMessageSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9C9839C2B79CFD700BDBE6A /* TextMessageSize.swift */; }; D9C983A02B79D0E800BDBE6A /* AlertButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9C9839F2B79D0E800BDBE6A /* AlertButton.swift */; }; D9C983A22B79D1A600BDBE6A /* RequestPositionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9C983A12B79D1A600BDBE6A /* RequestPositionButton.swift */; }; - DD007BAE2AA4E91200F5FA12 /* MyInfoEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD007BAD2AA4E91200F5FA12 /* MyInfoEntityExtension.swift */; }; - DD007BB02AA5981000F5FA12 /* NodeInfoEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD007BAF2AA5981000F5FA12 /* NodeInfoEntityExtension.swift */; }; DD0D3D222A55CEB10066DB71 /* CocoaMQTT in Frameworks */ = {isa = PBXBuildFile; productRef = DD0D3D212A55CEB10066DB71 /* CocoaMQTT */; }; DD0E20FC2B87090400F2D100 /* atak.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD0E20F92B87090400F2D100 /* atak.pb.swift */; }; DD0E20FD2B87090400F2D100 /* clientonly.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD0E20FA2B87090400F2D100 /* clientonly.pb.swift */; }; @@ -69,8 +81,6 @@ DD4A911E2708C65400501B7E /* AppSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4A911D2708C65400501B7E /* AppSettings.swift */; }; DD4F23CD28779A3C001D37CB /* EnvironmentMetricsLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4F23CC28779A3C001D37CB /* EnvironmentMetricsLog.swift */; }; DD5394FC276993AD00AD86B1 /* SwiftProtobuf in Frameworks */ = {isa = PBXBuildFile; productRef = DD5394FB276993AD00AD86B1 /* SwiftProtobuf */; }; - DD5394FE276BA0EF00AD86B1 /* PositionEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5394FD276BA0EF00AD86B1 /* PositionEntityExtension.swift */; }; - DD58C5F22919AD3C00D5BEFB /* ChannelEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD58C5F12919AD3C00D5BEFB /* ChannelEntityExtension.swift */; }; DD5D0A9C2931B9F200F7EA61 /* EthernetModes.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5D0A9B2931B9F200F7EA61 /* EthernetModes.swift */; }; DD5E5202298EE33B00D21B61 /* admin.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5E51F0298EE33B00D21B61 /* admin.pb.swift */; }; DD5E5203298EE33B00D21B61 /* config.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5E51F1298EE33B00D21B61 /* config.pb.swift */; }; @@ -117,7 +127,6 @@ DD94B7402ACCE3BE00DCD1D1 /* MapSettingsForm.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD94B73F2ACCE3BE00DCD1D1 /* MapSettingsForm.swift */; }; DD964FBD296E6B01007C176F /* EmojiOnlyTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD964FBC296E6B01007C176F /* EmojiOnlyTextField.swift */; }; DD964FBF296E76EF007C176F /* WaypointFormMapKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD964FBE296E76EF007C176F /* WaypointFormMapKit.swift */; }; - DD964FC2297272AE007C176F /* WaypointEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD964FC1297272AE007C176F /* WaypointEntityExtension.swift */; }; DD964FC42974767D007C176F /* MapViewFitExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD964FC32974767D007C176F /* MapViewFitExtension.swift */; }; DD964FC62975DBFD007C176F /* QueryCoreData.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD964FC52975DBFD007C176F /* QueryCoreData.swift */; }; DD97E96628EFD9820056DDA4 /* MeshtasticLogo.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD97E96528EFD9820056DDA4 /* MeshtasticLogo.swift */; }; @@ -130,7 +139,6 @@ DDA9515C2BC6631200CEA535 /* TelemetryEnums.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA9515B2BC6631200CEA535 /* TelemetryEnums.swift */; }; DDA9515E2BC6F56F00CEA535 /* IndoorAirQuality.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA9515D2BC6F56F00CEA535 /* IndoorAirQuality.swift */; }; DDAB580D2B0DAA9E00147258 /* Routes.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDAB580C2B0DAA9E00147258 /* Routes.swift */; }; - DDAB580F2B0DAFBC00147258 /* LocationEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDAB580E2B0DAFBC00147258 /* LocationEntityExtension.swift */; }; DDAD49ED2AFB39DC00B4425D /* MeshMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDAD49EC2AFB39DC00B4425D /* MeshMap.swift */; }; DDAF8C5326EB1DF10058C060 /* BLEManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDAF8C5226EB1DF10058C060 /* BLEManager.swift */; }; DDB6ABD628AE742000384BA1 /* BluetoothConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB6ABD528AE742000384BA1 /* BluetoothConfig.swift */; }; @@ -170,7 +178,6 @@ DDD43FE32A78C8900083A3E9 /* MqttClientProxyManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD43FE22A78C8900083A3E9 /* MqttClientProxyManager.swift */; }; DDD6EEAF29BC024700383354 /* Firmware.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD6EEAE29BC024700383354 /* Firmware.swift */; }; DDD94A502845C8F5004A87A0 /* DateTimeText.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD94A4F2845C8F5004A87A0 /* DateTimeText.swift */; }; - DDD9E4E4284B208E003777C5 /* UserEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD9E4E3284B208E003777C5 /* UserEntityExtension.swift */; }; DDDB263F2AABEE20003AFCB7 /* NodeList.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDDB263E2AABEE20003AFCB7 /* NodeList.swift */; }; DDDB26422AABF655003AFCB7 /* NodeListItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDDB26412AABF655003AFCB7 /* NodeListItem.swift */; }; DDDB26442AAC0206003AFCB7 /* NodeDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDDB26432AAC0206003AFCB7 /* NodeDetail.swift */; }; @@ -203,7 +210,6 @@ DDDE5A1429AFEAB900490C6C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DDDE5A1229AFEAB900490C6C /* Assets.xcassets */; }; DDE0F7C5295F77B700B8AAB3 /* AppSettingsEnums.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE0F7C4295F77B700B8AAB3 /* AppSettingsEnums.swift */; }; DDE5B4042B2279A700FCDD05 /* TraceRouteLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE5B4032B2279A700FCDD05 /* TraceRouteLog.swift */; }; - DDE5B4062B227E3200FCDD05 /* TraceRouteEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE5B4052B227E3200FCDD05 /* TraceRouteEntityExtension.swift */; }; DDE9659C2B1C3B6A00531070 /* RouteRecorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE9659B2B1C3B6A00531070 /* RouteRecorder.swift */; }; DDF45C342BC1A48E005ED5F2 /* MQTTIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF45C332BC1A48E005ED5F2 /* MQTTIcon.swift */; }; DDF45C372BC46A5A005ED5F2 /* TimeZone.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF45C362BC46A5A005ED5F2 /* TimeZone.swift */; }; @@ -242,9 +248,23 @@ /* Begin PBXFileReference section */ 258EE1262C0E833D0025A5FB /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 25A978572C124FA70003AAE7 /* NodeInfoExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NodeInfoExtensions.swift; sourceTree = ""; }; + 25A9789A2C12BD3E0003AAE7 /* WaypointEntityExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WaypointEntityExtension.swift; sourceTree = ""; }; + 25A9789B2C12BD3E0003AAE7 /* ChannelEntityExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChannelEntityExtension.swift; sourceTree = ""; }; + 25A9789C2C12BD3E0003AAE7 /* MessageEntityExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageEntityExtension.swift; sourceTree = ""; }; + 25A9789D2C12BD3E0003AAE7 /* TraceRouteEntityExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TraceRouteEntityExtension.swift; sourceTree = ""; }; + 25A9789E2C12BD3E0003AAE7 /* LocationEntityExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocationEntityExtension.swift; sourceTree = ""; }; + 25A9789F2C12BD3E0003AAE7 /* RangeTestConfigEntityExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RangeTestConfigEntityExtension.swift; sourceTree = ""; }; + 25A978A02C12BD3E0003AAE7 /* MQTTConfigEntityExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MQTTConfigEntityExtension.swift; sourceTree = ""; }; + 25A978A12C12BD3E0003AAE7 /* ExternalNotificationConfigEntityExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExternalNotificationConfigEntityExtension.swift; sourceTree = ""; }; + 25A978A22C12BD3E0003AAE7 /* StoreForwardConfigEntityExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StoreForwardConfigEntityExtension.swift; sourceTree = ""; }; + 25A978A32C12BD3E0003AAE7 /* SerialConfigEntityExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SerialConfigEntityExtension.swift; sourceTree = ""; }; + 25A978A42C12BD3F0003AAE7 /* DeviceMetadataEntityExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceMetadataEntityExtension.swift; sourceTree = ""; }; + 25A978A52C12BD3F0003AAE7 /* UserEntityExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserEntityExtension.swift; sourceTree = ""; }; + 25A978A62C12BD3F0003AAE7 /* MyInfoEntityExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MyInfoEntityExtension.swift; sourceTree = ""; }; + 25A978A72C12BD3F0003AAE7 /* PositionEntityExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PositionEntityExtension.swift; sourceTree = ""; }; + 25A978A82C12BD3F0003AAE7 /* NodeInfoEntityExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NodeInfoEntityExtension.swift; sourceTree = ""; }; 6DA39D8D2A92DC52007E311C /* MeshtasticAppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshtasticAppDelegate.swift; sourceTree = ""; }; 6DEDA5592A957B8E00321D2E /* DetectionSensorLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetectionSensorLog.swift; sourceTree = ""; }; - 6DEDA55B2A9592F900321D2E /* MessageEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageEntityExtension.swift; sourceTree = ""; }; A65FA974296876BF00A97686 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; B399E8A32B6F486400E4488E /* RetryButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RetryButton.swift; sourceTree = ""; }; B3E905B02B71F7F300654D07 /* TextMessageField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextMessageField.swift; sourceTree = ""; }; @@ -261,8 +281,6 @@ D9C9839C2B79CFD700BDBE6A /* TextMessageSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextMessageSize.swift; sourceTree = ""; }; D9C9839F2B79D0E800BDBE6A /* AlertButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertButton.swift; sourceTree = ""; }; D9C983A12B79D1A600BDBE6A /* RequestPositionButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestPositionButton.swift; sourceTree = ""; }; - DD007BAD2AA4E91200F5FA12 /* MyInfoEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyInfoEntityExtension.swift; sourceTree = ""; }; - DD007BAF2AA5981000F5FA12 /* NodeInfoEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeInfoEntityExtension.swift; sourceTree = ""; }; DD05296F2B77F454008E44CD /* MeshtasticDataModelV 26.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 26.xcdatamodel"; sourceTree = ""; }; DD0E20F92B87090400F2D100 /* atak.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = atak.pb.swift; sourceTree = ""; }; DD0E20FA2B87090400F2D100 /* clientonly.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = clientonly.pb.swift; sourceTree = ""; }; @@ -318,8 +336,6 @@ DD4975A42B147BA90026544E /* AmbientLightingConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AmbientLightingConfig.swift; sourceTree = ""; }; DD4A911D2708C65400501B7E /* AppSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettings.swift; sourceTree = ""; }; DD4F23CC28779A3C001D37CB /* EnvironmentMetricsLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnvironmentMetricsLog.swift; sourceTree = ""; }; - DD5394FD276BA0EF00AD86B1 /* PositionEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PositionEntityExtension.swift; sourceTree = ""; }; - DD58C5F12919AD3C00D5BEFB /* ChannelEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelEntityExtension.swift; sourceTree = ""; }; DD5D0A9A2931AD6B00F7EA61 /* MeshtasticDataModelV2.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV2.xcdatamodel; sourceTree = ""; }; DD5D0A9B2931B9F200F7EA61 /* EthernetModes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EthernetModes.swift; sourceTree = ""; }; DD5E51CC2986643400D21B61 /* MeshtasticDataModelV7.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV7.xcdatamodel; sourceTree = ""; }; @@ -370,7 +386,6 @@ DD964FBC296E6B01007C176F /* EmojiOnlyTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiOnlyTextField.swift; sourceTree = ""; }; DD964FBE296E76EF007C176F /* WaypointFormMapKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaypointFormMapKit.swift; sourceTree = ""; }; DD964FC029724F6D007C176F /* MeshtasticDataModelV6.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV6.xcdatamodel; sourceTree = ""; }; - DD964FC1297272AE007C176F /* WaypointEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaypointEntityExtension.swift; sourceTree = ""; }; DD964FC32974767D007C176F /* MapViewFitExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapViewFitExtension.swift; sourceTree = ""; }; DD964FC52975DBFD007C176F /* QueryCoreData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueryCoreData.swift; sourceTree = ""; }; DD9681A22BBB22BE00FD2C47 /* MeshtasticDataModelV32.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV32.xcdatamodel; sourceTree = ""; }; @@ -386,7 +401,6 @@ DDA9515D2BC6F56F00CEA535 /* IndoorAirQuality.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IndoorAirQuality.swift; sourceTree = ""; }; DDAB580B2B0D913500147258 /* MeshtasticDataModelV20.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV20.xcdatamodel; sourceTree = ""; }; DDAB580C2B0DAA9E00147258 /* Routes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Routes.swift; sourceTree = ""; }; - DDAB580E2B0DAFBC00147258 /* LocationEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationEntityExtension.swift; sourceTree = ""; }; DDAD49EC2AFB39DC00B4425D /* MeshMap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshMap.swift; sourceTree = ""; }; DDAF8C5226EB1DF10058C060 /* BLEManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BLEManager.swift; sourceTree = ""; }; DDB234392B5CA9B000DA6FB1 /* MeshtasticDataModelV 24.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 24.xcdatamodel"; sourceTree = ""; }; @@ -437,7 +451,6 @@ DDD43FE22A78C8900083A3E9 /* MqttClientProxyManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MqttClientProxyManager.swift; sourceTree = ""; }; DDD6EEAE29BC024700383354 /* Firmware.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Firmware.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 = ""; }; DDDB263E2AABEE20003AFCB7 /* NodeList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeList.swift; sourceTree = ""; }; DDDB26412AABF655003AFCB7 /* NodeListItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeListItem.swift; sourceTree = ""; }; DDDB26432AAC0206003AFCB7 /* NodeDetail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeDetail.swift; sourceTree = ""; }; @@ -475,7 +488,6 @@ DDDEE5E229DBE43E00A8E078 /* MeshtasticDataModelV11.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV11.xcdatamodel; sourceTree = ""; }; DDE0F7C4295F77B700B8AAB3 /* AppSettingsEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettingsEnums.swift; sourceTree = ""; }; DDE5B4032B2279A700FCDD05 /* TraceRouteLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TraceRouteLog.swift; sourceTree = ""; }; - DDE5B4052B227E3200FCDD05 /* TraceRouteEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TraceRouteEntityExtension.swift; sourceTree = ""; }; DDE9659B2B1C3B6A00531070 /* RouteRecorder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RouteRecorder.swift; sourceTree = ""; }; DDEE03EC29544A1000FCAD57 /* MeshtasticDataModelV4.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV4.xcdatamodel; sourceTree = ""; }; DDF45C332BC1A48E005ED5F2 /* MQTTIcon.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MQTTIcon.swift; sourceTree = ""; }; @@ -559,15 +571,21 @@ DD007BB12AA59B9A00F5FA12 /* CoreData */ = { isa = PBXGroup; children = ( - DD58C5F12919AD3C00D5BEFB /* ChannelEntityExtension.swift */, - 6DEDA55B2A9592F900321D2E /* MessageEntityExtension.swift */, - DD007BAD2AA4E91200F5FA12 /* MyInfoEntityExtension.swift */, - DD007BAF2AA5981000F5FA12 /* NodeInfoEntityExtension.swift */, - DD5394FD276BA0EF00AD86B1 /* PositionEntityExtension.swift */, - DDD9E4E3284B208E003777C5 /* UserEntityExtension.swift */, - DD964FC1297272AE007C176F /* WaypointEntityExtension.swift */, - DDAB580E2B0DAFBC00147258 /* LocationEntityExtension.swift */, - DDE5B4052B227E3200FCDD05 /* TraceRouteEntityExtension.swift */, + 25A9789B2C12BD3E0003AAE7 /* ChannelEntityExtension.swift */, + 25A978A42C12BD3F0003AAE7 /* DeviceMetadataEntityExtension.swift */, + 25A978A12C12BD3E0003AAE7 /* ExternalNotificationConfigEntityExtension.swift */, + 25A9789E2C12BD3E0003AAE7 /* LocationEntityExtension.swift */, + 25A9789C2C12BD3E0003AAE7 /* MessageEntityExtension.swift */, + 25A978A02C12BD3E0003AAE7 /* MQTTConfigEntityExtension.swift */, + 25A978A62C12BD3F0003AAE7 /* MyInfoEntityExtension.swift */, + 25A978A82C12BD3F0003AAE7 /* NodeInfoEntityExtension.swift */, + 25A978A72C12BD3F0003AAE7 /* PositionEntityExtension.swift */, + 25A9789F2C12BD3E0003AAE7 /* RangeTestConfigEntityExtension.swift */, + 25A978A32C12BD3E0003AAE7 /* SerialConfigEntityExtension.swift */, + 25A978A22C12BD3E0003AAE7 /* StoreForwardConfigEntityExtension.swift */, + 25A9789D2C12BD3E0003AAE7 /* TraceRouteEntityExtension.swift */, + 25A978A52C12BD3F0003AAE7 /* UserEntityExtension.swift */, + 25A9789A2C12BD3E0003AAE7 /* WaypointEntityExtension.swift */, ); path = CoreData; sourceTree = ""; @@ -1175,7 +1193,6 @@ DD5E5203298EE33B00D21B61 /* config.pb.swift in Sources */, DD798B072915928D005217CD /* ChannelMessageList.swift in Sources */, DDC2E1A726CEB3400042C5E4 /* LocationHelper.swift in Sources */, - DD5394FE276BA0EF00AD86B1 /* PositionEntityExtension.swift in Sources */, DD77093D2AA1AFA3007A8BF0 /* ChannelTips.swift in Sources */, DD913639270DFF4C00D7ACF3 /* LocalNotificationManager.swift in Sources */, DDDB444C29F8AAA600EE2349 /* Color.swift in Sources */, @@ -1193,12 +1210,15 @@ DDDB445229F8ACF900EE2349 /* Date.swift in Sources */, DDC4D568275499A500A4208E /* Persistence.swift in Sources */, DDD6EEAF29BC024700383354 /* Firmware.swift in Sources */, + 25A978B72C12BD3F0003AAE7 /* NodeInfoEntityExtension.swift in Sources */, DD77093B2AA1ABB8007A8BF0 /* BluetoothTips.swift in Sources */, + 25A978B22C12BD3F0003AAE7 /* SerialConfigEntityExtension.swift in Sources */, DD90860E26F69BAE00DC5189 /* NodeMap.swift in Sources */, D9C9839D2B79CFD700BDBE6A /* TextMessageSize.swift in Sources */, DDC94FCE29CF55310082EA6E /* RtttlConfig.swift in Sources */, DD964FBD296E6B01007C176F /* EmojiOnlyTextField.swift in Sources */, DD8169FF272476C700F4AB02 /* LogDocument.swift in Sources */, + 25A978B32C12BD3F0003AAE7 /* DeviceMetadataEntityExtension.swift in Sources */, DDC94FC129CE063B0082EA6E /* BatteryLevel.swift in Sources */, DD354FD92BD96A0B0061A25F /* IAQScale.swift in Sources */, DDDB445429F8AD1600EE2349 /* Data.swift in Sources */, @@ -1206,6 +1226,8 @@ DD2AD8A8296D2DF9001FF0E7 /* MapViewSwiftUI.swift in Sources */, DD5E5213298EE33B00D21B61 /* deviceonly.pb.swift in Sources */, DDE5B4042B2279A700FCDD05 /* TraceRouteLog.swift in Sources */, + 25A978B02C12BD3F0003AAE7 /* ExternalNotificationConfigEntityExtension.swift in Sources */, + 25A978AA2C12BD3F0003AAE7 /* ChannelEntityExtension.swift in Sources */, DD5E5208298EE33B00D21B61 /* rtttl.pb.swift in Sources */, DD6193792863875F00E59241 /* SerialConfig.swift in Sources */, DDDB263F2AABEE20003AFCB7 /* NodeList.swift in Sources */, @@ -1219,7 +1241,7 @@ DD5E5209298EE33B00D21B61 /* module_config.pb.swift in Sources */, DD2160AF28C5552500C17253 /* MQTTConfig.swift in Sources */, DD13AA492AB73BF400BA0C98 /* PositionPopover.swift in Sources */, - 6DEDA55C2A9592F900321D2E /* MessageEntityExtension.swift in Sources */, + 25A978B52C12BD3F0003AAE7 /* MyInfoEntityExtension.swift in Sources */, DDDB444229F8A88700EE2349 /* Double.swift in Sources */, DD5E520F298EE33B00D21B61 /* cannedmessages.pb.swift in Sources */, DDF45C342BC1A48E005ED5F2 /* MQTTIcon.swift in Sources */, @@ -1233,17 +1255,18 @@ DDDB444A29F8AA3A00EE2349 /* CLLocationCoordinate2D.swift in Sources */, DD41582628582E9B009B0E59 /* DeviceConfig.swift in Sources */, DDF45C372BC46A5A005ED5F2 /* TimeZone.swift in Sources */, - DD007BAE2AA4E91200F5FA12 /* MyInfoEntityExtension.swift in Sources */, DD33DB622B3D27C7003E1EA0 /* FirmwareApi.swift in Sources */, DD3CC6B528E33FD100FA9159 /* ShareChannels.swift in Sources */, + 25A978AC2C12BD3F0003AAE7 /* TraceRouteEntityExtension.swift in Sources */, DD1BF2F92776FE2E008C8D2F /* UserMessageList.swift in Sources */, DD5E5207298EE33B00D21B61 /* connection_status.pb.swift in Sources */, DD3CC6C228EB9D4900FA9159 /* UpdateCoreData.swift in Sources */, DDE0F7C5295F77B700B8AAB3 /* AppSettingsEnums.swift in Sources */, + 25A978AD2C12BD3F0003AAE7 /* LocationEntityExtension.swift in Sources */, DDB6ABE628B1406100384BA1 /* LoraConfigEnums.swift in Sources */, DDB8F4142A9EE5F000230ECE /* ChannelList.swift in Sources */, DDD43FE32A78C8900083A3E9 /* MqttClientProxyManager.swift in Sources */, - DD007BB02AA5981000F5FA12 /* NodeInfoEntityExtension.swift in Sources */, + 25A978B12C12BD3F0003AAE7 /* StoreForwardConfigEntityExtension.swift in Sources */, DDDB26422AABF655003AFCB7 /* NodeListItem.swift in Sources */, DD0E20FC2B87090400F2D100 /* atak.pb.swift in Sources */, DDDB444629F8A96500EE2349 /* Character.swift in Sources */, @@ -1251,7 +1274,7 @@ DDB6ABDB28B0AC6000384BA1 /* DistanceText.swift in Sources */, DD5E520D298EE33B00D21B61 /* storeforward.pb.swift in Sources */, DD94B7402ACCE3BE00DCD1D1 /* MapSettingsForm.swift in Sources */, - DD964FC2297272AE007C176F /* WaypointEntityExtension.swift in Sources */, + 25A978AF2C12BD3F0003AAE7 /* MQTTConfigEntityExtension.swift in Sources */, 6DA39D8E2A92DC52007E311C /* MeshtasticAppDelegate.swift in Sources */, D93068DB2B81C85E0066FBC8 /* PowerConfig.swift in Sources */, D93068D32B8129510066FBC8 /* MessageContextMenuItems.swift in Sources */, @@ -1263,7 +1286,6 @@ DDC2E18F26CE25FE0042C5E4 /* ContentView.swift in Sources */, DD2553572855B02500E55709 /* LoRaConfig.swift in Sources */, DDB6ABD928B0A4BA00384BA1 /* BluetoothModes.swift in Sources */, - DDD9E4E4284B208E003777C5 /* UserEntityExtension.swift in Sources */, DD2553592855B52700E55709 /* PositionConfig.swift in Sources */, DD97E96828EFE9A00056DDA4 /* About.swift in Sources */, DDDB444029F79AB000EE2349 /* UserDefaults.swift in Sources */, @@ -1279,8 +1301,11 @@ DD964FC62975DBFD007C176F /* QueryCoreData.swift in Sources */, DDB75A112A059258006ED576 /* Url.swift in Sources */, DDAD49ED2AFB39DC00B4425D /* MeshMap.swift in Sources */, + 25A978A92C12BD3F0003AAE7 /* WaypointEntityExtension.swift in Sources */, DD8169FB271F1F3A00F4AB02 /* MeshLog.swift in Sources */, + 25A978AB2C12BD3F0003AAE7 /* MessageEntityExtension.swift in Sources */, DD1B8F402B35E2F10022AABC /* GPSStatus.swift in Sources */, + 25A978B42C12BD3F0003AAE7 /* UserEntityExtension.swift in Sources */, DD8ED9C52898D51F00B3B0AB /* NetworkConfig.swift in Sources */, DDC3B274283F411B00AC321C /* LastHeardText.swift in Sources */, DDDE5A1029AFE69700490C6C /* MeshActivityAttributes.swift in Sources */, @@ -1319,7 +1344,6 @@ DDAB580D2B0DAA9E00147258 /* Routes.swift in Sources */, C9697F9D279336B700250207 /* LocalMBTileOverlay.swift in Sources */, D93068D52B812B700066FBC8 /* MessageDestination.swift in Sources */, - DD58C5F22919AD3C00D5BEFB /* ChannelEntityExtension.swift in Sources */, DDA9515E2BC6F56F00CEA535 /* IndoorAirQuality.swift in Sources */, DDDB444E29F8AB0E00EE2349 /* Int.swift in Sources */, DD0F791B28713C8A00A6FDAD /* AdminMessageList.swift in Sources */, @@ -1334,15 +1358,15 @@ DDF6B2482A9AEBF500BA6931 /* StoreForwardConfig.swift in Sources */, DD8169F9271F1A6100F4AB02 /* MeshLogger.swift in Sources */, DD93800B2BA3F968008BEC06 /* NodeMapContent.swift in Sources */, + 25A978AE2C12BD3F0003AAE7 /* RangeTestConfigEntityExtension.swift in Sources */, DD41582A28585C32009B0E59 /* RangeTestConfig.swift in Sources */, DD1925B728CDA5A400720036 /* CannedMessagesConfigEnums.swift in Sources */, DDDB444429F8A8DD00EE2349 /* Float.swift in Sources */, - DDAB580F2B0DAFBC00147258 /* LocationEntityExtension.swift in Sources */, + 25A978B62C12BD3F0003AAE7 /* PositionEntityExtension.swift in Sources */, B3E905B12B71F7F300654D07 /* TextMessageField.swift in Sources */, DD5E5211298EE33B00D21B61 /* remote_hardware.pb.swift in Sources */, D93068D72B8146690066FBC8 /* MessageText.swift in Sources */, DD5E5204298EE33B00D21B61 /* xmodem.pb.swift in Sources */, - DDE5B4062B227E3200FCDD05 /* TraceRouteEntityExtension.swift in Sources */, DDC2E15826CE248E0042C5E4 /* MeshtasticApp.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Meshtastic/Extensions/CoreData/ChannelEntityExtension.swift b/Meshtastic/Extensions/CoreData/ChannelEntityExtension.swift index 2568943a..84ef3ece 100644 --- a/Meshtastic/Extensions/CoreData/ChannelEntityExtension.swift +++ b/Meshtastic/Extensions/CoreData/ChannelEntityExtension.swift @@ -5,9 +5,50 @@ // Copyright(c) Garth Vander Houwen 11/7/22. // import Foundation +import CoreData extension ChannelEntity { + convenience init( + context: NSManagedObjectContext, + id: Int32, + index: Int32, + uplinkEnabled: Bool, + downlinkEnabled: Bool, + name: String?, + role: Int32, + psk: Data, + positionPrecision: Int32 + ) { + self.init(context: context) + self.id = id + self.index = index + self.uplinkEnabled = uplinkEnabled + self.downlinkEnabled = downlinkEnabled + self.name = name + self.role = role + self.psk = psk + self.positionPrecision = positionPrecision + } + + convenience init( + context: NSManagedObjectContext, + channel: Channel + ) { + self.init(context: context) + self.id = Int32(channel.index) + self.index = Int32(channel.index) + self.uplinkEnabled = channel.settings.uplinkEnabled + self.downlinkEnabled = channel.settings.downlinkEnabled + self.name = channel.settings.name + self.role = Int32(channel.role.rawValue) + self.psk = channel.settings.psk + if channel.settings.hasModuleSettings { + self.positionPrecision = Int32(truncatingIfNeeded: channel.settings.moduleSettings.positionPrecision) + self.mute = channel.settings.moduleSettings.isClientMuted + } + } + var allPrivateMessages: [MessageEntity] { self.value(forKey: "allPrivateMessages") as? [MessageEntity] ?? [MessageEntity]() diff --git a/Meshtastic/Extensions/CoreData/DeviceMetadataEntityExtension.swift b/Meshtastic/Extensions/CoreData/DeviceMetadataEntityExtension.swift new file mode 100644 index 00000000..cc0bd37f --- /dev/null +++ b/Meshtastic/Extensions/CoreData/DeviceMetadataEntityExtension.swift @@ -0,0 +1,24 @@ +import Foundation +import CoreData + +extension DeviceMetadataEntity { + convenience init( + context: NSManagedObjectContext, + metadata: DeviceMetadata + ) { + self.init(context: context) + self.time = Date() + self.deviceStateVersion = Int32(metadata.deviceStateVersion) + self.canShutdown = metadata.canShutdown + self.hasWifi = metadata.hasWifi_p + self.hasBluetooth = metadata.hasBluetooth_p + self.hasEthernet = metadata.hasEthernet_p + self.role = Int32(metadata.role.rawValue) + self.positionFlags = Int32(metadata.positionFlags) + // Swift does strings weird, this does work to get the version without the github hash + let lastDotIndex = metadata.firmwareVersion.lastIndex(of: ".") + var version = metadata.firmwareVersion[...(lastDotIndex ?? String.Index(utf16Offset: 6, in: metadata.firmwareVersion))] + version = version.dropLast() + self.firmwareVersion = String(version) + } +} diff --git a/Meshtastic/Extensions/CoreData/ExternalNotificationConfigEntityExtension.swift b/Meshtastic/Extensions/CoreData/ExternalNotificationConfigEntityExtension.swift new file mode 100644 index 00000000..72632308 --- /dev/null +++ b/Meshtastic/Extensions/CoreData/ExternalNotificationConfigEntityExtension.swift @@ -0,0 +1,43 @@ +import CoreData + +extension ExternalNotificationConfigEntity { + convenience init( + context: NSManagedObjectContext, + config: ModuleConfig.ExternalNotificationConfig + ) { + self.init() + self.enabled = config.enabled + self.usePWM = config.usePwm + self.alertBell = config.alertBell + self.alertBellBuzzer = config.alertBellBuzzer + self.alertBellVibra = config.alertBellVibra + self.alertMessage = config.alertMessage + self.alertMessageBuzzer = config.alertMessageBuzzer + self.alertMessageVibra = config.alertMessageVibra + self.active = config.active + self.output = Int32(config.output) + self.outputBuzzer = Int32(config.outputBuzzer) + self.outputVibra = Int32(config.outputVibra) + self.outputMilliseconds = Int32(config.outputMs) + self.nagTimeout = Int32(config.nagTimeout) + self.useI2SAsBuzzer = config.useI2SAsBuzzer + } + + func update(with config: ModuleConfig.ExternalNotificationConfig) { + enabled = config.enabled + usePWM = config.usePwm + alertBell = config.alertBell + alertBellBuzzer = config.alertBellBuzzer + alertBellVibra = config.alertBellVibra + alertMessage = config.alertMessage + alertMessageBuzzer = config.alertMessageBuzzer + alertMessageVibra = config.alertMessageVibra + active = config.active + output = Int32(config.output) + outputBuzzer = Int32(config.outputBuzzer) + outputVibra = Int32(config.outputVibra) + outputMilliseconds = Int32(config.outputMs) + nagTimeout = Int32(config.nagTimeout) + useI2SAsBuzzer = config.useI2SAsBuzzer + } +} diff --git a/Meshtastic/Extensions/CoreData/LocationEntityExtension.swift b/Meshtastic/Extensions/CoreData/LocationEntityExtension.swift index 5b35f620..d66ec017 100644 --- a/Meshtastic/Extensions/CoreData/LocationEntityExtension.swift +++ b/Meshtastic/Extensions/CoreData/LocationEntityExtension.swift @@ -11,6 +11,22 @@ import MapKit import SwiftUI extension LocationEntity { + + convenience init( + context: NSManagedObjectContext, + route: RouteEntity, + id: Int32, + location: CLLocation + ) { + self.init(context: context) + self.routeLocation = route + self.id = id + self.altitude = Int32(location.altitude) + self.heading = Int32(location.course) + self.speed = Int32(location.speed) + self.latitudeI = Int32(location.coordinate.latitude * 1e7) + self.longitudeI = Int32(location.coordinate.longitude * 1e7) + } var latitude: Double? { diff --git a/Meshtastic/Extensions/CoreData/MQTTConfigEntityExtension.swift b/Meshtastic/Extensions/CoreData/MQTTConfigEntityExtension.swift new file mode 100644 index 00000000..32744db5 --- /dev/null +++ b/Meshtastic/Extensions/CoreData/MQTTConfigEntityExtension.swift @@ -0,0 +1,37 @@ +import CoreData + +extension MQTTConfigEntity { + convenience init( + context: NSManagedObjectContext, + config: ModuleConfig.MQTTConfig + ) { + self.init(context: context) + self.enabled = config.enabled + self.proxyToClientEnabled = config.proxyToClientEnabled + self.address = config.address + self.username = config.username + self.password = config.password + self.root = config.root + self.encryptionEnabled = config.encryptionEnabled + self.jsonEnabled = config.jsonEnabled + self.tlsEnabled = config.tlsEnabled + self.mapReportingEnabled = config.mapReportingEnabled + self.mapPositionPrecision = Int32(config.mapReportSettings.positionPrecision) + self.mapPublishIntervalSecs = Int32(config.mapReportSettings.publishIntervalSecs) + } + + func update(with config: ModuleConfig.MQTTConfig) { + enabled = config.enabled + proxyToClientEnabled = config.proxyToClientEnabled + address = config.address + username = config.username + password = config.password + root = config.root + encryptionEnabled = config.encryptionEnabled + jsonEnabled = config.jsonEnabled + tlsEnabled = config.tlsEnabled + mapReportingEnabled = config.mapReportingEnabled + mapPositionPrecision = Int32(config.mapReportSettings.positionPrecision) + mapPublishIntervalSecs = Int32(config.mapReportSettings.publishIntervalSecs) + } +} diff --git a/Meshtastic/Extensions/CoreData/NodeInfoEntityExtension.swift b/Meshtastic/Extensions/CoreData/NodeInfoEntityExtension.swift index 9258b424..935e35a8 100644 --- a/Meshtastic/Extensions/CoreData/NodeInfoEntityExtension.swift +++ b/Meshtastic/Extensions/CoreData/NodeInfoEntityExtension.swift @@ -9,6 +9,15 @@ import Foundation import CoreData extension NodeInfoEntity { + convenience init( + context: NSManagedObjectContext, + num: Int + ) { + self.init(context: context) + self.id = Int64(num) + self.num = Int64(num) + self.user = UserEntity(context: context, num: num) + } var hasPositions: Bool { return positions?.count ?? 0 > 0 @@ -47,20 +56,3 @@ extension NodeInfoEntity { return false } } - -public func createNodeInfo(num: Int64, context: NSManagedObjectContext) -> NodeInfoEntity { - - let newNode = NodeInfoEntity(context: context) - newNode.id = Int64(num) - newNode.num = Int64(num) - let newUser = UserEntity(context: context) - newUser.num = Int64(num) - let userId = String(format: "%2X", num) - newUser.userId = "!\(userId)" - let last4 = String(userId.suffix(4)) - newUser.longName = "Meshtastic \(last4)" - newUser.shortName = last4 - newUser.hwModel = "UNSET" - newNode.user = newUser - return newNode -} diff --git a/Meshtastic/Extensions/CoreData/RangeTestConfigEntityExtension.swift b/Meshtastic/Extensions/CoreData/RangeTestConfigEntityExtension.swift new file mode 100644 index 00000000..9be459be --- /dev/null +++ b/Meshtastic/Extensions/CoreData/RangeTestConfigEntityExtension.swift @@ -0,0 +1,19 @@ +import CoreData + +extension RangeTestConfigEntity { + convenience init( + context: NSManagedObjectContext, + config: ModuleConfig.RangeTestConfig + ) { + self.init(context: context) + self.sender = Int32(config.sender) + self.enabled = config.enabled + self.save = config.save + } + + func update(with config: ModuleConfig.RangeTestConfig) { + sender = Int32(config.sender) + enabled = config.enabled + save = config.save + } +} diff --git a/Meshtastic/Extensions/CoreData/SerialConfigEntityExtension.swift b/Meshtastic/Extensions/CoreData/SerialConfigEntityExtension.swift new file mode 100644 index 00000000..ac114909 --- /dev/null +++ b/Meshtastic/Extensions/CoreData/SerialConfigEntityExtension.swift @@ -0,0 +1,27 @@ +import CoreData + +extension SerialConfigEntity { + convenience init( + context: NSManagedObjectContext, + config: ModuleConfig.SerialConfig + ) { + self.init(context: context) + self.enabled = config.enabled + self.echo = config.echo + self.rxd = Int32(config.rxd) + self.txd = Int32(config.txd) + self.baudRate = Int32(config.baud.rawValue) + self.timeout = Int32(config.timeout) + self.mode = Int32(config.mode.rawValue) + } + + func update(with config: ModuleConfig.SerialConfig) { + enabled = config.enabled + echo = config.echo + rxd = Int32(config.rxd) + txd = Int32(config.txd) + baudRate = Int32(config.baud.rawValue) + timeout = Int32(config.timeout) + mode = Int32(config.mode.rawValue) + } +} diff --git a/Meshtastic/Extensions/CoreData/StoreForwardConfigEntityExtension.swift b/Meshtastic/Extensions/CoreData/StoreForwardConfigEntityExtension.swift new file mode 100644 index 00000000..2951bb8a --- /dev/null +++ b/Meshtastic/Extensions/CoreData/StoreForwardConfigEntityExtension.swift @@ -0,0 +1,23 @@ +import CoreData + +extension StoreForwardConfigEntity { + convenience init( + context: NSManagedObjectContext, + config: ModuleConfig.StoreForwardConfig + ) { + self.init(context: context) + self.enabled = config.enabled + self.heartbeat = config.heartbeat + self.records = Int32(config.records) + self.historyReturnMax = Int32(config.historyReturnMax) + self.historyReturnWindow = Int32(config.historyReturnWindow) + } + + func update(with config: ModuleConfig.StoreForwardConfig) { + enabled = config.enabled + heartbeat = config.heartbeat + records = Int32(config.records) + historyReturnMax = Int32(config.historyReturnMax) + historyReturnWindow = Int32(config.historyReturnWindow) + } +} diff --git a/Meshtastic/Extensions/CoreData/WaypointEntityExtension.swift b/Meshtastic/Extensions/CoreData/WaypointEntityExtension.swift index b17ccecf..96b5ed5f 100644 --- a/Meshtastic/Extensions/CoreData/WaypointEntityExtension.swift +++ b/Meshtastic/Extensions/CoreData/WaypointEntityExtension.swift @@ -10,6 +10,19 @@ import MapKit import SwiftUI extension WaypointEntity { + + convenience init( + context: NSManagedObjectContext, + coordinate: CLLocationCoordinate2D + ) { + self.init(context: context) + self.id = 0 + self.name = "Waypoint Pin" + self.expire = Date.now.addingTimeInterval(60 * 480) + self.latitudeI = Int32(coordinate.latitude * 1e7) + self.longitudeI = Int32(coordinate.longitude * 1e7) + self.expire = Date.now.addingTimeInterval(60 * 480) + } static func allWaypointssFetchRequest() -> NSFetchRequest { let request: NSFetchRequest = WaypointEntity.fetchRequest() diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index fb2c5f29..660021d8 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -685,8 +685,8 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate var hopNodes: [TraceRouteHopEntity] = [] for node in routingMessage.route { var hopNode = getNodeInfo(id: Int64(node), context: context!) - if hopNode == nil && hopNode?.num ?? 0 > 0 { - hopNode = createNodeInfo(num: Int64(node), context: context!) + if hopNode == nil { + hopNode = NodeInfoEntity(context: context!, num: Int(node)) } let traceRouteHop = TraceRouteHopEntity(context: context!) traceRouteHop.time = Date() diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index 645b52b0..bb4c2e2e 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -156,30 +156,29 @@ func channelPacket (channel: Channel, fromNum: Int64, context: NSManagedObjectCo return } if fetchedMyInfo.count == 1 { - let newChannel = ChannelEntity(context: context) - newChannel.id = Int32(channel.index) - newChannel.index = Int32(channel.index) - newChannel.uplinkEnabled = channel.settings.uplinkEnabled - newChannel.downlinkEnabled = channel.settings.downlinkEnabled - newChannel.name = channel.settings.name - newChannel.role = Int32(channel.role.rawValue) - newChannel.psk = channel.settings.psk - if channel.settings.hasModuleSettings { - newChannel.positionPrecision = Int32(truncatingIfNeeded: channel.settings.moduleSettings.positionPrecision) - newChannel.mute = channel.settings.moduleSettings.isClientMuted - } - guard let mutableChannels = fetchedMyInfo[0].channels!.mutableCopy() as? NSMutableOrderedSet else { + let newChannel = ChannelEntity( + context: context, + channel: channel + ) + guard let mutableChannels = fetchedMyInfo.first?.channels?.mutableCopy() as? NSMutableOrderedSet else { return } - if let oldChannel = mutableChannels.first(where: {($0 as AnyObject).index == newChannel.index }) as? ChannelEntity { - let index = mutableChannels.index(of: oldChannel as Any) + let oldChannel = mutableChannels.first(where: { + if let channel = $0 as? ChannelEntity { + return channel.index == newChannel.index + } + return false + }) as? ChannelEntity + + if let oldChannel { + let index = mutableChannels.index(of: oldChannel) mutableChannels.replaceObject(at: index, with: newChannel) } else { mutableChannels.add(newChannel) } - fetchedMyInfo[0].channels = mutableChannels.copy() as? NSOrderedSet + fetchedMyInfo.first?.channels = mutableChannels.copy() as? NSOrderedSet if newChannel.name?.lowercased() == "admin" { - fetchedMyInfo[0].adminIndex = newChannel.index + fetchedMyInfo.first?.adminIndex = newChannel.index } context.refresh(newChannel, mergeChanges: true) do { @@ -212,29 +211,18 @@ func deviceMetadataPacket (metadata: DeviceMetadata, fromNum: Int64, context: NS guard let fetchedNode = try context.fetch(fetchedNodeRequest) as? [NodeInfoEntity] else { return } - let newMetadata = DeviceMetadataEntity(context: context) - newMetadata.time = Date() - newMetadata.deviceStateVersion = Int32(metadata.deviceStateVersion) - newMetadata.canShutdown = metadata.canShutdown - newMetadata.hasWifi = metadata.hasWifi_p - newMetadata.hasBluetooth = metadata.hasBluetooth_p - newMetadata.hasEthernet = metadata.hasEthernet_p - newMetadata.role = Int32(metadata.role.rawValue) - newMetadata.positionFlags = Int32(metadata.positionFlags) - // Swift does strings weird, this does work to get the version without the github hash - let lastDotIndex = metadata.firmwareVersion.lastIndex(of: ".") - var version = metadata.firmwareVersion[...(lastDotIndex ?? String.Index(utf16Offset: 6, in: metadata.firmwareVersion))] - version = version.dropLast() - newMetadata.firmwareVersion = String(version) - if fetchedNode.count > 0 { - fetchedNode[0].metadata = newMetadata - } else { - - if fromNum > 0 { - let newNode = createNodeInfo(num: Int64(fromNum), context: context) - newNode.metadata = newMetadata - } + let newMetadata = DeviceMetadataEntity( + context: context, + metadata: metadata + ) + + if let node = fetchedNode.first { + node.metadata = newMetadata + } else if fromNum > 0 { + let node = NodeInfoEntity(context: context, num: Int(fromNum)) + node.metadata = newMetadata } + do { try context.save() } catch { diff --git a/Meshtastic/Persistence/UpdateCoreData.swift b/Meshtastic/Persistence/UpdateCoreData.swift index 9472fd05..cb36435c 100644 --- a/Meshtastic/Persistence/UpdateCoreData.swift +++ b/Meshtastic/Persistence/UpdateCoreData.swift @@ -960,56 +960,28 @@ func upsertExternalNotificationModuleConfigPacket(config: Meshtastic.ModuleConfi guard let fetchedNode = try context.fetch(fetchNodeInfoRequest) as? [NodeInfoEntity] else { return } + + guard let node = fetchedNode.first else { + return Logger.data.error("No Nodes found in local database matching node number \(nodeNum) unable to save External Notification Module Config") + } + // Found a node, save External Notificaitone Config - if !fetchedNode.isEmpty { - - if fetchedNode[0].externalNotificationConfig == nil { - let newExternalNotificationConfig = ExternalNotificationConfigEntity(context: context) - newExternalNotificationConfig.enabled = config.enabled - newExternalNotificationConfig.usePWM = config.usePwm - newExternalNotificationConfig.alertBell = config.alertBell - newExternalNotificationConfig.alertBellBuzzer = config.alertBellBuzzer - newExternalNotificationConfig.alertBellVibra = config.alertBellVibra - newExternalNotificationConfig.alertMessage = config.alertMessage - newExternalNotificationConfig.alertMessageBuzzer = config.alertMessageBuzzer - newExternalNotificationConfig.alertMessageVibra = config.alertMessageVibra - newExternalNotificationConfig.active = config.active - newExternalNotificationConfig.output = Int32(config.output) - newExternalNotificationConfig.outputBuzzer = Int32(config.outputBuzzer) - newExternalNotificationConfig.outputVibra = Int32(config.outputVibra) - newExternalNotificationConfig.outputMilliseconds = Int32(config.outputMs) - newExternalNotificationConfig.nagTimeout = Int32(config.nagTimeout) - newExternalNotificationConfig.useI2SAsBuzzer = config.useI2SAsBuzzer - fetchedNode[0].externalNotificationConfig = newExternalNotificationConfig - - } else { - fetchedNode[0].externalNotificationConfig?.enabled = config.enabled - fetchedNode[0].externalNotificationConfig?.usePWM = config.usePwm - fetchedNode[0].externalNotificationConfig?.alertBell = config.alertBell - fetchedNode[0].externalNotificationConfig?.alertBellBuzzer = config.alertBellBuzzer - fetchedNode[0].externalNotificationConfig?.alertBellVibra = config.alertBellVibra - fetchedNode[0].externalNotificationConfig?.alertMessage = config.alertMessage - fetchedNode[0].externalNotificationConfig?.alertMessageBuzzer = config.alertMessageBuzzer - fetchedNode[0].externalNotificationConfig?.alertMessageVibra = config.alertMessageVibra - fetchedNode[0].externalNotificationConfig?.active = config.active - fetchedNode[0].externalNotificationConfig?.output = Int32(config.output) - fetchedNode[0].externalNotificationConfig?.outputBuzzer = Int32(config.outputBuzzer) - fetchedNode[0].externalNotificationConfig?.outputVibra = Int32(config.outputVibra) - fetchedNode[0].externalNotificationConfig?.outputMilliseconds = Int32(config.outputMs) - fetchedNode[0].externalNotificationConfig?.nagTimeout = Int32(config.nagTimeout) - fetchedNode[0].externalNotificationConfig?.useI2SAsBuzzer = config.useI2SAsBuzzer - } - - do { - try context.save() - Logger.data.info("💾 Updated External Notification Module Config for node number: \(String(nodeNum))") - } catch { - context.rollback() - let nsError = error as NSError - Logger.data.error("Error Updating Core Data ExternalNotificationConfigEntity: \(nsError)") - } + if let externalNotificationConfig = node.externalNotificationConfig { + externalNotificationConfig.update(with: config) } else { - Logger.data.error("No Nodes found in local database matching node number \(nodeNum) unable to save External Notification Module Config") + node.externalNotificationConfig = ExternalNotificationConfigEntity( + context: context, + config: config + ) + } + + do { + try context.save() + Logger.data.info("💾 Updated External Notification Module Config for node number: \(String(nodeNum))") + } catch { + context.rollback() + let nsError = error as NSError + Logger.data.error("Error Updating Core Data ExternalNotificationConfigEntity: \(nsError)") } } catch { let nsError = error as NSError @@ -1114,48 +1086,29 @@ func upsertMqttModuleConfigPacket(config: Meshtastic.ModuleConfig.MQTTConfig, no 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 - newMQTTConfig.proxyToClientEnabled = config.proxyToClientEnabled - newMQTTConfig.address = config.address - newMQTTConfig.username = config.username - newMQTTConfig.password = config.password - newMQTTConfig.root = config.root - newMQTTConfig.encryptionEnabled = config.encryptionEnabled - newMQTTConfig.jsonEnabled = config.jsonEnabled - newMQTTConfig.tlsEnabled = config.tlsEnabled - newMQTTConfig.mapReportingEnabled = config.mapReportingEnabled - newMQTTConfig.mapPositionPrecision = Int32(config.mapReportSettings.positionPrecision) - newMQTTConfig.mapPublishIntervalSecs = Int32(config.mapReportSettings.publishIntervalSecs) - fetchedNode[0].mqttConfig = newMQTTConfig - } else { - fetchedNode[0].mqttConfig?.enabled = config.enabled - fetchedNode[0].mqttConfig?.proxyToClientEnabled = config.proxyToClientEnabled - fetchedNode[0].mqttConfig?.address = config.address - fetchedNode[0].mqttConfig?.username = config.username - fetchedNode[0].mqttConfig?.password = config.password - fetchedNode[0].mqttConfig?.root = config.root - fetchedNode[0].mqttConfig?.encryptionEnabled = config.encryptionEnabled - fetchedNode[0].mqttConfig?.jsonEnabled = config.jsonEnabled - fetchedNode[0].mqttConfig?.tlsEnabled = config.tlsEnabled - fetchedNode[0].mqttConfig?.mapReportingEnabled = config.mapReportingEnabled - fetchedNode[0].mqttConfig?.mapPositionPrecision = Int32(config.mapReportSettings.positionPrecision) - fetchedNode[0].mqttConfig?.mapPublishIntervalSecs = Int32(config.mapReportSettings.publishIntervalSecs) - } - do { - try context.save() - Logger.data.info("💾 Updated MQTT Config for node number: \(String(nodeNum))") - } catch { - context.rollback() - let nsError = error as NSError - Logger.data.error("Error Updating Core Data MQTTConfigEntity: \(nsError)") - } - } else { + + guard let node = fetchedNode.first else { Logger.data.error("No Nodes found in local database matching node number \(nodeNum) unable to save MQTT Module Config") + return + } + // Found a node, save MQTT Config + + if let mqttConfig = node.mqttConfig { + mqttConfig.update(with: config) + } else { + node.mqttConfig = MQTTConfigEntity( + context: context, + config: config + ) + } + + do { + try context.save() + Logger.data.info("💾 Updated MQTT Config for node number: \(String(nodeNum))") + } catch { + context.rollback() + let nsError = error as NSError + Logger.data.error("Error Updating Core Data MQTTConfigEntity: \(nsError)") } } catch { let nsError = error as NSError @@ -1177,32 +1130,28 @@ func upsertRangeTestModuleConfigPacket(config: Meshtastic.ModuleConfig.RangeTest return } // Found a node, save Device Config - if !fetchedNode.isEmpty { - if fetchedNode[0].rangeTestConfig == nil { - let newRangeTestConfig = RangeTestConfigEntity(context: context) - newRangeTestConfig.sender = Int32(config.sender) - newRangeTestConfig.enabled = config.enabled - newRangeTestConfig.save = config.save - fetchedNode[0].rangeTestConfig = newRangeTestConfig + if let node = fetchedNode.first { + if let rangeTestConfig = node.rangeTestConfig { + rangeTestConfig.update(with: config) } else { - fetchedNode[0].rangeTestConfig?.sender = Int32(config.sender) - fetchedNode[0].rangeTestConfig?.enabled = config.enabled - fetchedNode[0].rangeTestConfig?.save = config.save + node.rangeTestConfig = RangeTestConfigEntity( + context: context, + config: config + ) } + do { try context.save() Logger.data.info("💾 Updated Range Test Config for node number: \(String(nodeNum))") } catch { context.rollback() - let nsError = error as NSError - Logger.data.error("Error Updating Core Data RangeTestConfigEntity: \(nsError)") + Logger.data.error("Error Updating Core Data RangeTestConfigEntity: \(error.localizedDescription)") } } else { Logger.data.error("No Nodes found in local database matching node number \(nodeNum) unable to save Range Test Module Config") } } catch { - let nsError = error as NSError - Logger.data.error("Fetching node for core data RangeTestConfigEntity failed: \(nsError)") + Logger.data.error("Fetching node for core data RangeTestConfigEntity failed: \(error.localizedDescription)") } } @@ -1215,55 +1164,32 @@ func upsertSerialModuleConfigPacket(config: Meshtastic.ModuleConfig.SerialConfig fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum)) do { - guard let fetchedNode = try context.fetch(fetchNodeInfoRequest) as? [NodeInfoEntity] else { return } - + guard let node = fetchedNode.first else { + return Logger.data.error("No Nodes found in local database matching node number \(nodeNum) unable to save Serial Module Config") + } + // 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 - newSerialConfig.rxd = Int32(config.rxd) - newSerialConfig.txd = Int32(config.txd) - newSerialConfig.baudRate = Int32(config.baud.rawValue) - 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 - fetchedNode[0].serialConfig?.rxd = Int32(config.rxd) - fetchedNode[0].serialConfig?.txd = Int32(config.txd) - fetchedNode[0].serialConfig?.baudRate = Int32(config.baud.rawValue) - fetchedNode[0].serialConfig?.timeout = Int32(config.timeout) - fetchedNode[0].serialConfig?.mode = Int32(config.mode.rawValue) - } - - do { - try context.save() - Logger.data.info("💾 Updated Serial Module Config for node number: \(String(nodeNum))") - - } catch { - - context.rollback() - - let nsError = error as NSError - Logger.data.error("Error Updating Core Data SerialConfigEntity: \(nsError)") - } - + if let serialConfig = node.serialConfig { + node.serialConfig?.update(with: config) } else { - - Logger.data.error("No Nodes found in local database matching node number \(nodeNum) unable to save Serial Module Config") + node.serialConfig = SerialConfigEntity( + context: context, + config: config + ) } + do { + try context.save() + Logger.data.info("💾 Updated Serial Module Config for node number: \(String(nodeNum))") + } catch { + context.rollback() + let nsError = error as NSError + Logger.data.error("Error Updating Core Data SerialConfigEntity: \(nsError)") + } } catch { - let nsError = error as NSError Logger.data.error("Fetching node for core data SerialConfigEntity failed: \(nsError)") } @@ -1282,36 +1208,26 @@ func upsertStoreForwardModuleConfigPacket(config: Meshtastic.ModuleConfig.StoreF guard let fetchedNode = try context.fetch(fetchNodeInfoRequest) as? [NodeInfoEntity] else { return } - // Found a node, save Store & Forward Sensor Config - if !fetchedNode.isEmpty { - - if fetchedNode[0].storeForwardConfig == nil { - - let newConfig = StoreForwardConfigEntity(context: context) - newConfig.enabled = config.enabled - newConfig.heartbeat = config.heartbeat - newConfig.records = Int32(config.records) - newConfig.historyReturnMax = Int32(config.historyReturnMax) - newConfig.historyReturnWindow = Int32(config.historyReturnWindow) - fetchedNode[0].storeForwardConfig = newConfig - - } else { - fetchedNode[0].storeForwardConfig?.enabled = config.enabled - fetchedNode[0].storeForwardConfig?.heartbeat = config.heartbeat - fetchedNode[0].storeForwardConfig?.records = Int32(config.records) - fetchedNode[0].storeForwardConfig?.historyReturnMax = Int32(config.historyReturnMax) - fetchedNode[0].storeForwardConfig?.historyReturnWindow = Int32(config.historyReturnWindow) - } - do { - try context.save() - Logger.data.info("💾 Updated Store & Forward Module Config for node number: \(String(nodeNum))") - } catch { - context.rollback() - let nsError = error as NSError - Logger.data.error("Error Updating Core Data StoreForwardConfigEntity: \(nsError)") - } - } else { + guard let node = fetchedNode.first else { Logger.data.error("No Nodes found in local database matching node number \(nodeNum) unable to save Store & Forward Module Config") + return + } + // Found a node, save Store & Forward Sensor Config + if let storeForwardConfig = node.storeForwardConfig { + storeForwardConfig.update(with: config) + } else { + node.storeForwardConfig = StoreForwardConfigEntity( + context: context, + config: config + ) + } + do { + try context.save() + Logger.data.info("💾 Updated Store & Forward Module Config for node number: \(String(nodeNum))") + } catch { + context.rollback() + let nsError = error as NSError + Logger.data.error("Error Updating Core Data StoreForwardConfigEntity: \(nsError)") } } catch { let nsError = error as NSError diff --git a/Meshtastic/Views/MapKitMap/Custom/MapViewSwiftUI.swift b/Meshtastic/Views/MapKitMap/Custom/MapViewSwiftUI.swift index 5c7291a5..78d66cf2 100644 --- a/Meshtastic/Views/MapKitMap/Custom/MapViewSwiftUI.swift +++ b/Meshtastic/Views/MapKitMap/Custom/MapViewSwiftUI.swift @@ -183,7 +183,11 @@ struct MapViewSwiftUI: UIViewRepresentable { } var lineIndex = 0 for position in latest { - let nodePositions = positions.filter { $0.nodeCoordinate != nil && $0.nodePosition?.num ?? 0 == position.nodePosition?.num ?? -1 } + let nodePositions = positions.filter { + $0.nodeCoordinate != nil && + $0.nodePosition != nil && + $0.nodePosition?.num == position.nodePosition?.num + } let lineCoords = nodePositions.compactMap({(position) -> CLLocationCoordinate2D in return position.nodeCoordinate ?? LocationHelper.DefaultLocation }) diff --git a/Meshtastic/Views/Nodes/MeshMap.swift b/Meshtastic/Views/Nodes/MeshMap.swift index caa3f5d6..eff15a39 100644 --- a/Meshtastic/Views/Nodes/MeshMap.swift +++ b/Meshtastic/Views/Nodes/MeshMap.swift @@ -78,13 +78,10 @@ struct MeshMap: View { } newWaypointCoord = coordinate - editingWaypoint = WaypointEntity(context: context) - editingWaypoint!.name = "Waypoint Pin" - editingWaypoint!.expire = Date.now.addingTimeInterval(60 * 480) - editingWaypoint!.latitudeI = Int32((newWaypointCoord?.latitude ?? 0) * 1e7) - editingWaypoint!.longitudeI = Int32((newWaypointCoord?.longitude ?? 0) * 1e7) - editingWaypoint!.expire = Date.now.addingTimeInterval(60 * 480) - editingWaypoint!.id = 0 + editingWaypoint = WaypointEntity( + context: context, + coordinate: coordinate + ) Logger.services.debug("Long press occured at Lat: \(coordinate.latitude) Long: \(coordinate.longitude)") default: return } @@ -133,7 +130,7 @@ struct MeshMap: View { // //position = .camera(MapCamera(centerCoordinate: waypoint.coordinate, distance: 1000, heading: 0, pitch: 60)) // } // } - .onChange(of: (selectedMapLayer)) { newMapLayer in + .onChange(of: selectedMapLayer) { newMapLayer in switch selectedMapLayer { case .standard: UserDefaults.mapLayer = newMapLayer diff --git a/Meshtastic/Views/Settings/Channels.swift b/Meshtastic/Views/Settings/Channels.swift index 689a009e..18a9890d 100644 --- a/Meshtastic/Views/Settings/Channels.swift +++ b/Meshtastic/Views/Settings/Channels.swift @@ -259,18 +259,18 @@ struct Channels: View { uplink = false downlink = false hasChanges = true - - let newChannel = ChannelEntity(context: context) - newChannel.id = channelIndex - newChannel.index = channelIndex - newChannel.uplinkEnabled = uplink - newChannel.downlinkEnabled = downlink - newChannel.name = channelName - newChannel.role = Int32(channelRole) - newChannel.psk = Data(base64Encoded: channelKey) ?? Data() - newChannel.positionPrecision = Int32(positionPrecision) - selectedChannel = newChannel - + + selectedChannel = ChannelEntity( + context: context, + id: channelIndex, + index: channelIndex, + uplinkEnabled: uplink, + downlinkEnabled: downlink, + name: channelName, + role: Int32(channelRole), + psk: Data(base64Encoded: channelKey) ?? Data(), + positionPrecision: Int32(positionPrecision) + ) } label: { Label("Add Channel", systemImage: "plus.square") } diff --git a/Meshtastic/Views/Settings/RouteRecorder.swift b/Meshtastic/Views/Settings/RouteRecorder.swift index c19dbd9f..38685a23 100644 --- a/Meshtastic/Views/Settings/RouteRecorder.swift +++ b/Meshtastic/Views/Settings/RouteRecorder.swift @@ -284,29 +284,22 @@ struct RouteRecorder: View { .onDisappear(perform: { UIApplication.shared.isIdleTimerDisabled = false }) - .onChange(of: locationsHandler.locationsArray.last) { newLoc in - if locationsHandler.isRecording { - if let loc = newLoc { - if recording != nil { - let locationEntity = LocationEntity(context: context) - locationEntity.routeLocation = recording - locationEntity.id = Int32(locationsHandler.count) - locationEntity.altitude = Int32(loc.altitude) - locationEntity.heading = Int32(loc.course) - locationEntity.speed = Int32(loc.speed) - locationEntity.latitudeI = Int32(loc.coordinate.latitude * 1e7) - locationEntity.longitudeI = Int32(loc.coordinate.longitude * 1e7) - do { - try context.save() - Logger.data.info("💾 Saved a new route location") - // logger.info("💾 Updated Canned Messages Messages For: \(fetchedNode[0].num)") - } catch { - context.rollback() - let nsError = error as NSError - Logger.data.error("Error Saving LocationEntity from the Route Recorder \(nsError)") - } - } - } + .onChange(of: locationsHandler.locationsArray.last) { location in + guard locationsHandler.isRecording, let location, let recording else { return } + let locationEntity = LocationEntity( + context: context, + route: recording, + id: Int32(locationsHandler.count), + location: location + ) + + do { + try context.save() + Logger.data.info("💾 Saved a new route location") + } catch { + context.rollback() + let nsError = error as NSError + Logger.data.error("Error Saving LocationEntity from the Route Recorder \(nsError)") } } } diff --git a/Widgets/Info.plist b/Widgets/Info.plist index 0f118fb7..b3dac3d4 100644 --- a/Widgets/Info.plist +++ b/Widgets/Info.plist @@ -2,6 +2,8 @@ + CFBundleVersion + $(CURRENT_PROJECT_VERSION) NSExtension NSExtensionPointIdentifier From 9ca99c7ed30afd0666dcff291795ef0087b3b8cf Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Thu, 6 Jun 2024 22:51:16 -0700 Subject: [PATCH 29/32] Add admin messages category Remove admin message log --- Meshtastic/Extensions/Logger.swift | 6 ++++++ Meshtastic/Helpers/BLEManager.swift | 4 ++-- Meshtastic/Views/Settings/AppLog.swift | 4 ++-- Meshtastic/Views/Settings/Settings.swift | 11 ----------- 4 files changed, 10 insertions(+), 15 deletions(-) diff --git a/Meshtastic/Extensions/Logger.swift b/Meshtastic/Extensions/Logger.swift index 930d47ef..c932d4df 100644 --- a/Meshtastic/Extensions/Logger.swift +++ b/Meshtastic/Extensions/Logger.swift @@ -17,6 +17,9 @@ extension Logger { /// All logs related to the mesh static let mesh = Logger(subsystem: subsystem, category: "🕸️ Mesh") + + /// All admin messages + static let admin = Logger(subsystem: subsystem, category: "🏛 Admin") /// All logs related to services such as network calls, location, etc. static let services = Logger(subsystem: subsystem, category: "🍏 Services") @@ -29,6 +32,9 @@ extension Logger { let store = try OSLogStore(scope: .currentProcessIdentifier) let position = store.position(timeIntervalSinceLatestBoot: 0) + let calendar = Calendar.current + //let dayAgo = calendar.date(byAdding: .day, value: -1, to: Date.now) + //let position = store.position(date: dayAgo!) let predicate = NSPredicate(format: predicateFormat) let entries = try store.getEntries(at: position, matching: predicate) diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index fb2c5f29..aad44104 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -2824,12 +2824,12 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate do { connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse) try context!.save() - Logger.mesh.debug("\(adminDescription)") + Logger.admin.debug("\(adminDescription)") return true } catch { context!.rollback() let nsError = error as NSError - Logger.data.error("Error inserting new core data MessageEntity: \(nsError)") + Logger.admin.error("Error inserting new core data admin MessageEntity: \(nsError)") } } return false diff --git a/Meshtastic/Views/Settings/AppLog.swift b/Meshtastic/Views/Settings/AppLog.swift index ffb83514..4feb0865 100644 --- a/Meshtastic/Views/Settings/AppLog.swift +++ b/Meshtastic/Views/Settings/AppLog.swift @@ -12,7 +12,7 @@ import OSLog struct AppLog: View { @State private var logs: [OSLogEntryLog] = [] - @State private var sortOrder = [KeyPathComparator(\OSLogEntryLog.date)] + @State private var sortOrder = [KeyPathComparator(\OSLogEntryLog.date, order: .forward)] @State private var selection: OSLogEntry.ID? @State private var selectedLog: OSLogEntryLog? @State private var presentingErrorDetails: Bool = false @@ -53,7 +53,7 @@ struct AppLog: View { .width(ideal: 200, max: .infinity) } - .searchable(text: $searchTerm, prompt: "Search") + .searchable(text: $searchTerm, placement: .navigationBarDrawer, prompt: "Search") .disabled(selection != nil) .overlay { if logs.isEmpty { diff --git a/Meshtastic/Views/Settings/Settings.swift b/Meshtastic/Views/Settings/Settings.swift index 45a14230..690ebdaa 100644 --- a/Meshtastic/Views/Settings/Settings.swift +++ b/Meshtastic/Views/Settings/Settings.swift @@ -413,17 +413,6 @@ struct Settings: View { } } .tag(SettingsSidebar.meshLog) - NavigationLink { - let connectedNode = nodes.first(where: { $0.num == preferredNodeNum }) - AdminMessageList(user: connectedNode?.user) - } label: { - Label { - Text("admin.log") - } icon: { - Image(systemName: "building.columns") - } - } - .tag(SettingsSidebar.adminMessageLog) if #available (iOS 17.4, *) { NavigationLink { AppLog() From 91817a72e3df370bca2e3948d334b8d77dec3607 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Thu, 6 Jun 2024 23:40:18 -0700 Subject: [PATCH 30/32] Finsh up the log fonts --- Meshtastic/Views/Helpers/LogDetail.swift | 15 ++++++++++----- Meshtastic/Views/Settings/AppLog.swift | 17 +++++++++++------ 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/Meshtastic/Views/Helpers/LogDetail.swift b/Meshtastic/Views/Helpers/LogDetail.swift index 079d00f2..69cf87a2 100644 --- a/Meshtastic/Views/Helpers/LogDetail.swift +++ b/Meshtastic/Views/Helpers/LogDetail.swift @@ -40,7 +40,8 @@ struct LogDetail: View { .frame(width: 35) } .padding(.bottom, 5) - .listRowSeparator(.visible) + .listSectionSeparator(.hidden, edges: .top) + .listSectionSeparator(.visible, edges: .bottom) /// Subsystem Label { Text("log.subsystem".localized + ":") @@ -76,7 +77,7 @@ struct LogDetail: View { Text(log.category) .font(idiom == .phone ? .callout : .title) } icon: { - Image(systemName: "rectangle.3.group") + Image(systemName: "square.grid.2x2") .symbolRenderingMode(.hierarchical) .font(idiom == .phone ? .callout : .title) .frame(width: 35) @@ -101,6 +102,10 @@ struct LogDetail: View { Label { Text("log.message".localized + ":") .font(idiom == .phone ? .callout : .title) + Text(log.composedMessage) + .textSelection(.enabled) + .font(idiom == .phone ? .callout : .title) + .padding(.bottom, 5) } icon: { Image(systemName: "text.bubble") @@ -109,11 +114,10 @@ struct LogDetail: View { .frame(width: 35) } .listRowSeparator(.hidden) - Text(log.composedMessage) - .font(idiom == .phone ? .callout : .title) - .padding(.bottom, 5) + } .listStyle(.plain) + } Spacer() } @@ -131,6 +135,7 @@ struct LogDetail: View { .padding(.bottom) #endif } + .monospaced() .presentationDetents([.fraction(0.65), .fraction(0.75), .fraction(0.85)]) .presentationDragIndicator(.visible) } diff --git a/Meshtastic/Views/Settings/AppLog.swift b/Meshtastic/Views/Settings/AppLog.swift index 4feb0865..22402ce5 100644 --- a/Meshtastic/Views/Settings/AppLog.swift +++ b/Meshtastic/Views/Settings/AppLog.swift @@ -12,7 +12,7 @@ import OSLog struct AppLog: View { @State private var logs: [OSLogEntryLog] = [] - @State private var sortOrder = [KeyPathComparator(\OSLogEntryLog.date, order: .forward)] + @State private var sortOrder = [KeyPathComparator(\OSLogEntryLog.date, order: .reverse)] @State private var selection: OSLogEntry.ID? @State private var selectedLog: OSLogEntryLog? @State private var presentingErrorDetails: Bool = false @@ -41,18 +41,22 @@ struct AppLog: View { TableColumn("log.time", value: \.date) { value in Text(value.date.formatted(dateFormatStyle)) } - .width(min: 100, max: 125) + .width(min: 125, max: 150) TableColumn("log.category", value: \.category) - .width(min: 100, max: 125) + .width(min: 125, max: 150) TableColumn("log.level") { value in Text(value.level.description) } - .width(min: 50, max: 100) + .width(min: 75, max: 100) } - TableColumn("log.message", value: \.composedMessage) - .width(ideal: 200, max: .infinity) + TableColumn("log.message", value: \.composedMessage) { value in + Text(value.composedMessage) + } + .width(ideal: 200, max: .infinity) + } + .monospaced() .searchable(text: $searchTerm, placement: .navigationBarDrawer, prompt: "Search") .disabled(selection != nil) .overlay { @@ -78,6 +82,7 @@ struct AppLog: View { } .task { logs = await fetchLogs() + logs.sort(using: sortOrder) } .fileExporter( isPresented: $isExporting, From 644305bc5e19a3f4fe44b7c8e07ad728b7fd97e6 Mon Sep 17 00:00:00 2001 From: Blake McAnally Date: Fri, 7 Jun 2024 08:22:47 -0500 Subject: [PATCH 31/32] Pass context to CoreData entity initializer --- .../CoreData/ExternalNotificationConfigEntityExtension.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Meshtastic/Extensions/CoreData/ExternalNotificationConfigEntityExtension.swift b/Meshtastic/Extensions/CoreData/ExternalNotificationConfigEntityExtension.swift index 72632308..6baa3213 100644 --- a/Meshtastic/Extensions/CoreData/ExternalNotificationConfigEntityExtension.swift +++ b/Meshtastic/Extensions/CoreData/ExternalNotificationConfigEntityExtension.swift @@ -5,7 +5,7 @@ extension ExternalNotificationConfigEntity { context: NSManagedObjectContext, config: ModuleConfig.ExternalNotificationConfig ) { - self.init() + self.init(context: context) self.enabled = config.enabled self.usePWM = config.usePwm self.alertBell = config.alertBell From 6e3314aeda8926299043a8690ab1858a5683a09b Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Fri, 7 Jun 2024 08:29:10 -0700 Subject: [PATCH 32/32] Fix widget version --- Meshtastic.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 9a194160..60847f85 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -1625,7 +1625,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.3.10; + MARKETING_VERSION = 2.3.11; OTHER_SWIFT_FLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1659,7 +1659,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.3.10; + MARKETING_VERSION = 2.3.11; OTHER_SWIFT_FLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets; PRODUCT_NAME = "$(TARGET_NAME)";