mirror of
https://github.com/meshtastic/Meshtastic-Apple.git
synced 2026-04-20 22:13:56 +00:00
Merge pull request #662 from meshtastic/2.3.11_Working_Changes
2.3.11 Working Changes
This commit is contained in:
commit
9c8dadddef
59 changed files with 1834 additions and 1422 deletions
|
|
@ -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
|
||||
|
|
@ -7,10 +7,24 @@
|
|||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
25183D462C0A6D97001E31D5 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25183D452C0A6D97001E31D5 /* Logger.swift */; };
|
||||
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 */; };
|
||||
|
|
@ -156,7 +164,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 */; };
|
||||
|
|
@ -167,10 +174,10 @@
|
|||
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 */; };
|
||||
DDD28D3C2C0EC51D0063CFA3 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD28D3B2C0EC51D0063CFA3 /* Logger.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 */; };
|
||||
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,24 +210,18 @@
|
|||
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 */; };
|
||||
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 */; };
|
||||
/* 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 */;
|
||||
|
|
@ -245,10 +246,25 @@
|
|||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
25183D452C0A6D97001E31D5 /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = "<group>"; };
|
||||
258EE1262C0E833D0025A5FB /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
|
||||
25A978572C124FA70003AAE7 /* NodeInfoExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NodeInfoExtensions.swift; sourceTree = "<group>"; };
|
||||
25A9789A2C12BD3E0003AAE7 /* WaypointEntityExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WaypointEntityExtension.swift; sourceTree = "<group>"; };
|
||||
25A9789B2C12BD3E0003AAE7 /* ChannelEntityExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChannelEntityExtension.swift; sourceTree = "<group>"; };
|
||||
25A9789C2C12BD3E0003AAE7 /* MessageEntityExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageEntityExtension.swift; sourceTree = "<group>"; };
|
||||
25A9789D2C12BD3E0003AAE7 /* TraceRouteEntityExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TraceRouteEntityExtension.swift; sourceTree = "<group>"; };
|
||||
25A9789E2C12BD3E0003AAE7 /* LocationEntityExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocationEntityExtension.swift; sourceTree = "<group>"; };
|
||||
25A9789F2C12BD3E0003AAE7 /* RangeTestConfigEntityExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RangeTestConfigEntityExtension.swift; sourceTree = "<group>"; };
|
||||
25A978A02C12BD3E0003AAE7 /* MQTTConfigEntityExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MQTTConfigEntityExtension.swift; sourceTree = "<group>"; };
|
||||
25A978A12C12BD3E0003AAE7 /* ExternalNotificationConfigEntityExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExternalNotificationConfigEntityExtension.swift; sourceTree = "<group>"; };
|
||||
25A978A22C12BD3E0003AAE7 /* StoreForwardConfigEntityExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StoreForwardConfigEntityExtension.swift; sourceTree = "<group>"; };
|
||||
25A978A32C12BD3E0003AAE7 /* SerialConfigEntityExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SerialConfigEntityExtension.swift; sourceTree = "<group>"; };
|
||||
25A978A42C12BD3F0003AAE7 /* DeviceMetadataEntityExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceMetadataEntityExtension.swift; sourceTree = "<group>"; };
|
||||
25A978A52C12BD3F0003AAE7 /* UserEntityExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserEntityExtension.swift; sourceTree = "<group>"; };
|
||||
25A978A62C12BD3F0003AAE7 /* MyInfoEntityExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MyInfoEntityExtension.swift; sourceTree = "<group>"; };
|
||||
25A978A72C12BD3F0003AAE7 /* PositionEntityExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PositionEntityExtension.swift; sourceTree = "<group>"; };
|
||||
25A978A82C12BD3F0003AAE7 /* NodeInfoEntityExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NodeInfoEntityExtension.swift; sourceTree = "<group>"; };
|
||||
6DA39D8D2A92DC52007E311C /* MeshtasticAppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshtasticAppDelegate.swift; sourceTree = "<group>"; };
|
||||
6DEDA5592A957B8E00321D2E /* DetectionSensorLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetectionSensorLog.swift; sourceTree = "<group>"; };
|
||||
6DEDA55B2A9592F900321D2E /* MessageEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageEntityExtension.swift; sourceTree = "<group>"; };
|
||||
A65FA974296876BF00A97686 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
||||
B399E8A32B6F486400E4488E /* RetryButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RetryButton.swift; sourceTree = "<group>"; };
|
||||
B3E905B02B71F7F300654D07 /* TextMessageField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextMessageField.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -265,8 +281,6 @@
|
|||
D9C9839C2B79CFD700BDBE6A /* TextMessageSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextMessageSize.swift; sourceTree = "<group>"; };
|
||||
D9C9839F2B79D0E800BDBE6A /* AlertButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertButton.swift; sourceTree = "<group>"; };
|
||||
D9C983A12B79D1A600BDBE6A /* RequestPositionButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestPositionButton.swift; sourceTree = "<group>"; };
|
||||
DD007BAD2AA4E91200F5FA12 /* MyInfoEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyInfoEntityExtension.swift; sourceTree = "<group>"; };
|
||||
DD007BAF2AA5981000F5FA12 /* NodeInfoEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeInfoEntityExtension.swift; sourceTree = "<group>"; };
|
||||
DD05296F2B77F454008E44CD /* MeshtasticDataModelV 26.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 26.xcdatamodel"; sourceTree = "<group>"; };
|
||||
DD0E20F92B87090400F2D100 /* atak.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = atak.pb.swift; sourceTree = "<group>"; };
|
||||
DD0E20FA2B87090400F2D100 /* clientonly.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = clientonly.pb.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -322,8 +336,6 @@
|
|||
DD4975A42B147BA90026544E /* AmbientLightingConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AmbientLightingConfig.swift; sourceTree = "<group>"; };
|
||||
DD4A911D2708C65400501B7E /* AppSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettings.swift; sourceTree = "<group>"; };
|
||||
DD4F23CC28779A3C001D37CB /* EnvironmentMetricsLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnvironmentMetricsLog.swift; sourceTree = "<group>"; };
|
||||
DD5394FD276BA0EF00AD86B1 /* PositionEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PositionEntityExtension.swift; sourceTree = "<group>"; };
|
||||
DD58C5F12919AD3C00D5BEFB /* ChannelEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelEntityExtension.swift; sourceTree = "<group>"; };
|
||||
DD5D0A9A2931AD6B00F7EA61 /* MeshtasticDataModelV2.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV2.xcdatamodel; sourceTree = "<group>"; };
|
||||
DD5D0A9B2931B9F200F7EA61 /* EthernetModes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EthernetModes.swift; sourceTree = "<group>"; };
|
||||
DD5E51CC2986643400D21B61 /* MeshtasticDataModelV7.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV7.xcdatamodel; sourceTree = "<group>"; };
|
||||
|
|
@ -374,7 +386,6 @@
|
|||
DD964FBC296E6B01007C176F /* EmojiOnlyTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiOnlyTextField.swift; sourceTree = "<group>"; };
|
||||
DD964FBE296E76EF007C176F /* WaypointFormMapKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaypointFormMapKit.swift; sourceTree = "<group>"; };
|
||||
DD964FC029724F6D007C176F /* MeshtasticDataModelV6.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV6.xcdatamodel; sourceTree = "<group>"; };
|
||||
DD964FC1297272AE007C176F /* WaypointEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaypointEntityExtension.swift; sourceTree = "<group>"; };
|
||||
DD964FC32974767D007C176F /* MapViewFitExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapViewFitExtension.swift; sourceTree = "<group>"; };
|
||||
DD964FC52975DBFD007C176F /* QueryCoreData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueryCoreData.swift; sourceTree = "<group>"; };
|
||||
DD9681A22BBB22BE00FD2C47 /* MeshtasticDataModelV32.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV32.xcdatamodel; sourceTree = "<group>"; };
|
||||
|
|
@ -390,7 +401,6 @@
|
|||
DDA9515D2BC6F56F00CEA535 /* IndoorAirQuality.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IndoorAirQuality.swift; sourceTree = "<group>"; };
|
||||
DDAB580B2B0D913500147258 /* MeshtasticDataModelV20.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV20.xcdatamodel; sourceTree = "<group>"; };
|
||||
DDAB580C2B0DAA9E00147258 /* Routes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Routes.swift; sourceTree = "<group>"; };
|
||||
DDAB580E2B0DAFBC00147258 /* LocationEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationEntityExtension.swift; sourceTree = "<group>"; };
|
||||
DDAD49EC2AFB39DC00B4425D /* MeshMap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshMap.swift; sourceTree = "<group>"; };
|
||||
DDAF8C5226EB1DF10058C060 /* BLEManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BLEManager.swift; sourceTree = "<group>"; };
|
||||
DDB234392B5CA9B000DA6FB1 /* MeshtasticDataModelV 24.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 24.xcdatamodel"; sourceTree = "<group>"; };
|
||||
|
|
@ -422,10 +432,6 @@
|
|||
DDC2E15B26CE248F0042C5E4 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = ../Assets.xcassets; sourceTree = "<group>"; };
|
||||
DDC2E15E26CE248F0042C5E4 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
|
||||
DDC2E16526CE248F0042C5E4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
DDC2E17026CE248F0042C5E4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
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 = "<group>"; };
|
||||
DDC2E17B26CE248F0042C5E4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
DDC2E18E26CE25FE0042C5E4 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
|
||||
DDC2E1A626CEB3400042C5E4 /* LocationHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationHelper.swift; sourceTree = "<group>"; };
|
||||
DDC3B273283F411B00AC321C /* LastHeardText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LastHeardText.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -441,11 +447,10 @@
|
|||
DDCE4E2B2869F92900BE9F8F /* UserConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserConfig.swift; sourceTree = "<group>"; };
|
||||
DDD28D362C0CCCD10063CFA3 /* pt-PT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-PT"; path = "pt-PT.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
||||
DDD28D372C0CD2670063CFA3 /* MeshtasticDataModelV 37.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 37.xcdatamodel"; sourceTree = "<group>"; };
|
||||
DDD3BBD4292D763200D609B3 /* MeshtasticTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MeshtasticTests.swift; sourceTree = "<group>"; };
|
||||
DDD28D3B2C0EC51D0063CFA3 /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = "<group>"; };
|
||||
DDD43FE22A78C8900083A3E9 /* MqttClientProxyManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MqttClientProxyManager.swift; sourceTree = "<group>"; };
|
||||
DDD6EEAE29BC024700383354 /* Firmware.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Firmware.swift; sourceTree = "<group>"; };
|
||||
DDD94A4F2845C8F5004A87A0 /* DateTimeText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateTimeText.swift; sourceTree = "<group>"; };
|
||||
DDD9E4E3284B208E003777C5 /* UserEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserEntityExtension.swift; sourceTree = "<group>"; };
|
||||
DDDB263E2AABEE20003AFCB7 /* NodeList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeList.swift; sourceTree = "<group>"; };
|
||||
DDDB26412AABF655003AFCB7 /* NodeListItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeListItem.swift; sourceTree = "<group>"; };
|
||||
DDDB26432AAC0206003AFCB7 /* NodeDetail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeDetail.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -483,7 +488,6 @@
|
|||
DDDEE5E229DBE43E00A8E078 /* MeshtasticDataModelV11.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV11.xcdatamodel; sourceTree = "<group>"; };
|
||||
DDE0F7C4295F77B700B8AAB3 /* AppSettingsEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettingsEnums.swift; sourceTree = "<group>"; };
|
||||
DDE5B4032B2279A700FCDD05 /* TraceRouteLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TraceRouteLog.swift; sourceTree = "<group>"; };
|
||||
DDE5B4052B227E3200FCDD05 /* TraceRouteEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TraceRouteEntityExtension.swift; sourceTree = "<group>"; };
|
||||
DDE9659B2B1C3B6A00531070 /* RouteRecorder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RouteRecorder.swift; sourceTree = "<group>"; };
|
||||
DDEE03EC29544A1000FCAD57 /* MeshtasticDataModelV4.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV4.xcdatamodel; sourceTree = "<group>"; };
|
||||
DDF45C332BC1A48E005ED5F2 /* MQTTIcon.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MQTTIcon.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -493,6 +497,8 @@
|
|||
DDF6B2462A9AEB9E00BA6931 /* MeshtasticDataModelV17.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV17.xcdatamodel; sourceTree = "<group>"; };
|
||||
DDF6B2472A9AEBF500BA6931 /* StoreForwardConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreForwardConfig.swift; sourceTree = "<group>"; };
|
||||
DDF6B24B2A9C2FC800BA6931 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
DDF8E1F32C10125B0019C87E /* AppLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLog.swift; sourceTree = "<group>"; };
|
||||
DDF8E1F82C115CCE0019C87E /* LogDetail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogDetail.swift; sourceTree = "<group>"; };
|
||||
DDF924C926FBB953009FE055 /* ConnectedDevice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectedDevice.swift; sourceTree = "<group>"; };
|
||||
DDFEB3BA29900C1200EE7472 /* CurrentConditionsCompact.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentConditionsCompact.swift; sourceTree = "<group>"; };
|
||||
DDFFA7462B3A7F3C004730DB /* Bundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bundle.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -509,13 +515,6 @@
|
|||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
DDC2E17226CE248F0042C5E4 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
DDDE59F129AF163D00490C6C /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
|
|
@ -528,6 +527,14 @@
|
|||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
25A978582C124FA70003AAE7 /* Protobufs */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
25A978572C124FA70003AAE7 /* NodeInfoExtensions.swift */,
|
||||
);
|
||||
path = Protobufs;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C9483F6B2773016700998F6B /* MapKitMap */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
|
@ -564,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 = "<group>";
|
||||
|
|
@ -609,6 +622,7 @@
|
|||
DD93800C2BA74CE3008BEC06 /* Channels */,
|
||||
DD97E96728EFE9A00056DDA4 /* About.swift */,
|
||||
DD0F791A28713C8A00A6FDAD /* AdminMessageList.swift */,
|
||||
DDF8E1F32C10125B0019C87E /* AppLog.swift */,
|
||||
DD4A911D2708C65400501B7E /* AppSettings.swift */,
|
||||
DDAB580C2B0DAA9E00147258 /* Routes.swift */,
|
||||
DDE9659B2B1C3B6A00531070 /* RouteRecorder.swift */,
|
||||
|
|
@ -794,12 +808,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 +824,6 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
DDC2E15426CE248E0042C5E4 /* Meshtastic.app */,
|
||||
DDC2E17526CE248F0042C5E4 /* MeshtasticUITests.xctest */,
|
||||
DDDE59F429AF163D00490C6C /* WidgetsExtension.appex */,
|
||||
);
|
||||
name = Products;
|
||||
|
|
@ -847,24 +859,6 @@
|
|||
path = "Preview Content";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DDC2E16D26CE248F0042C5E4 /* MeshtasticTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DDD3BBD4292D763200D609B3 /* MeshtasticTests.swift */,
|
||||
DDC2E17026CE248F0042C5E4 /* Info.plist */,
|
||||
);
|
||||
path = MeshtasticTests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DDC2E17826CE248F0042C5E4 /* MeshtasticUITests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DDC2E17926CE248F0042C5E4 /* MeshtasticUITests.swift */,
|
||||
DDC2E17B26CE248F0042C5E4 /* Info.plist */,
|
||||
);
|
||||
path = MeshtasticUITests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DDC2E18726CE24E40042C5E4 /* Views */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
|
@ -930,6 +924,7 @@
|
|||
DDB75A1D2A0B0CD0006ED576 /* LoRaSignalStrengthIndicator.swift */,
|
||||
DDB75A202A12B954006ED576 /* LoRaSignalStrength.swift */,
|
||||
DDB75A222A13CDA9006ED576 /* BatteryLevelCompact.swift */,
|
||||
DDF8E1F82C115CCE0019C87E /* LogDetail.swift */,
|
||||
);
|
||||
path = Helpers;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -947,7 +942,6 @@
|
|||
DD964FBC296E6B01007C176F /* EmojiOnlyTextField.swift */,
|
||||
DDDB443C29F6592F00EE2349 /* NetworkManager.swift */,
|
||||
DD3619142B1EF9F900C41C8C /* LocationsHandler.swift */,
|
||||
25183D452C0A6D97001E31D5 /* Logger.swift */,
|
||||
);
|
||||
path = Helpers;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -985,24 +979,26 @@
|
|||
DDDB443E29F79A9400EE2349 /* Extensions */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
25A978582C124FA70003AAE7 /* Protobufs */,
|
||||
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 = "<group>";
|
||||
|
|
@ -1037,11 +1033,11 @@
|
|||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = DDC2E17E26CE248F0042C5E4 /* Build configuration list for PBXNativeTarget "Meshtastic" */;
|
||||
buildPhases = (
|
||||
BB450974275599CE00509624 /* ShellScript */,
|
||||
DDC2E15026CE248E0042C5E4 /* Sources */,
|
||||
DDC2E15126CE248E0042C5E4 /* Frameworks */,
|
||||
DDC2E15226CE248E0042C5E4 /* Resources */,
|
||||
DDDE5A0829AF163F00490C6C /* Embed Foundation Extensions */,
|
||||
DDF8E1F52C10C84D0019C87E /* ShellScript */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
|
|
@ -1058,24 +1054,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" */;
|
||||
|
|
@ -1107,10 +1085,6 @@
|
|||
CreatedOnToolsVersion = 12.5.1;
|
||||
LastSwiftMigration = 1340;
|
||||
};
|
||||
DDC2E17426CE248F0042C5E4 = {
|
||||
CreatedOnToolsVersion = 12.5.1;
|
||||
TestTargetID = DDC2E15326CE248E0042C5E4;
|
||||
};
|
||||
DDDE59F329AF163D00490C6C = {
|
||||
CreatedOnToolsVersion = 14.2;
|
||||
};
|
||||
|
|
@ -1137,13 +1111,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 +1138,6 @@
|
|||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
DDC2E17326CE248F0042C5E4 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
DDDE59F229AF163D00490C6C /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
|
|
@ -1182,10 +1149,10 @@
|
|||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
BB450974275599CE00509624 /* ShellScript */ = {
|
||||
DDF8E1F52C10C84D0019C87E /* ShellScript */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
alwaysOutOfDate = 1;
|
||||
buildActionMask = 2147483647;
|
||||
buildActionMask = 8;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
|
|
@ -1196,9 +1163,9 @@
|
|||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
runOnlyForDeploymentPostprocessing = 1;
|
||||
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";
|
||||
shellScript = "defaults write com.apple.dt.Xcode IDESkipPackagePluginFingerprintValidatation -bool YES\n";
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
|
|
@ -1226,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 */,
|
||||
|
|
@ -1244,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 */,
|
||||
|
|
@ -1257,9 +1226,12 @@
|
|||
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 */,
|
||||
DDF8E1F92C115CCE0019C87E /* LogDetail.swift in Sources */,
|
||||
DDA0B6B2294CDC55001356EC /* Channels.swift in Sources */,
|
||||
DDE9659C2B1C3B6A00531070 /* RouteRecorder.swift in Sources */,
|
||||
B399E8A42B6F486400E4488E /* RetryButton.swift in Sources */,
|
||||
|
|
@ -1269,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 */,
|
||||
|
|
@ -1283,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 */,
|
||||
|
|
@ -1301,20 +1274,18 @@
|
|||
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 */,
|
||||
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 */,
|
||||
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 */,
|
||||
|
|
@ -1326,11 +1297,15 @@
|
|||
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 */,
|
||||
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 */,
|
||||
|
|
@ -1350,6 +1325,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 */,
|
||||
|
|
@ -1368,12 +1344,12 @@
|
|||
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 */,
|
||||
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 */,
|
||||
|
|
@ -1382,27 +1358,19 @@
|
|||
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;
|
||||
};
|
||||
DDC2E17126CE248F0042C5E4 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
DDC2E17A26CE248F0042C5E4 /* MeshtasticUITests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
DDDE59F029AF163D00490C6C /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
|
|
@ -1417,11 +1385,6 @@
|
|||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
DDC2E17726CE248F0042C5E4 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = DDC2E15326CE248E0042C5E4 /* Meshtastic */;
|
||||
targetProxy = DDC2E17626CE248F0042C5E4 /* PBXContainerItemProxy */;
|
||||
};
|
||||
DDDE5A0229AF163E00490C6C /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
platformFilter = ios;
|
||||
|
|
@ -1507,6 +1470,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";
|
||||
|
|
@ -1564,6 +1528,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";
|
||||
|
|
@ -1583,7 +1548,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;
|
||||
|
|
@ -1595,7 +1560,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;
|
||||
|
|
@ -1617,7 +1582,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;
|
||||
|
|
@ -1629,7 +1594,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;
|
||||
|
|
@ -1639,48 +1604,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 = {
|
||||
|
|
@ -1702,7 +1625,8 @@
|
|||
"@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)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
|
|
@ -1735,7 +1659,8 @@
|
|||
"@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)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
|
|
@ -1768,15 +1693,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 +1705,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";
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"originHash" : "e9855e3a299c14a10f11ee0b8f29e4170b09548533939361223a0f50e7caac8c",
|
||||
"originHash" : "32ea1ad7873163554215310b8eea710c94c63f855b5b01c0b790e7b537747ceb",
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "cocoamqtt",
|
||||
|
|
@ -10,6 +10,24 @@
|
|||
"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",
|
||||
|
|
@ -19,6 +37,15 @@
|
|||
"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",
|
||||
|
|
@ -37,6 +64,15 @@
|
|||
"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",
|
||||
|
|
@ -45,6 +81,51 @@
|
|||
"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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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]()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
import CoreData
|
||||
|
||||
extension ExternalNotificationConfigEntity {
|
||||
convenience init(
|
||||
context: NSManagedObjectContext,
|
||||
config: ModuleConfig.ExternalNotificationConfig
|
||||
) {
|
||||
self.init(context: context)
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
@ -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? {
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<PositionEntity> {
|
||||
let request: NSFetchRequest<PositionEntity> = PositionEntity.fetchRequest()
|
||||
request.fetchLimit = 1000
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<WaypointEntity> {
|
||||
let request: NSFetchRequest<WaypointEntity> = WaypointEntity.fetchRequest()
|
||||
|
|
|
|||
68
Meshtastic/Extensions/Logger.swift
Normal file
68
Meshtastic/Extensions/Logger.swift
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
//
|
||||
// Logger.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Copyright(c) Garth Vander Houwen 6/3/24.
|
||||
//
|
||||
|
||||
import OSLog
|
||||
|
||||
extension Logger {
|
||||
|
||||
/// The logger's subsystem.
|
||||
private static var subsystem = Bundle.main.bundleIdentifier!
|
||||
|
||||
/// All logs related to data such as decoding error, parsing issues, etc.
|
||||
static let data = Logger(subsystem: subsystem, category: "🗄️ Data")
|
||||
|
||||
/// 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")
|
||||
|
||||
/// All logs related to tracking and analytics.
|
||||
static let statistics = Logger(subsystem: subsystem, category: "📊 Stats")
|
||||
|
||||
/// Fetch from the logstore
|
||||
static public func fetch(predicateFormat: String) async throws -> [OSLogEntryLog] {
|
||||
|
||||
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)
|
||||
|
||||
var logs: [OSLogEntryLog] = []
|
||||
for entry in entries {
|
||||
|
||||
try Task.checkCancellation()
|
||||
|
||||
if let log = entry as? OSLogEntryLog {
|
||||
logs.append(log)
|
||||
}
|
||||
}
|
||||
|
||||
if logs.isEmpty { logs = [] }
|
||||
return logs
|
||||
}
|
||||
}
|
||||
|
||||
extension OSLogEntryLog.Level {
|
||||
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"
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Meshtastic/Extensions/Protobufs/NodeInfoExtensions.swift
Normal file
11
Meshtastic/Extensions/Protobufs/NodeInfoExtensions.swift
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import Foundation
|
||||
|
||||
extension NodeInfo {
|
||||
var isValidPosition: Bool {
|
||||
hasPosition &&
|
||||
position.longitudeI != 0 &&
|
||||
position.latitudeI != 0 &&
|
||||
position.latitudeI != 373346000 &&
|
||||
position.longitudeI != -1220090000
|
||||
}
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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()
|
||||
|
|
@ -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("💥 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
|
||||
|
|
@ -686,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()
|
||||
|
|
@ -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)")
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +0,0 @@
|
|||
import OSLog
|
||||
|
||||
extension Logger {
|
||||
|
||||
/// The logger's subsystem.
|
||||
private static var subsystem = Bundle.main.bundleIdentifier!
|
||||
|
||||
/// All logs related to data such as decoding error, parsing issues, etc.
|
||||
static let data = Logger(subsystem: subsystem, category: "🗄️ Data")
|
||||
|
||||
/// All logs related to the mesh
|
||||
static let mesh = Logger(subsystem: subsystem, category: "🕸️ Mesh")
|
||||
|
||||
/// All logs related to services such as network calls, location, etc.
|
||||
static let services = Logger(subsystem: subsystem, category: "🍏 Services")
|
||||
|
||||
/// All logs related to tracking and analytics.
|
||||
static let statistics = Logger(subsystem: subsystem, category: "📈 Stats")
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
@ -251,180 +239,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<NSFetchRequestResult> = 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)
|
||||
|
||||
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<NSFetchRequestResult> = 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)
|
||||
|
||||
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<NSFetchRequestResult> = 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")
|
||||
|
|
@ -619,6 +538,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)
|
||||
|
|
@ -683,6 +604,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
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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])
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
@ -966,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
|
||||
|
|
@ -1120,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
|
||||
|
|
@ -1183,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)")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1221,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)")
|
||||
}
|
||||
|
|
@ -1288,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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
142
Meshtastic/Views/Helpers/LogDetail.swift
Normal file
142
Meshtastic/Views/Helpers/LogDetail.swift
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
//
|
||||
// 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
|
||||
private var idiom: UIUserInterfaceIdiom { UIDevice.current.userInterfaceIdiom }
|
||||
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("log.time".localized + ":")
|
||||
.font(idiom == .phone ? .callout : .title)
|
||||
LastHeardText(lastHeard: log.date)
|
||||
.font(idiom == .phone ? .callout : .title)
|
||||
} icon: {
|
||||
Image(systemName: "timer")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.font(idiom == .phone ? .callout : .title)
|
||||
.frame(width: 35)
|
||||
}
|
||||
.padding(.bottom, 5)
|
||||
.listSectionSeparator(.hidden, edges: .top)
|
||||
.listSectionSeparator(.visible, edges: .bottom)
|
||||
/// Subsystem
|
||||
Label {
|
||||
Text("log.subsystem".localized + ":")
|
||||
.font(idiom == .phone ? .callout : .title)
|
||||
Text(log.subsystem)
|
||||
.font(idiom == .phone ? .callout : .title)
|
||||
} icon: {
|
||||
Image(systemName: "gear")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.font(idiom == .phone ? .callout : .title)
|
||||
.frame(width: 35)
|
||||
}
|
||||
.padding(.bottom, 5)
|
||||
.listRowSeparator(.visible)
|
||||
/// Process
|
||||
Label {
|
||||
Text("log.process".localized + ":")
|
||||
.font(idiom == .phone ? .callout : .title)
|
||||
Text(log.process)
|
||||
.font(idiom == .phone ? .callout : .title)
|
||||
} icon: {
|
||||
Image(systemName: "tag")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.font(idiom == .phone ? .callout : .title)
|
||||
.frame(width: 35)
|
||||
}
|
||||
.padding(.bottom, 5)
|
||||
.listRowSeparator(.visible)
|
||||
/// Category
|
||||
Label {
|
||||
Text("log.category".localized + ":")
|
||||
.font(idiom == .phone ? .callout : .title)
|
||||
Text(log.category)
|
||||
.font(idiom == .phone ? .callout : .title)
|
||||
} icon: {
|
||||
Image(systemName: "square.grid.2x2")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.font(idiom == .phone ? .callout : .title)
|
||||
.frame(width: 35)
|
||||
}
|
||||
.padding(.bottom, 5)
|
||||
.listRowSeparator(.visible)
|
||||
/// Level
|
||||
Label {
|
||||
Text("log.level".localized + ":")
|
||||
.font(idiom == .phone ? .callout : .title)
|
||||
Text(log.level.description)
|
||||
.font(idiom == .phone ? .callout : .title)
|
||||
} icon: {
|
||||
Image(systemName: "stairs")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.font(idiom == .phone ? .callout : .title)
|
||||
.frame(width: 35)
|
||||
}
|
||||
.padding(.bottom, 5)
|
||||
.listRowSeparator(.visible)
|
||||
/// message
|
||||
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")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.font(idiom == .phone ? .callout : .title)
|
||||
.frame(width: 35)
|
||||
}
|
||||
.listRowSeparator(.hidden)
|
||||
|
||||
}
|
||||
.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
|
||||
}
|
||||
.monospaced()
|
||||
.presentationDetents([.fraction(0.65), .fraction(0.75), .fraction(0.85)])
|
||||
.presentationDragIndicator(.visible)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ struct MeshMapContent: MapContent {
|
|||
}
|
||||
}
|
||||
}
|
||||
.onTapGesture { location in
|
||||
.onTapGesture { _ in
|
||||
selectedPosition = (selectedPosition == position ? nil : position)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<WaypointEntity>
|
||||
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -74,7 +75,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")
|
||||
|
|
@ -193,7 +195,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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
142
Meshtastic/Views/Settings/AppLog.swift
Normal file
142
Meshtastic/Views/Settings/AppLog.swift
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
//
|
||||
// AppLog.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Created by Garth Vander Houwen on 6/4/24.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import OSLog
|
||||
|
||||
@available(iOS 17.4, *)
|
||||
struct AppLog: View {
|
||||
|
||||
@State private var logs: [OSLogEntryLog] = []
|
||||
@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
|
||||
@State private var searchTerm = ""
|
||||
@State var isExporting = false
|
||||
@State var exportString = ""
|
||||
private var idiom: UIUserInterfaceIdiom { UIDevice.current.userInterfaceIdiom }
|
||||
private let dateFormatStyle = Date.FormatStyle()
|
||||
.hour(.twoDigits(amPM: .omitted))
|
||||
.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(searchResults, selection: $selection, sortOrder: $sortOrder) {
|
||||
if idiom != .phone {
|
||||
TableColumn("log.time", value: \.date) { value in
|
||||
Text(value.date.formatted(dateFormatStyle))
|
||||
}
|
||||
.width(min: 125, max: 150)
|
||||
TableColumn("log.category", value: \.category)
|
||||
.width(min: 125, max: 150)
|
||||
TableColumn("log.level") { value in
|
||||
Text(value.level.description)
|
||||
}
|
||||
.width(min: 75, max: 100)
|
||||
}
|
||||
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 {
|
||||
if logs.isEmpty {
|
||||
ContentUnavailableView("Getting Logs . . .", systemImage: "scroll")
|
||||
}
|
||||
}
|
||||
.onChange(of: sortOrder) { _, sortOrder in
|
||||
withAnimation {
|
||||
logs.sort(using: sortOrder)
|
||||
}
|
||||
}
|
||||
.onChange(of: selection) { newSelection in
|
||||
presentingErrorDetails = true
|
||||
let log = logs.first {
|
||||
$0.id == newSelection
|
||||
}
|
||||
selectedLog = log
|
||||
}
|
||||
.sheet(item: $selectedLog, onDismiss: didDismiss) { log in
|
||||
LogDetail(log: log)
|
||||
.padding()
|
||||
}
|
||||
.task {
|
||||
logs = await fetchLogs()
|
||||
logs.sort(using: sortOrder)
|
||||
}
|
||||
.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\(logs.isEmpty ? "" : " (\(logs.count))")", displayMode: .inline)
|
||||
.toolbar {
|
||||
if !logs.isEmpty {
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
Button(action: {
|
||||
exportString = logToCsvFile(log: logs)
|
||||
isExporting = true
|
||||
}) {
|
||||
Image(systemName: "square.and.arrow.down")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func didDismiss() {
|
||||
selection = nil
|
||||
selectedLog = nil
|
||||
}
|
||||
}
|
||||
|
||||
@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] {
|
||||
do {
|
||||
let predicate = NSPredicate(format: "subsystem IN %@", [
|
||||
"com.apple.coredata",
|
||||
"gvh.MeshtasticClient"
|
||||
])
|
||||
let logs = try await Logger.fetch(predicateFormat: predicate.predicateFormat)
|
||||
return logs
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension OSLogEntry: Identifiable { }
|
||||
|
|
@ -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")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ struct Settings: View {
|
|||
case telemetryConfig
|
||||
case meshLog
|
||||
case adminMessageLog
|
||||
case appLog
|
||||
case about
|
||||
}
|
||||
var body: some View {
|
||||
|
|
@ -412,17 +413,18 @@ 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")
|
||||
if #available (iOS 17.4, *) {
|
||||
NavigationLink {
|
||||
AppLog()
|
||||
} label: {
|
||||
Label {
|
||||
Text("Debug Logs")
|
||||
} icon: {
|
||||
Image(systemName: "stethoscope")
|
||||
}
|
||||
}
|
||||
.tag(SettingsSidebar.appLog)
|
||||
}
|
||||
.tag(SettingsSidebar.adminMessageLog)
|
||||
}
|
||||
Section(header: Text("Firmware")) {
|
||||
NavigationLink {
|
||||
|
|
|
|||
|
|
@ -1,22 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
@ -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.
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
52
README.md
52
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.
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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"="מפת מש";
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -2,376 +2,382 @@
|
|||
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";
|
||||
"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";
|
||||
"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";
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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 地图";
|
||||
|
|
|
|||
|
|
@ -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 地圖";
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue