mirror of
https://github.com/meshtastic/Meshtastic-Apple.git
synced 2026-04-20 22:13:56 +00:00
Merge branch '2.5.22' into StoreAndForwardUpdate
This commit is contained in:
commit
b08064258f
116 changed files with 4972 additions and 3847 deletions
11
.github/ISSUE_TEMPLATE/bug.yml
vendored
11
.github/ISSUE_TEMPLATE/bug.yml
vendored
|
|
@ -8,6 +8,17 @@ body:
|
|||
value: |
|
||||
Thanks for taking the time to fill out this bug report!
|
||||
|
||||
- type: checkboxes
|
||||
id: prerequisites
|
||||
attributes:
|
||||
label: Before submitting
|
||||
description: Please confirm you've completed the following steps
|
||||
options:
|
||||
- label: I have searched existing issues to make sure this bug hasn't already been reported
|
||||
required: true
|
||||
- label: I have updated to the latest version of the software to verify the issue still exists
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
|
|
|
|||
2
.github/pull_request_template.md
vendored
2
.github/pull_request_template.md
vendored
|
|
@ -16,6 +16,6 @@
|
|||
- [ ] My code adheres to the project's coding and style guidelines.
|
||||
- [ ] I have conducted a self-review of my code.
|
||||
- [ ] I have commented my code, particularly in complex areas.
|
||||
- [ ] I have made corresponding changes to the documentation.
|
||||
- [ ] I have verified whether these changes require an update to existing documentation or if new documentation is needed, and created an issue in the [docs repo](http://github.com/meshtastic/meshtastic/issues) if applicable.
|
||||
- [ ] I have tested the change to ensure that it works as intended.
|
||||
|
||||
|
|
|
|||
|
|
@ -4,29 +4,31 @@ Thank you for considering contributing to Meshtastic! We appreciate your time an
|
|||
|
||||
## Table of Contents
|
||||
|
||||
1. [Getting Started](#getting-started)
|
||||
2. [Development Workflow](#development-workflow)
|
||||
- [Targeting `main`](#targeting-main)
|
||||
- [Small, Incremental Changes](#small-incremental-changes)
|
||||
- [Rebase Commits](#rebase-commits)
|
||||
3. [Creating a Branch](#creating-a-branch)
|
||||
4. [Making Changes](#making-changes)
|
||||
5. [Commit Messages](#commit-messages)
|
||||
6. [Merging Changes](#merging-changes)
|
||||
7. [Testing](#testing)
|
||||
8. [Code Review](#code-review)
|
||||
9. [Documentation](#documentation)
|
||||
10. [Style Guides](#style-guides)
|
||||
- [Contributing to Meshtastic](#contributing-to-meshtastic)
|
||||
- [Table of Contents](#table-of-contents)
|
||||
- [Getting Started](#getting-started)
|
||||
- [Development Workflow](#development-workflow)
|
||||
- [Targeting `main`](#targeting-main)
|
||||
- [Small, Incremental Changes](#small-incremental-changes)
|
||||
- [Rebase Commits](#rebase-commits)
|
||||
- [Creating a Branch](#creating-a-branch)
|
||||
- [Making Changes](#making-changes)
|
||||
- [Commit Messages](#commit-messages)
|
||||
- [Merging Changes](#merging-changes)
|
||||
- [Testing](#testing)
|
||||
- [Code Review](#code-review)
|
||||
- [Documentation](#documentation)
|
||||
- [Style Guides](#style-guides)
|
||||
- [Git Commit Messages](#git-commit-messages)
|
||||
- [Code Style](#code-style)
|
||||
11. [Community](#community)
|
||||
- [Community](#community)
|
||||
|
||||
## Getting Started
|
||||
|
||||
1. Fork the repository on GitLab.
|
||||
1. Fork the repository on GitHub.
|
||||
2. Clone your fork to your local machine:
|
||||
```sh
|
||||
git clone https://gitlab.com/<your-username>/Meshtastic-Apple.git
|
||||
git clone https://github.com/<your-username>/Meshtastic-Apple.git
|
||||
```
|
||||
3. Navigate to the project directory:
|
||||
```sh
|
||||
|
|
|
|||
|
|
@ -2203,22 +2203,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"Backup Database" : {
|
||||
"localizations" : {
|
||||
"sr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Резервна база података"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "备份数据库"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Bandwidth" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
|
|
@ -4267,6 +4251,9 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Community Support" : {
|
||||
|
||||
},
|
||||
"Config" : {
|
||||
"localizations" : {
|
||||
|
|
@ -5628,6 +5615,9 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Confirm" : {
|
||||
|
||||
},
|
||||
"Connect to a Node" : {
|
||||
"localizations" : {
|
||||
|
|
@ -5644,6 +5634,9 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Connect to new radio?" : {
|
||||
|
||||
},
|
||||
"connected" : {
|
||||
"localizations" : {
|
||||
|
|
@ -5852,6 +5845,9 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Connecting to a new radio will clear all app data on the phone." : {
|
||||
|
||||
},
|
||||
"Connection Attempt %lld of 10" : {
|
||||
"localizations" : {
|
||||
|
|
@ -6363,6 +6359,9 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Currently showing modules that may not be supported by this node." : {
|
||||
|
||||
},
|
||||
"Currently the recommended way to update ESP32 devices is using the web flasher on a desktop computer from a chrome based browser. It does not work on mobile devices or over BLE." : {
|
||||
"localizations" : {
|
||||
|
|
@ -9297,6 +9296,9 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Enable broadcasting packets via UDP over the local network." : {
|
||||
|
||||
},
|
||||
"Enable Notifications" : {
|
||||
"localizations" : {
|
||||
|
|
@ -10687,6 +10689,9 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Full Support" : {
|
||||
|
||||
},
|
||||
"gas" : {
|
||||
"extractionState" : "manual",
|
||||
|
|
@ -12145,23 +12150,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"HUMIDITY" : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "LUFTFEUCHTIGKEIT"
|
||||
}
|
||||
},
|
||||
"sr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "ВЛАЖНОСТ"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"hybrid" : {
|
||||
"extractionState" : "migrated",
|
||||
"localizations" : {
|
||||
|
|
@ -17147,71 +17135,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"mesh.log" : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Mesh Log"
|
||||
}
|
||||
},
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Mesh Log"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Journal du maillage"
|
||||
}
|
||||
},
|
||||
"he" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "יומן מש"
|
||||
}
|
||||
},
|
||||
"pl" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Dziennik Sieci"
|
||||
}
|
||||
},
|
||||
"pt-PT" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Log do Mesh"
|
||||
}
|
||||
},
|
||||
"se" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Mesh-logg"
|
||||
}
|
||||
},
|
||||
"sr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Логови мреже"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Mesh 日志"
|
||||
}
|
||||
},
|
||||
"zh-Hant-TW" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Mesh 紀錄檔"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"mesh.log.ambientlighting.config %@" : {
|
||||
"extractionState" : "migrated",
|
||||
"localizations" : {
|
||||
|
|
@ -22221,21 +22144,8 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"OTA Updates are not supported on the this NRF Device." : {
|
||||
"localizations" : {
|
||||
"sr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "ОТА ажурирања нису подржана на овом NRF уређају."
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "OTA 更新不支持 NRF 设备"
|
||||
}
|
||||
}
|
||||
}
|
||||
"OTA Updates are not supported on this NRF Device." : {
|
||||
|
||||
},
|
||||
"OTA Updates are not supported on your platform." : {
|
||||
"localizations" : {
|
||||
|
|
@ -23614,6 +23524,9 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Radiation" : {
|
||||
|
||||
},
|
||||
"Radio Disconnected" : {
|
||||
"extractionState" : "manual",
|
||||
|
|
@ -24701,22 +24614,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"restore" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Wiederherstellen"
|
||||
}
|
||||
},
|
||||
"sr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Обнова"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"resume" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
|
|
@ -28531,6 +28428,12 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Soil Moisture" : {
|
||||
|
||||
},
|
||||
"Soil Temp" : {
|
||||
|
||||
},
|
||||
"Specifies how long the monitored GPIO should output." : {
|
||||
"localizations" : {
|
||||
|
|
@ -30203,6 +30106,9 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"The Router roles are designed for high vantage locations like mountaintops and towers. This node needs to be able to have a good direct connection to most of the nodes on the network or else this will significantly hurt the network." : {
|
||||
|
||||
},
|
||||
"The secondary public key authorized to send admin messages to this node." : {
|
||||
"localizations" : {
|
||||
|
|
@ -30376,6 +30282,9 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"This node does not support any configurable modules." : {
|
||||
|
||||
},
|
||||
"This will disable fixed position and remove the currently set position." : {
|
||||
"localizations" : {
|
||||
|
|
@ -31526,6 +31435,9 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"UDP Broadcast" : {
|
||||
|
||||
},
|
||||
"Ukraine 433mhz" : {
|
||||
"extractionState" : "manual",
|
||||
|
|
@ -32699,6 +32611,9 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Weight" : {
|
||||
|
||||
},
|
||||
"What does the lock mean?" : {
|
||||
"localizations" : {
|
||||
|
|
|
|||
|
|
@ -11,13 +11,23 @@
|
|||
231B3F222D087A4C0069A07D /* MetricsColumnList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 231B3F1F2D087A4C0069A07D /* MetricsColumnList.swift */; };
|
||||
231B3F252D087C3C0069A07D /* EnvironmentDefaultColumns.swift in Sources */ = {isa = PBXBuildFile; fileRef = 231B3F242D087C3C0069A07D /* EnvironmentDefaultColumns.swift */; };
|
||||
231B3F272D0885240069A07D /* MetricsColumnDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = 231B3F262D0885240069A07D /* MetricsColumnDetail.swift */; };
|
||||
233E99B62D849C3D00CC3A77 /* WeatherConditionsCompactWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 233E99B52D849C3D00CC3A77 /* WeatherConditionsCompactWidget.swift */; };
|
||||
233E99B82D849C6500CC3A77 /* HumidityCompactWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 233E99B72D849C6500CC3A77 /* HumidityCompactWidget.swift */; };
|
||||
233E99BA2D849C7000CC3A77 /* PressureCompactWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 233E99B92D849C7000CC3A77 /* PressureCompactWidget.swift */; };
|
||||
233E99BC2D849C8C00CC3A77 /* WindCompactWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 233E99BB2D849C8C00CC3A77 /* WindCompactWidget.swift */; };
|
||||
233E99BE2D849D3200CC3A77 /* RadiationCompactWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 233E99BD2D849D3200CC3A77 /* RadiationCompactWidget.swift */; };
|
||||
233E99C12D849D6000CC3A77 /* DistanceCompactWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 233E99C02D849D6000CC3A77 /* DistanceCompactWidget.swift */; };
|
||||
233E99C32D849D7A00CC3A77 /* WeightCompactWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 233E99C22D849D7A00CC3A77 /* WeightCompactWidget.swift */; };
|
||||
233E99C52D84A0B600CC3A77 /* CompactWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 233E99C42D84A0B600CC3A77 /* CompactWidget.swift */; };
|
||||
233E99C72D84A70900CC3A77 /* SoilCompactWidgets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 233E99C62D84A70900CC3A77 /* SoilCompactWidgets.swift */; };
|
||||
233E99CB2D85AAA900CC3A77 /* RainfallCompactWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 233E99CA2D85AAA900CC3A77 /* RainfallCompactWidget.swift */; };
|
||||
2344A2AB2D66974300170A77 /* ManagedAttributePropertyWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2344A2AA2D66973D00170A77 /* ManagedAttributePropertyWrapper.swift */; };
|
||||
2344A2AF2D6697A700170A77 /* TelemetryEntity+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2344A2AD2D6697A700170A77 /* TelemetryEntity+CoreDataClass.swift */; };
|
||||
2344A2B02D6697A700170A77 /* TelemetryEntity+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2344A2AE2D6697A700170A77 /* TelemetryEntity+CoreDataProperties.swift */; };
|
||||
2344A2B12D68DFF800170A77 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25C49D8F2C471AEA0024FBD1 /* Constants.swift */; };
|
||||
2373AE132D0A216C0086C749 /* MetricsChartSeries.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2373AE122D0A216C0086C749 /* MetricsChartSeries.swift */; };
|
||||
2373AE152D0A24930086C749 /* MetricsSeriesList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2373AE142D0A24930086C749 /* MetricsSeriesList.swift */; };
|
||||
2373AE172D0A26620086C749 /* EnviornmentDefaultSeries.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2373AE162D0A26620086C749 /* EnviornmentDefaultSeries.swift */; };
|
||||
2373AE172D0A26620086C749 /* EnvironmentDefaultSeries.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2373AE162D0A26620086C749 /* EnvironmentDefaultSeries.swift */; };
|
||||
251926852C3BA97800249DF5 /* FavoriteNodeButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 251926842C3BA97800249DF5 /* FavoriteNodeButton.swift */; };
|
||||
251926872C3BAE2200249DF5 /* NodeAlertsButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 251926862C3BAE2200249DF5 /* NodeAlertsButton.swift */; };
|
||||
2519268A2C3BB1B200249DF5 /* ExchangePositionsButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 251926892C3BB1B200249DF5 /* ExchangePositionsButton.swift */; };
|
||||
|
|
@ -56,7 +66,6 @@
|
|||
BCE2D3C52C7AE369008E6199 /* RestartNodeIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCE2D3C42C7AE369008E6199 /* RestartNodeIntent.swift */; };
|
||||
BCE2D3C72C7B0D0A008E6199 /* ShortcutsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCE2D3C62C7B0D0A008E6199 /* ShortcutsProvider.swift */; };
|
||||
BCE2D3C92C7C377F008E6199 /* FactoryResetNodeIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCE2D3C82C7C377F008E6199 /* FactoryResetNodeIntent.swift */; };
|
||||
C9697FA527933B8C00250207 /* SQLite in Frameworks */ = {isa = PBXBuildFile; productRef = C9697FA427933B8C00250207 /* SQLite */; };
|
||||
D93068D32B8129510066FBC8 /* MessageContextMenuItems.swift in Sources */ = {isa = PBXBuildFile; fileRef = D93068D22B8129510066FBC8 /* MessageContextMenuItems.swift */; };
|
||||
D93068D52B812B700066FBC8 /* MessageDestination.swift in Sources */ = {isa = PBXBuildFile; fileRef = D93068D42B812B700066FBC8 /* MessageDestination.swift */; };
|
||||
D93068D72B8146690066FBC8 /* MessageText.swift in Sources */ = {isa = PBXBuildFile; fileRef = D93068D62B8146690066FBC8 /* MessageText.swift */; };
|
||||
|
|
@ -64,7 +73,6 @@
|
|||
D93068DB2B81C85E0066FBC8 /* PowerConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = D93068DA2B81C85E0066FBC8 /* PowerConfig.swift */; };
|
||||
D93068DD2B81CA820066FBC8 /* ConfigHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = D93068DC2B81CA820066FBC8 /* ConfigHeader.swift */; };
|
||||
D93069082B81DF040066FBC8 /* SaveConfigButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D93069072B81DF040066FBC8 /* SaveConfigButton.swift */; };
|
||||
D9BC22DB2B7DE8E2006A37D5 /* TileDownloadStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9BC22DA2B7DE8E2006A37D5 /* TileDownloadStatus.swift */; };
|
||||
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 */; };
|
||||
|
|
@ -90,7 +98,6 @@
|
|||
DD2553572855B02500E55709 /* LoRaConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2553562855B02500E55709 /* LoRaConfig.swift */; };
|
||||
DD2553592855B52700E55709 /* PositionConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2553582855B52700E55709 /* PositionConfig.swift */; };
|
||||
DD268D8E2BCC90E2008073AE /* RouteEnums.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD268D8D2BCC90E2008073AE /* RouteEnums.swift */; };
|
||||
DD2AD8A8296D2DF9001FF0E7 /* MapViewSwiftUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2AD8A7296D2DF9001FF0E7 /* MapViewSwiftUI.swift */; };
|
||||
DD33DB622B3D27C7003E1EA0 /* FirmwareApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD33DB612B3D27C7003E1EA0 /* FirmwareApi.swift */; };
|
||||
DD3501892852FC3B000FC853 /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3501882852FC3B000FC853 /* Settings.swift */; };
|
||||
DD354FD92BD96A0B0061A25F /* IAQScale.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD354FD82BD96A0B0061A25F /* IAQScale.swift */; };
|
||||
|
|
@ -144,9 +151,7 @@
|
|||
DD93800E2BA74D0C008BEC06 /* ChannelForm.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD93800D2BA74D0C008BEC06 /* ChannelForm.swift */; };
|
||||
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 */; };
|
||||
DD97E96828EFE9A00056DDA4 /* About.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD97E96728EFE9A00056DDA4 /* About.swift */; };
|
||||
|
|
@ -203,8 +208,6 @@
|
|||
DDDB26422AABF655003AFCB7 /* NodeListItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDDB26412AABF655003AFCB7 /* NodeListItem.swift */; };
|
||||
DDDB26442AAC0206003AFCB7 /* NodeDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDDB26432AAC0206003AFCB7 /* NodeDetail.swift */; };
|
||||
DDDB26462AACC0B7003AFCB7 /* NodeInfoItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDDB26452AACC0B7003AFCB7 /* NodeInfoItem.swift */; };
|
||||
DDDB26482AACD6D1003AFCB7 /* NodeMapMapkit.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDDB26472AACD6D1003AFCB7 /* NodeMapMapkit.swift */; };
|
||||
DDDB443629F6287000EE2349 /* MapButtons.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDDB443529F6287000EE2349 /* MapButtons.swift */; };
|
||||
DDDB444029F79AB000EE2349 /* UserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDDB443F29F79AB000EE2349 /* UserDefaults.swift */; };
|
||||
DDDB444229F8A88700EE2349 /* Double.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDDB444129F8A88700EE2349 /* Double.swift */; };
|
||||
DDDB444429F8A8DD00EE2349 /* Float.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDDB444329F8A8DD00EE2349 /* Float.swift */; };
|
||||
|
|
@ -277,12 +280,23 @@
|
|||
231B3F202D087A4C0069A07D /* MetricTableColumn.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetricTableColumn.swift; sourceTree = "<group>"; };
|
||||
231B3F242D087C3C0069A07D /* EnvironmentDefaultColumns.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnvironmentDefaultColumns.swift; sourceTree = "<group>"; };
|
||||
231B3F262D0885240069A07D /* MetricsColumnDetail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetricsColumnDetail.swift; sourceTree = "<group>"; };
|
||||
233E99B32D84969500CC3A77 /* MeshtasticDataModelV 50.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 50.xcdatamodel"; sourceTree = "<group>"; };
|
||||
233E99B52D849C3D00CC3A77 /* WeatherConditionsCompactWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeatherConditionsCompactWidget.swift; sourceTree = "<group>"; };
|
||||
233E99B72D849C6500CC3A77 /* HumidityCompactWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HumidityCompactWidget.swift; sourceTree = "<group>"; };
|
||||
233E99B92D849C7000CC3A77 /* PressureCompactWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PressureCompactWidget.swift; sourceTree = "<group>"; };
|
||||
233E99BB2D849C8C00CC3A77 /* WindCompactWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindCompactWidget.swift; sourceTree = "<group>"; };
|
||||
233E99BD2D849D3200CC3A77 /* RadiationCompactWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadiationCompactWidget.swift; sourceTree = "<group>"; };
|
||||
233E99C02D849D6000CC3A77 /* DistanceCompactWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DistanceCompactWidget.swift; sourceTree = "<group>"; };
|
||||
233E99C22D849D7A00CC3A77 /* WeightCompactWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeightCompactWidget.swift; sourceTree = "<group>"; };
|
||||
233E99C42D84A0B600CC3A77 /* CompactWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompactWidget.swift; sourceTree = "<group>"; };
|
||||
233E99C62D84A70900CC3A77 /* SoilCompactWidgets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoilCompactWidgets.swift; sourceTree = "<group>"; };
|
||||
233E99CA2D85AAA900CC3A77 /* RainfallCompactWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RainfallCompactWidget.swift; sourceTree = "<group>"; };
|
||||
2344A2AA2D66973D00170A77 /* ManagedAttributePropertyWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManagedAttributePropertyWrapper.swift; sourceTree = "<group>"; };
|
||||
2344A2AD2D6697A700170A77 /* TelemetryEntity+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TelemetryEntity+CoreDataClass.swift"; sourceTree = "<group>"; };
|
||||
2344A2AE2D6697A700170A77 /* TelemetryEntity+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TelemetryEntity+CoreDataProperties.swift"; sourceTree = "<group>"; };
|
||||
2373AE122D0A216C0086C749 /* MetricsChartSeries.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetricsChartSeries.swift; sourceTree = "<group>"; };
|
||||
2373AE142D0A24930086C749 /* MetricsSeriesList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetricsSeriesList.swift; sourceTree = "<group>"; };
|
||||
2373AE162D0A26620086C749 /* EnviornmentDefaultSeries.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnviornmentDefaultSeries.swift; sourceTree = "<group>"; };
|
||||
2373AE162D0A26620086C749 /* EnvironmentDefaultSeries.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnvironmentDefaultSeries.swift; sourceTree = "<group>"; };
|
||||
251926842C3BA97800249DF5 /* FavoriteNodeButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoriteNodeButton.swift; sourceTree = "<group>"; };
|
||||
251926862C3BAE2200249DF5 /* NodeAlertsButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeAlertsButton.swift; sourceTree = "<group>"; };
|
||||
251926892C3BB1B200249DF5 /* ExchangePositionsButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExchangePositionsButton.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -324,7 +338,6 @@
|
|||
D93068DC2B81CA820066FBC8 /* ConfigHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigHeader.swift; sourceTree = "<group>"; };
|
||||
D93069062B81D8900066FBC8 /* MeshtasticDataModelV 27.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 27.xcdatamodel"; sourceTree = "<group>"; };
|
||||
D93069072B81DF040066FBC8 /* SaveConfigButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SaveConfigButton.swift; sourceTree = "<group>"; };
|
||||
D9BC22DA2B7DE8E2006A37D5 /* TileDownloadStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TileDownloadStatus.swift; sourceTree = "<group>"; };
|
||||
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>"; };
|
||||
|
|
@ -359,7 +372,6 @@
|
|||
DD268D8D2BCC90E2008073AE /* RouteEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RouteEnums.swift; sourceTree = "<group>"; };
|
||||
DD295CE92B323ED9002CC4AC /* MeshtasticDataModelV22.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV22.xcdatamodel; sourceTree = "<group>"; };
|
||||
DD2984A82C5AEF7500B1268D /* MeshtasticDataModelV 41.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 41.xcdatamodel"; sourceTree = "<group>"; };
|
||||
DD2AD8A7296D2DF9001FF0E7 /* MapViewSwiftUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapViewSwiftUI.swift; sourceTree = "<group>"; };
|
||||
DD2CC2E52ABFE04E00EDFDA7 /* MeshtasticDataModelV19.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV19.xcdatamodel; sourceTree = "<group>"; };
|
||||
DD31B04D2BDC6FD30024FA63 /* MeshtasticDataModelV 36.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 36.xcdatamodel"; sourceTree = "<group>"; };
|
||||
DD33DB602B3D1ECC003E1EA0 /* MeshtasticDataModelV 23.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 23.xcdatamodel"; sourceTree = "<group>"; };
|
||||
|
|
@ -431,10 +443,8 @@
|
|||
DD93800D2BA74D0C008BEC06 /* ChannelForm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelForm.swift; sourceTree = "<group>"; };
|
||||
DD94B73F2ACCE3BE00DCD1D1 /* MapSettingsForm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapSettingsForm.swift; sourceTree = "<group>"; };
|
||||
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>"; };
|
||||
DD97E96528EFD9820056DDA4 /* MeshtasticLogo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshtasticLogo.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -508,9 +518,7 @@
|
|||
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>"; };
|
||||
DDDB26452AACC0B7003AFCB7 /* NodeInfoItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeInfoItem.swift; sourceTree = "<group>"; };
|
||||
DDDB26472AACD6D1003AFCB7 /* NodeMapMapkit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeMapMapkit.swift; sourceTree = "<group>"; };
|
||||
DDDB26492AAD743E003AFCB7 /* MeshtasticDataModelV18.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV18.xcdatamodel; sourceTree = "<group>"; };
|
||||
DDDB443529F6287000EE2349 /* MapButtons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapButtons.swift; sourceTree = "<group>"; };
|
||||
DDDB443F29F79AB000EE2349 /* UserDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaults.swift; sourceTree = "<group>"; };
|
||||
DDDB444129F8A88700EE2349 /* Double.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Double.swift; sourceTree = "<group>"; };
|
||||
DDDB444329F8A8DD00EE2349 /* Float.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Float.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -567,7 +575,6 @@
|
|||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
25A978BA2C13F8ED0003AAE7 /* MeshtasticProtobufs in Frameworks */,
|
||||
C9697FA527933B8C00250207 /* SQLite in Frameworks */,
|
||||
DD0D3D222A55CEB10066DB71 /* CocoaMQTT in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
|
@ -600,12 +607,30 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
231B3F242D087C3C0069A07D /* EnvironmentDefaultColumns.swift */,
|
||||
2373AE162D0A26620086C749 /* EnviornmentDefaultSeries.swift */,
|
||||
2373AE162D0A26620086C749 /* EnvironmentDefaultSeries.swift */,
|
||||
231B3F262D0885240069A07D /* MetricsColumnDetail.swift */,
|
||||
);
|
||||
path = "Metrics Columns";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
233E99B42D849C2D00CC3A77 /* Compact Widgets */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
233E99C42D84A0B600CC3A77 /* CompactWidget.swift */,
|
||||
233E99B72D849C6500CC3A77 /* HumidityCompactWidget.swift */,
|
||||
233E99B52D849C3D00CC3A77 /* WeatherConditionsCompactWidget.swift */,
|
||||
233E99B92D849C7000CC3A77 /* PressureCompactWidget.swift */,
|
||||
233E99BB2D849C8C00CC3A77 /* WindCompactWidget.swift */,
|
||||
DDFEB3BA29900C1200EE7472 /* CurrentConditionsCompact.swift */,
|
||||
233E99BD2D849D3200CC3A77 /* RadiationCompactWidget.swift */,
|
||||
233E99C02D849D6000CC3A77 /* DistanceCompactWidget.swift */,
|
||||
233E99C22D849D7A00CC3A77 /* WeightCompactWidget.swift */,
|
||||
233E99C62D84A70900CC3A77 /* SoilCompactWidgets.swift */,
|
||||
233E99CA2D85AAA900CC3A77 /* RainfallCompactWidget.swift */,
|
||||
);
|
||||
path = "Compact Widgets";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
2344A2AC2D66978000170A77 /* CoreData */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
|
@ -665,27 +690,6 @@
|
|||
path = AppIntents;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C9483F6B2773016700998F6B /* MapKitMap */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C9A7BC0E27759A6800760B50 /* Custom */,
|
||||
DDDB26472AACD6D1003AFCB7 /* NodeMapMapkit.swift */,
|
||||
DD964FBE296E76EF007C176F /* WaypointFormMapKit.swift */,
|
||||
);
|
||||
path = MapKitMap;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C9A7BC0E27759A6800760B50 /* Custom */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DD964FC32974767D007C176F /* MapViewFitExtension.swift */,
|
||||
DD2AD8A7296D2DF9001FF0E7 /* MapViewSwiftUI.swift */,
|
||||
DDDB443529F6287000EE2349 /* MapButtons.swift */,
|
||||
D9BC22DA2B7DE8E2006A37D5 /* TileDownloadStatus.swift */,
|
||||
);
|
||||
path = Custom;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D9C9839E2B79D0C600BDBE6A /* TextMessageField */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
|
@ -777,7 +781,6 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
DD5E523E298F5A9E00D21B61 /* AirQualityIndex.swift */,
|
||||
DDFEB3BA29900C1200EE7472 /* CurrentConditionsCompact.swift */,
|
||||
DDA9515D2BC6F56F00CEA535 /* IndoorAirQuality.swift */,
|
||||
DD354FD82BD96A0B0061A25F /* IAQScale.swift */,
|
||||
DD41A61429AB0035003C5A37 /* NodeWeatherForecast.swift */,
|
||||
|
|
@ -987,7 +990,6 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
DD6D5A312CA1176A00ED3032 /* Layouts */,
|
||||
C9483F6B2773016700998F6B /* MapKitMap */,
|
||||
DDC2E18D26CE25CB0042C5E4 /* Helpers */,
|
||||
DD47E3D726F2F21A00029299 /* Bluetooth */,
|
||||
DDC2E18B26CE25A70042C5E4 /* Messages */,
|
||||
|
|
@ -1038,6 +1040,7 @@
|
|||
DDC2E18D26CE25CB0042C5E4 /* Helpers */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
233E99B42D849C2D00CC3A77 /* Compact Widgets */,
|
||||
DD6F65772C6EAB860053C113 /* Help */,
|
||||
DD3CC6BD28E4CD9800FA9159 /* BatteryGauge.swift */,
|
||||
DD3CC24B2C498D6C001BD3A2 /* BatteryCompact.swift */,
|
||||
|
|
@ -1213,7 +1216,6 @@
|
|||
);
|
||||
name = Meshtastic;
|
||||
packageProductDependencies = (
|
||||
C9697FA427933B8C00250207 /* SQLite */,
|
||||
DD0D3D212A55CEB10066DB71 /* CocoaMQTT */,
|
||||
25A978B92C13F8ED0003AAE7 /* MeshtasticProtobufs */,
|
||||
);
|
||||
|
|
@ -1283,7 +1285,6 @@
|
|||
);
|
||||
mainGroup = DDC2E14B26CE248E0042C5E4;
|
||||
packageReferences = (
|
||||
C9697FA327933B8C00250207 /* XCRemoteSwiftPackageReference "SQLite.swift" */,
|
||||
DD0D3D202A55CEB10066DB71 /* XCRemoteSwiftPackageReference "CocoaMQTT" */,
|
||||
25A978B82C13F8ED0003AAE7 /* XCLocalSwiftPackageReference "MeshtasticProtobufs" */,
|
||||
259792242C2F10B600AD1659 /* XCRemoteSwiftPackageReference "swift-protobuf" */,
|
||||
|
|
@ -1380,11 +1381,9 @@
|
|||
DD836AE726F6B38600ABCC23 /* Connect.swift in Sources */,
|
||||
D93069082B81DF040066FBC8 /* SaveConfigButton.swift in Sources */,
|
||||
DD5E523F298F5A9E00D21B61 /* AirQualityIndex.swift in Sources */,
|
||||
DD964FBF296E76EF007C176F /* WaypointFormMapKit.swift in Sources */,
|
||||
DDD5BB182C2F9C36007E03CA /* OSLogEntryLog.swift in Sources */,
|
||||
DD3501892852FC3B000FC853 /* Settings.swift in Sources */,
|
||||
DDDC22382BA92344002C44F1 /* MeshMapContent.swift in Sources */,
|
||||
DDDB443629F6287000EE2349 /* MapButtons.swift in Sources */,
|
||||
DD5D0A9C2931B9F200F7EA61 /* EthernetModes.swift in Sources */,
|
||||
6DEDA55A2A957B8E00321D2E /* DetectionSensorLog.swift in Sources */,
|
||||
DD798B072915928D005217CD /* ChannelMessageList.swift in Sources */,
|
||||
|
|
@ -1405,8 +1404,10 @@
|
|||
DDB6ABD628AE742000384BA1 /* BluetoothConfig.swift in Sources */,
|
||||
251926902C3CB44900249DF5 /* ClientHistoryButton.swift in Sources */,
|
||||
DDD5BB102C285FB3007E03CA /* AppLogFilter.swift in Sources */,
|
||||
2373AE172D0A26620086C749 /* EnviornmentDefaultSeries.swift in Sources */,
|
||||
2373AE172D0A26620086C749 /* EnvironmentDefaultSeries.swift in Sources */,
|
||||
233E99B82D849C6500CC3A77 /* HumidityCompactWidget.swift in Sources */,
|
||||
DD4640202AFF10F4002A5ECB /* WaypointForm.swift in Sources */,
|
||||
233E99C12D849D6000CC3A77 /* DistanceCompactWidget.swift in Sources */,
|
||||
DD769E0328D18BF1001A3F05 /* DeviceMetricsLog.swift in Sources */,
|
||||
DDAF8C5326EB1DF10058C060 /* BLEManager.swift in Sources */,
|
||||
DD15E4F32B8BA56E00654F61 /* PaxCounterConfig.swift in Sources */,
|
||||
|
|
@ -1427,7 +1428,6 @@
|
|||
DD354FD92BD96A0B0061A25F /* IAQScale.swift in Sources */,
|
||||
DDDB445429F8AD1600EE2349 /* Data.swift in Sources */,
|
||||
DDDB26462AACC0B7003AFCB7 /* NodeInfoItem.swift in Sources */,
|
||||
DD2AD8A8296D2DF9001FF0E7 /* MapViewSwiftUI.swift in Sources */,
|
||||
DDE5B4042B2279A700FCDD05 /* TraceRouteLog.swift in Sources */,
|
||||
DD6193792863875F00E59241 /* SerialConfig.swift in Sources */,
|
||||
DDDB263F2AABEE20003AFCB7 /* NodeList.swift in Sources */,
|
||||
|
|
@ -1436,7 +1436,7 @@
|
|||
DDE9659C2B1C3B6A00531070 /* RouteRecorder.swift in Sources */,
|
||||
B399E8A42B6F486400E4488E /* RetryButton.swift in Sources */,
|
||||
DDB8F4102A9EE5B400230ECE /* Messages.swift in Sources */,
|
||||
DDDB26482AACD6D1003AFCB7 /* NodeMapMapkit.swift in Sources */,
|
||||
233E99C32D849D7A00CC3A77 /* WeightCompactWidget.swift in Sources */,
|
||||
DD4A911E2708C65400501B7E /* AppSettings.swift in Sources */,
|
||||
DD1BD0F32C63C65E008C0C70 /* SecurityConfig.swift in Sources */,
|
||||
DD2160AF28C5552500C17253 /* MQTTConfig.swift in Sources */,
|
||||
|
|
@ -1453,6 +1453,7 @@
|
|||
DD6193772862F90F00E59241 /* CannedMessagesConfig.swift in Sources */,
|
||||
DD3619152B1EF9F900C41C8C /* LocationsHandler.swift in Sources */,
|
||||
DD6F65792C6EADE60053C113 /* DirectMessagesHelp.swift in Sources */,
|
||||
233E99B62D849C3D00CC3A77 /* WeatherConditionsCompactWidget.swift in Sources */,
|
||||
25F5D5C02C3F6DA6008036E3 /* Router.swift in Sources */,
|
||||
DDDB444A29F8AA3A00EE2349 /* CLLocationCoordinate2D.swift in Sources */,
|
||||
25C49D902C471AEA0024FBD1 /* Constants.swift in Sources */,
|
||||
|
|
@ -1479,7 +1480,6 @@
|
|||
D93068DB2B81C85E0066FBC8 /* PowerConfig.swift in Sources */,
|
||||
D93068D32B8129510066FBC8 /* MessageContextMenuItems.swift in Sources */,
|
||||
DD8EBF43285058FA00426DCA /* DisplayConfig.swift in Sources */,
|
||||
DD964FC42974767D007C176F /* MapViewFitExtension.swift in Sources */,
|
||||
BCE2D3C72C7B0D0A008E6199 /* ShortcutsProvider.swift in Sources */,
|
||||
DD47E3D626F17ED900029299 /* CircleText.swift in Sources */,
|
||||
DDC2E18F26CE25FE0042C5E4 /* ContentView.swift in Sources */,
|
||||
|
|
@ -1488,18 +1488,20 @@
|
|||
DD1BD0EE2C603C91008C0C70 /* CustomFormatters.swift in Sources */,
|
||||
DD0BE3102CB9FDC4000BA445 /* DetectionSensorEnums.swift in Sources */,
|
||||
DDD9E4E4284B208E003777C5 /* UserEntityExtension.swift in Sources */,
|
||||
233E99CB2D85AAA900CC3A77 /* RainfallCompactWidget.swift in Sources */,
|
||||
DD2553592855B52700E55709 /* PositionConfig.swift in Sources */,
|
||||
DD97E96828EFE9A00056DDA4 /* About.swift in Sources */,
|
||||
DDDB444029F79AB000EE2349 /* UserDefaults.swift in Sources */,
|
||||
233E99BA2D849C7000CC3A77 /* PressureCompactWidget.swift in Sources */,
|
||||
DDB6ABE028B13AC700384BA1 /* DeviceEnums.swift in Sources */,
|
||||
DD86D40C287F401000BAEB7A /* SaveChannelQRCode.swift in Sources */,
|
||||
D93068DD2B81CA820066FBC8 /* ConfigHeader.swift in Sources */,
|
||||
251926872C3BAE2200249DF5 /* NodeAlertsButton.swift in Sources */,
|
||||
DDA1C48E28DB49D3009933EC /* ChannelRoles.swift in Sources */,
|
||||
D9BC22DB2B7DE8E2006A37D5 /* TileDownloadStatus.swift in Sources */,
|
||||
DDD5BB092C285DDC007E03CA /* AppLog.swift in Sources */,
|
||||
BC5EBA3C2D002A2000C442FF /* MessageNodeIntent.swift in Sources */,
|
||||
DD8ED9C8289CE4B900B3B0AB /* RoutingError.swift in Sources */,
|
||||
233E99C52D84A0B600CC3A77 /* CompactWidget.swift in Sources */,
|
||||
DDC1B81A2AB5377B00C71E39 /* MessagesTips.swift in Sources */,
|
||||
DD964FC62975DBFD007C176F /* QueryCoreData.swift in Sources */,
|
||||
DDB75A112A059258006ED576 /* Url.swift in Sources */,
|
||||
|
|
@ -1535,6 +1537,7 @@
|
|||
8D3F8A3F2D44BB02009EAAA4 /* PowerMetrics.swift in Sources */,
|
||||
2519268A2C3BB1B200249DF5 /* ExchangePositionsButton.swift in Sources */,
|
||||
DD86D40A287F04F100BAEB7A /* InvalidVersion.swift in Sources */,
|
||||
233E99BE2D849D3200CC3A77 /* RadiationCompactWidget.swift in Sources */,
|
||||
DDD94A502845C8F5004A87A0 /* DateTimeText.swift in Sources */,
|
||||
DDB6ABE228B13FB500384BA1 /* PositionConfigEnums.swift in Sources */,
|
||||
DD994B69295F88B60013760A /* IntervalEnums.swift in Sources */,
|
||||
|
|
@ -1566,12 +1569,14 @@
|
|||
DDDB26442AAC0206003AFCB7 /* NodeDetail.swift in Sources */,
|
||||
DD77093F2AA1B146007A8BF0 /* UIColor.swift in Sources */,
|
||||
DDF6B2482A9AEBF500BA6931 /* StoreForwardConfig.swift in Sources */,
|
||||
233E99C72D84A70900CC3A77 /* SoilCompactWidgets.swift in Sources */,
|
||||
BCE2D3C92C7C377F008E6199 /* FactoryResetNodeIntent.swift in Sources */,
|
||||
DD93800B2BA3F968008BEC06 /* NodeMapContent.swift in Sources */,
|
||||
DD41582A28585C32009B0E59 /* RangeTestConfig.swift in Sources */,
|
||||
DD1925B728CDA5A400720036 /* CannedMessagesConfigEnums.swift in Sources */,
|
||||
DDDB444429F8A8DD00EE2349 /* Float.swift in Sources */,
|
||||
DDAB580F2B0DAFBC00147258 /* LocationEntityExtension.swift in Sources */,
|
||||
233E99BC2D849C8C00CC3A77 /* WindCompactWidget.swift in Sources */,
|
||||
B3E905B12B71F7F300654D07 /* TextMessageField.swift in Sources */,
|
||||
BC6B45FF2CB2F98900723CEB /* SaveChannelSettingsIntent.swift in Sources */,
|
||||
D93068D72B8146690066FBC8 /* MessageText.swift in Sources */,
|
||||
|
|
@ -1800,7 +1805,7 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.5.20;
|
||||
MARKETING_VERSION = 2.5.22;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
|
|
@ -1834,7 +1839,7 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.5.20;
|
||||
MARKETING_VERSION = 2.5.22;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
|
|
@ -1866,7 +1871,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.5.20;
|
||||
MARKETING_VERSION = 2.5.22;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
|
|
@ -1899,7 +1904,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.5.20;
|
||||
MARKETING_VERSION = 2.5.22;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
|
|
@ -1968,14 +1973,6 @@
|
|||
minimumVersion = 1.26.0;
|
||||
};
|
||||
};
|
||||
C9697FA327933B8C00250207 /* XCRemoteSwiftPackageReference "SQLite.swift" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/stephencelis/SQLite.swift.git";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 0.9.2;
|
||||
};
|
||||
};
|
||||
DD0D3D202A55CEB10066DB71 /* XCRemoteSwiftPackageReference "CocoaMQTT" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/emqx/CocoaMQTT";
|
||||
|
|
@ -1995,11 +1992,6 @@
|
|||
isa = XCSwiftPackageProductDependency;
|
||||
productName = MeshtasticProtobufs;
|
||||
};
|
||||
C9697FA427933B8C00250207 /* SQLite */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = C9697FA327933B8C00250207 /* XCRemoteSwiftPackageReference "SQLite.swift" */;
|
||||
productName = SQLite;
|
||||
};
|
||||
DD0D3D212A55CEB10066DB71 /* CocoaMQTT */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = DD0D3D202A55CEB10066DB71 /* XCRemoteSwiftPackageReference "CocoaMQTT" */;
|
||||
|
|
@ -2011,6 +2003,7 @@
|
|||
DD3CC6BA28E366DF00FA9159 /* Meshtastic.xcdatamodeld */ = {
|
||||
isa = XCVersionGroup;
|
||||
children = (
|
||||
233E99B32D84969500CC3A77 /* MeshtasticDataModelV 50.xcdatamodel */,
|
||||
8D3F8A3D2D44B137009EAAA4 /* MeshtasticDataModelV 49.xcdatamodel */,
|
||||
DDA28B1B2D32C89200EF726F /* MeshtasticDataModelV 48.xcdatamodel */,
|
||||
DDDFE7402D0D4A070044463C /* MeshtasticDataModelV 47.xcdatamodel */,
|
||||
|
|
@ -2061,7 +2054,7 @@
|
|||
DD5D0A9A2931AD6B00F7EA61 /* MeshtasticDataModelV2.xcdatamodel */,
|
||||
DD3CC6BB28E366DF00FA9159 /* MeshtasticDataModel.xcdatamodel */,
|
||||
);
|
||||
currentVersion = 8D3F8A3D2D44B137009EAAA4 /* MeshtasticDataModelV 49.xcdatamodel */;
|
||||
currentVersion = 233E99B32D84969500CC3A77 /* MeshtasticDataModelV 50.xcdatamodel */;
|
||||
name = Meshtastic.xcdatamodeld;
|
||||
path = Meshtastic/Meshtastic.xcdatamodeld;
|
||||
sourceTree = "<group>";
|
||||
|
|
|
|||
|
|
@ -1,51 +0,0 @@
|
|||
{
|
||||
"originHash" : "2d0b85469585b0d6079eac292d63864096062c24848a49380b9d9727f0ceb96c",
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "cocoamqtt",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/emqx/CocoaMQTT",
|
||||
"state" : {
|
||||
"revision" : "85387a2478551ad84f39be8a3c8587d34dd2bcf5",
|
||||
"version" : "2.1.5"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "mqttcocoaasyncsocket",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/leeway1208/MqttCocoaAsyncSocket",
|
||||
"state" : {
|
||||
"revision" : "ce3e18607fd01079495f86ff6195d8a3ca469f73",
|
||||
"version" : "1.0.8"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "sqlite.swift",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/stephencelis/SQLite.swift.git",
|
||||
"state" : {
|
||||
"revision" : "7a2e3cd27de56f6d396e84f63beefd0267b55ccb",
|
||||
"version" : "0.14.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "starscream",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/daltoniam/Starscream.git",
|
||||
"state" : {
|
||||
"revision" : "a063fda2b8145a231953c20e7a646be254365396",
|
||||
"version" : "3.1.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-protobuf",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-protobuf.git",
|
||||
"state" : {
|
||||
"revision" : "9f0c76544701845ad98716f3f6a774a892152bcb",
|
||||
"version" : "1.26.0"
|
||||
}
|
||||
}
|
||||
],
|
||||
"version" : 3
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
<?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/>
|
||||
</plist>
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"originHash" : "1571e0d09fede5d57a2c415019f30868d90fde5a53a863cc277593881c2dc4a5",
|
||||
"originHash" : "a3033aea781828906c453276e3723177901ce64df5757de7ada28c854c9662eb",
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "cocoamqtt",
|
||||
|
|
@ -19,15 +19,6 @@
|
|||
"version" : "1.0.8"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "sqlite.swift",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/stephencelis/SQLite.swift.git",
|
||||
"state" : {
|
||||
"revision" : "a95fc6df17d108bd99210db5e8a9bac90fe984b8",
|
||||
"version" : "0.15.3"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "starscream",
|
||||
"kind" : "remoteSourceControl",
|
||||
|
|
@ -42,8 +33,8 @@
|
|||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-protobuf.git",
|
||||
"state" : {
|
||||
"revision" : "edb6ed4919f7756157fe02f2552b7e3850a538e5",
|
||||
"version" : "1.28.1"
|
||||
"revision" : "d72aed98f8253ec1aa9ea1141e28150f408cf17f",
|
||||
"version" : "1.29.0"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ class AppIntentErrors {
|
|||
var localizedStringResource: LocalizedStringResource {
|
||||
switch self {
|
||||
case let .message(message):
|
||||
Logger.services.error("App Intent: \(message)")
|
||||
Logger.services.error("App Intent: \(message,privacy: .public)")
|
||||
return "Error: \(message)"
|
||||
case .notConnected:
|
||||
Logger.services.error("App Intent: No Connected Node")
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"symbols" : [
|
||||
{
|
||||
"filename" : "soilMoisture.variable.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,366 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!--Generator: Apple Native CoreSVG 341-->
|
||||
|
||||
<svg
|
||||
version="1.1"
|
||||
viewBox="0 0 3300 2200"
|
||||
id="svg681"
|
||||
sodipodi:docname="groundmoisture.variable.svg"
|
||||
inkscape:version="1.2.1 (9c6d41e, 2022-07-14)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs685" />
|
||||
<sodipodi:namedview
|
||||
id="namedview683"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="false"
|
||||
inkscape:zoom="0.55606061"
|
||||
inkscape:cx="1991.6894"
|
||||
inkscape:cy="935.14986"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1440"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="Symbols"
|
||||
showguides="false" />
|
||||
<g
|
||||
id="Notes">
|
||||
<rect
|
||||
height="2200"
|
||||
id="artboard"
|
||||
style="fill:white;opacity:1"
|
||||
width="3300"
|
||||
x="0"
|
||||
y="0" />
|
||||
<line
|
||||
style="fill:none;stroke:black;opacity:1;stroke-width:0.5;"
|
||||
x1="263"
|
||||
x2="3036"
|
||||
y1="292"
|
||||
y2="292"
|
||||
id="line562" />
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;font-weight:bold;"
|
||||
transform="matrix(1 0 0 1 263 322)"
|
||||
id="text564">Weight/Scale Variations</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;"
|
||||
transform="matrix(1 0 0 1 559.711 322)"
|
||||
id="text566">Ultralight</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;"
|
||||
transform="matrix(1 0 0 1 856.422 322)"
|
||||
id="text568">Thin</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;"
|
||||
transform="matrix(1 0 0 1 1153.13 322)"
|
||||
id="text570">Light</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;"
|
||||
transform="matrix(1 0 0 1 1449.84 322)"
|
||||
id="text572">Regular</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;"
|
||||
transform="matrix(1 0 0 1 1746.56 322)"
|
||||
id="text574">Medium</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;"
|
||||
transform="matrix(1 0 0 1 2043.27 322)"
|
||||
id="text576">Semibold</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;"
|
||||
transform="matrix(1 0 0 1 2339.98 322)"
|
||||
id="text578">Bold</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;"
|
||||
transform="matrix(1 0 0 1 2636.69 322)"
|
||||
id="text580">Heavy</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;"
|
||||
transform="matrix(1 0 0 1 2933.4 322)"
|
||||
id="text582">Black</text>
|
||||
<line
|
||||
style="fill:none;stroke:black;opacity:1;stroke-width:0.5;"
|
||||
x1="263"
|
||||
x2="3036"
|
||||
y1="1903"
|
||||
y2="1903"
|
||||
id="line584" />
|
||||
<g
|
||||
transform="matrix(0.2 0 0 0.2 263 1933)"
|
||||
id="g588">
|
||||
<path
|
||||
d="m46.2402 4.15039c21.7773 0 39.4531-17.627 39.4531-39.4043s-17.6758-39.4043-39.4531-39.4043c-21.7285 0-39.4043 17.627-39.4043 39.4043s17.6758 39.4043 39.4043 39.4043Zm0-7.42188c-17.6758 0-31.9336-14.3066-31.9336-31.9824s14.2578-31.9824 31.9336-31.9824 31.9824 14.3066 31.9824 31.9824-14.3066 31.9824-31.9824 31.9824Zm-17.9688-31.9824c0 2.14844 1.51367 3.61328 3.75977 3.61328h10.498v10.5957c0 2.19727 1.46484 3.71094 3.61328 3.71094 2.24609 0 3.71094-1.51367 3.71094-3.71094v-10.5957h10.5957c2.19727 0 3.71094-1.46484 3.71094-3.61328 0-2.19727-1.51367-3.71094-3.71094-3.71094h-10.5957v-10.5469c0-2.24609-1.46484-3.75977-3.71094-3.75977-2.14844 0-3.61328 1.51367-3.61328 3.75977v10.5469h-10.498c-2.24609 0-3.75977 1.51367-3.75977 3.71094Z"
|
||||
id="path586" />
|
||||
</g>
|
||||
<g
|
||||
transform="matrix(0.2 0 0 0.2 281.506 1933)"
|
||||
id="g592">
|
||||
<path
|
||||
d="m58.5449 14.5508c27.4902 0 49.8047-22.3145 49.8047-49.8047s-22.3145-49.8047-49.8047-49.8047-49.8047 22.3145-49.8047 49.8047 22.3145 49.8047 49.8047 49.8047Zm0-8.30078c-22.9492 0-41.5039-18.5547-41.5039-41.5039s18.5547-41.5039 41.5039-41.5039 41.5039 18.5547 41.5039 41.5039-18.5547 41.5039-41.5039 41.5039Zm-22.6562-41.5039c0 2.39258 1.66016 4.00391 4.15039 4.00391h14.3555v14.4043c0 2.44141 1.66016 4.15039 4.05273 4.15039 2.44141 0 4.15039-1.66016 4.15039-4.15039v-14.4043h14.4043c2.44141 0 4.15039-1.61133 4.15039-4.00391 0-2.44141-1.70898-4.15039-4.15039-4.15039h-14.4043v-14.3555c0-2.49023-1.70898-4.19922-4.15039-4.19922-2.39258 0-4.05273 1.70898-4.05273 4.19922v14.3555h-14.3555c-2.49023 0-4.15039 1.70898-4.15039 4.15039Z"
|
||||
id="path590" />
|
||||
</g>
|
||||
<g
|
||||
transform="matrix(0.2 0 0 0.2 304.924 1933)"
|
||||
id="g596">
|
||||
<path
|
||||
d="m74.8535 28.3203c35.1074 0 63.623-28.4668 63.623-63.5742s-28.5156-63.623-63.623-63.623-63.5742 28.5156-63.5742 63.623 28.4668 63.5742 63.5742 63.5742Zm0-9.08203c-30.127 0-54.4922-24.3652-54.4922-54.4922s24.3652-54.4922 54.4922-54.4922 54.4922 24.3652 54.4922 54.4922-24.3652 54.4922-54.4922 54.4922Zm-28.8574-54.4922c0 2.58789 1.85547 4.39453 4.58984 4.39453h19.7266v19.7754c0 2.68555 1.85547 4.58984 4.44336 4.58984 2.68555 0 4.54102-1.85547 4.54102-4.58984v-19.7754h19.7754c2.68555 0 4.58984-1.80664 4.58984-4.39453 0-2.73438-1.85547-4.58984-4.58984-4.58984h-19.7754v-19.7266c0-2.73438-1.85547-4.63867-4.54102-4.63867-2.58789 0-4.44336 1.9043-4.44336 4.63867v19.7266h-19.7266c-2.73438 0-4.58984 1.85547-4.58984 4.58984Z"
|
||||
id="path594" />
|
||||
</g>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;font-weight:bold;"
|
||||
transform="matrix(1 0 0 1 263 1953)"
|
||||
id="text598">Design Variations</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;"
|
||||
transform="matrix(1 0 0 1 263 1971)"
|
||||
id="text600">Symbols are supported in up to nine weights and three scales.</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;"
|
||||
transform="matrix(1 0 0 1 263 1989)"
|
||||
id="text602">For optimal layout with text and other symbols, vertically align</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;"
|
||||
transform="matrix(1 0 0 1 263 2007)"
|
||||
id="text604">symbols with the adjacent text.</text>
|
||||
<line
|
||||
style="fill:none;stroke:#00AEEF;stroke-width:0.5;opacity:1.0;"
|
||||
x1="776"
|
||||
x2="776"
|
||||
y1="1919"
|
||||
y2="1933"
|
||||
id="line606" />
|
||||
<g
|
||||
transform="matrix(0.2 0 0 0.2 776 1933)"
|
||||
id="g610">
|
||||
<path
|
||||
d="m16.5527 0.78125c2.58789 0 3.85742-0.976562 4.78516-3.71094l6.29883-17.2363h28.8086l6.29883 17.2363c0.927734 2.73438 2.19727 3.71094 4.73633 3.71094 2.58789 0 4.24805-1.5625 4.24805-4.00391 0-0.830078-0.146484-1.61133-0.537109-2.63672l-22.9004-60.9863c-1.12305-2.97852-3.125-4.49219-6.25-4.49219-3.02734 0-5.07812 1.46484-6.15234 4.44336l-22.9004 61.084c-0.390625 1.02539-0.537109 1.80664-0.537109 2.63672 0 2.44141 1.5625 3.95508 4.10156 3.95508Zm13.4766-28.3691 11.8652-32.8613h0.244141l11.8652 32.8613Z"
|
||||
id="path608" />
|
||||
</g>
|
||||
<line
|
||||
style="fill:none;stroke:#00AEEF;stroke-width:0.5;opacity:1.0;"
|
||||
x1="792.836"
|
||||
x2="792.836"
|
||||
y1="1919"
|
||||
y2="1933"
|
||||
id="line612" />
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;font-weight:bold;"
|
||||
transform="matrix(1 0 0 1 776 1953)"
|
||||
id="text614">Margins</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;"
|
||||
transform="matrix(1 0 0 1 776 1971)"
|
||||
id="text616">Leading and trailing margins on the left and right side of each symbol</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;"
|
||||
transform="matrix(1 0 0 1 776 1989)"
|
||||
id="text618">can be adjusted by modifying the x-location of the margin guidelines.</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;"
|
||||
transform="matrix(1 0 0 1 776 2007)"
|
||||
id="text620">Modifications are automatically applied proportionally to all</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;"
|
||||
transform="matrix(1 0 0 1 776 2025)"
|
||||
id="text622">scales and weights.</text>
|
||||
<g
|
||||
transform="matrix(0.2 0 0 0.2 1289 1933)"
|
||||
id="g626">
|
||||
<path
|
||||
d="m14.209 9.32617 8.49609 8.54492c4.29688 4.3457 9.22852 4.05273 13.8672-1.07422l53.4668-58.9355-4.83398-4.88281-53.0762 58.3984c-1.75781 2.00195-3.41797 2.49023-5.76172 0.146484l-5.85938-5.81055c-2.34375-2.29492-1.80664-4.00391 0.195312-5.81055l57.373-54.0039-4.88281-4.83398-57.959 54.4434c-4.93164 4.58984-5.32227 9.47266-1.02539 13.8184Zm32.0801-90.9668c-2.09961 2.05078-2.24609 4.93164-1.07422 6.88477 1.17188 1.80664 3.4668 2.97852 6.68945 2.14844 7.32422-1.70898 14.9414-2.00195 22.0703 2.68555l-2.92969 7.27539c-1.70898 4.15039-0.830078 7.08008 1.85547 9.81445l11.4746 11.5723c2.44141 2.44141 4.49219 2.53906 7.32422 2.05078l5.32227-0.976562 3.32031 3.36914-0.195312 2.7832c-0.195312 2.49023 0.439453 4.39453 2.88086 6.78711l3.80859 3.71094c2.39258 2.39258 5.46875 2.53906 7.8125 0.195312l14.5508-14.5996c2.34375-2.34375 2.24609-5.32227-0.146484-7.71484l-3.85742-3.80859c-2.39258-2.39258-4.24805-3.17383-6.64062-2.97852l-2.88086 0.244141-3.22266-3.17383 1.2207-5.61523c0.634766-2.83203-0.146484-5.0293-3.07617-7.95898l-10.9863-10.9375c-16.6992-16.6016-38.8672-16.2109-53.3203-1.75781Zm7.4707 1.85547c12.1582-8.88672 28.6133-7.37305 39.7461 3.75977l12.1582 12.0605c1.17188 1.17188 1.36719 2.09961 1.02539 3.80859l-1.61133 7.42188 7.51953 7.42188 4.93164-0.292969c1.26953-0.0488281 1.66016 0.0488281 2.63672 1.02539l2.88086 2.88086-12.207 12.207-2.88086-2.88086c-0.976562-0.976562-1.12305-1.36719-1.07422-2.68555l0.341797-4.88281-7.4707-7.42188-7.61719 1.26953c-1.61133 0.341797-2.34375 0.195312-3.56445-0.976562l-10.0098-10.0098c-1.26953-1.17188-1.41602-2.00195-0.634766-3.85742l4.39453-10.4492c-7.8125-7.27539-17.9688-10.4004-28.125-7.42188-0.78125 0.195312-1.07422-0.439453-0.439453-0.976562Z"
|
||||
id="path624" />
|
||||
</g>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;font-weight:bold;"
|
||||
transform="matrix(1 0 0 1 1289 1953)"
|
||||
id="text628">Exporting</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;"
|
||||
transform="matrix(1 0 0 1 1289 1971)"
|
||||
id="text630">Symbols should be outlined when exporting to ensure the</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;"
|
||||
transform="matrix(1 0 0 1 1289 1989)"
|
||||
id="text632">design is preserved when submitting to Xcode.</text>
|
||||
<text
|
||||
id="template-version"
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:end;"
|
||||
transform="matrix(1 0 0 1 3036 1933)">Template v.6.0</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:end;"
|
||||
transform="matrix(1 0 0 1 3036 1951)"
|
||||
id="text635">Requires Xcode 16 or greater</text>
|
||||
<text
|
||||
id="descriptive-name"
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:end;"
|
||||
transform="matrix(1 0 0 1 3036 1969)">Generated from thermometer.variable</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:end;"
|
||||
transform="matrix(1 0 0 1 3036 1987)"
|
||||
id="text638">Typeset at 100.0 points</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;"
|
||||
transform="matrix(1 0 0 1 263 726)"
|
||||
id="text640">Small</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;"
|
||||
transform="matrix(1 0 0 1 263 1156)"
|
||||
id="text642">Medium</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;"
|
||||
transform="matrix(1 0 0 1 263 1586)"
|
||||
id="text644">Large</text>
|
||||
</g>
|
||||
<g
|
||||
id="Guides">
|
||||
<g
|
||||
id="H-reference"
|
||||
style="fill:#27AAE1;stroke:none;"
|
||||
transform="matrix(1 0 0 1 339 696)">
|
||||
<path
|
||||
d="M0.993654 0L3.63775 0L29.3281-67.1323L30.0303-67.1323L30.0303-70.459L28.1226-70.459ZM11.6885-24.4799L46.9815-24.4799L46.2315-26.7285L12.4385-26.7285ZM55.1196 0L57.7637 0L30.6382-70.459L29.4326-70.459L29.4326-67.1323Z"
|
||||
id="path647" />
|
||||
</g>
|
||||
<line
|
||||
id="Baseline-S"
|
||||
style="fill:none;stroke:#27AAE1;opacity:1;stroke-width:0.5;"
|
||||
x1="263"
|
||||
x2="3036"
|
||||
y1="696"
|
||||
y2="696" />
|
||||
<line
|
||||
id="Capline-S"
|
||||
style="fill:none;stroke:#27AAE1;opacity:1;stroke-width:0.5;"
|
||||
x1="263"
|
||||
x2="3036"
|
||||
y1="625.541"
|
||||
y2="625.541" />
|
||||
<g
|
||||
id="g654"
|
||||
style="fill:#27AAE1;stroke:none;"
|
||||
transform="matrix(1 0 0 1 339 1126)">
|
||||
<path
|
||||
d="M0.993654 0L3.63775 0L29.3281-67.1323L30.0303-67.1323L30.0303-70.459L28.1226-70.459ZM11.6885-24.4799L46.9815-24.4799L46.2315-26.7285L12.4385-26.7285ZM55.1196 0L57.7637 0L30.6382-70.459L29.4326-70.459L29.4326-67.1323Z"
|
||||
id="path652" />
|
||||
</g>
|
||||
<line
|
||||
id="Baseline-M"
|
||||
style="fill:none;stroke:#27AAE1;opacity:1;stroke-width:0.5;"
|
||||
x1="263"
|
||||
x2="3036"
|
||||
y1="1126"
|
||||
y2="1126" />
|
||||
<line
|
||||
id="Capline-M"
|
||||
style="fill:none;stroke:#27AAE1;opacity:1;stroke-width:0.5;"
|
||||
x1="263"
|
||||
x2="3036"
|
||||
y1="1055.54"
|
||||
y2="1055.54" />
|
||||
<g
|
||||
id="g660"
|
||||
style="fill:#27AAE1;stroke:none;"
|
||||
transform="matrix(1 0 0 1 339 1556)">
|
||||
<path
|
||||
d="M0.993654 0L3.63775 0L29.3281-67.1323L30.0303-67.1323L30.0303-70.459L28.1226-70.459ZM11.6885-24.4799L46.9815-24.4799L46.2315-26.7285L12.4385-26.7285ZM55.1196 0L57.7637 0L30.6382-70.459L29.4326-70.459L29.4326-67.1323Z"
|
||||
id="path658" />
|
||||
</g>
|
||||
<line
|
||||
id="Baseline-L"
|
||||
style="fill:none;stroke:#27AAE1;opacity:1;stroke-width:0.5;"
|
||||
x1="263"
|
||||
x2="3036"
|
||||
y1="1556"
|
||||
y2="1556" />
|
||||
<line
|
||||
id="Capline-L"
|
||||
style="fill:none;stroke:#27AAE1;opacity:1;stroke-width:0.5;"
|
||||
x1="263"
|
||||
x2="3036"
|
||||
y1="1485.54"
|
||||
y2="1485.54" />
|
||||
<line
|
||||
id="right-margin-Black-S"
|
||||
style="opacity:1;fill:none;stroke:#00aeef;stroke-width:0.5"
|
||||
x1="2994.5601"
|
||||
x2="2994.5601"
|
||||
y1="600.78497"
|
||||
y2="720.12097" />
|
||||
<line
|
||||
id="left-margin-Black-S"
|
||||
style="opacity:1;fill:none;stroke:#00aeef;stroke-width:0.5"
|
||||
x1="2882.24"
|
||||
x2="2882.24"
|
||||
y1="600.78497"
|
||||
y2="720.12097" />
|
||||
<line
|
||||
id="right-margin-Regular-S"
|
||||
style="opacity:1;fill:none;stroke:#00aeef;stroke-width:0.5"
|
||||
x1="1492"
|
||||
x2="1492"
|
||||
y1="600.78497"
|
||||
y2="720.12097" />
|
||||
<line
|
||||
id="left-margin-Regular-S"
|
||||
style="opacity:1;fill:none;stroke:#00aeef;stroke-width:0.5"
|
||||
x1="1395.6899"
|
||||
x2="1395.6899"
|
||||
y1="600.78497"
|
||||
y2="720.12097" />
|
||||
<line
|
||||
id="right-margin-Ultralight-S"
|
||||
style="opacity:1;fill:none;stroke:#00aeef;stroke-width:0.5"
|
||||
x1="605.70599"
|
||||
x2="605.70599"
|
||||
y1="600.78497"
|
||||
y2="720.12097" />
|
||||
<line
|
||||
id="left-margin-Ultralight-S"
|
||||
style="opacity:1;fill:none;stroke:#00aeef;stroke-width:0.5"
|
||||
x1="513.71698"
|
||||
x2="513.71698"
|
||||
y1="600.78497"
|
||||
y2="720.12097" />
|
||||
</g>
|
||||
<g
|
||||
id="Symbols">
|
||||
<g
|
||||
id="Black-S"
|
||||
transform="matrix(1 0 0 1 2898.24 696)"
|
||||
style="fill:#000000">
|
||||
<path
|
||||
id="path1811"
|
||||
style="fill:#000000;stroke:#000000;stroke-width:0.5;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 40.160224,10.512322 c 13.8183,0 24.9508,-10.88863047 24.9508,-24.560501 0,-7.1289 -3.32,-13.6719 -5.5172,-17.7734 l -8.545,-15.869199 c -2.1972,-4.1504 -6.2011,-6.5918 -10.8886,-6.5918 -4.6387,0 -8.6426,2.4414 -10.8399,6.543 l -8.4961,15.869099 c -2.2461,4.1504 -5.6152,10.6446 -5.6152,17.8223 0,13.67187053 11.1816,24.560501 24.9512,24.560501 z m -18.6035,-24.560501 c 0,-5.5176 2.6855,-10.8399 4.8339,-14.7949 l 8.4961,-15.869199 c 1.1231,-2.0508 2.9785,-3.2226 5.2735,-3.2226 2.2949,0 4.1504,1.123 5.2734,3.2226 l 8.5449,15.869199 c 2.0996,3.955 4.7852,9.2773 4.7852,14.7949 0,10.1074205 -8.252,18.2128805 -18.6035,18.2128805 -10.3028,0 -18.6035,-8.10546 -18.6035,-18.2128805 z m 18.6035,6.5917905 c -4.9805,0 -8.9356,-3.8085895 -8.9356,-8.5448905 0,-3.2227 1.6602,-6.2989 3.7598,-10.2539 l 4.7851,-8.935599 c 0.1954,-0.3906 0.6348,-0.3906 0.8301,0 l 4.7364,8.935599 c 2.0996,3.955 3.7597,7.0312 3.7597,10.2539 0,4.736301 -3.9062,8.5448905 -8.9355,8.5448905 z M 93.819567,-0.549 c 0,2.346 -1.91,4.25 -4.25,4.25 -2.35,0 -4.25,-1.904 -4.25,-4.25 0,-0.295 -0.01,-0.589 -0.03,-0.881 -0.15,-2.341 1.62,-4.366 3.96,-4.519 2.34,-0.154 4.37,1.621 4.52,3.962 0.03,0.477 0.05,0.956 0.05,1.438 z m -5.03,-14.361 c 1.4,1.882 1.01,4.547 -0.87,5.947 -1.88,1.401 -4.55,1.011 -5.95,-0.871 -0.38,-0.515 -0.79,-1.02 -1.23,-1.516 -1.56,-1.755 -1.4,-4.443 0.36,-6 1.75,-1.556 4.44,-1.395 6,0.36 0.6,0.68 1.16,1.374 1.69,2.08 z m -11.75,-10.145 c 2.02,1.186 2.7,3.792 1.52,5.815 -1.19,2.024 -3.79,2.704 -5.82,1.518 -0.6,-0.352 -1.22,-0.695 -1.85,-1.027 -2.08,-1.088 -2.88,-3.659 -1.79,-5.737 1.09,-2.077 3.66,-2.881 5.73,-1.792 0.76,0.395 1.5,0.803 2.21,1.223 z M 5.8395672,-26.278 c 2.08,-1.089 4.6499998,-0.285 5.7399998,1.792 1.08,2.078 0.28,4.649 -1.7999998,5.737 -0.63,0.332 -1.25,0.675 -1.85,1.027 -2.02,1.186 -4.63,0.506 -5.82,-1.518 -1.17999998,-2.023 -0.5,-4.629 1.52,-5.815 0.72,-0.42 1.45,-0.828 2.21,-1.223 z m -12.26,9.288 c 1.55,-1.755 4.24,-1.916 6.00000002,-0.36 1.74999998,1.557 1.90999998,4.245 0.36,6 -0.44,0.496 -0.86,1.001 -1.24000002,1.516 -1.4,1.882 -4.06,2.272 -5.95,0.871 -1.88,-1.4 -2.27,-4.065 -0.87,-5.946 0.53,-0.707 1.09,-1.401 1.7,-2.081 z m -6.6800002,15.003 c 0.15,-2.341 2.18,-4.116 4.5200002,-3.962 2.34,0.153 4.12,2.178 3.96,4.519 -0.02,0.292 -0.03,0.586 -0.03,0.881 0,2.346 -1.9,4.25 -4.25,4.25 -2.3400002,0 -4.2500002,-1.904 -4.2500002,-4.25 0,-0.482 0.02,-0.961 0.05,-1.438 z" />
|
||||
</g>
|
||||
<g
|
||||
id="Regular-S"
|
||||
transform="matrix(1 0 0 1 1419.69 696)"
|
||||
style="fill:#000000">
|
||||
<path
|
||||
id="path1814"
|
||||
style="fill:#000000;stroke:#000000;stroke-opacity:1;stroke-width:0.5;stroke-dasharray:none"
|
||||
d="m 40.829821,-13.588987 c 0,-4.9805 -2.4903,-9.8633 -4.5899,-13.7695 l -8.3984,-15.5762 c -0.9766,-1.8555 -1.8555,-2.6855 -3.711,-2.6855 -1.8554,0 -2.7343,0.83 -3.7109,2.6855 l -8.3496,15.5762 c -2.0508,3.9062 -4.5410003,8.789 -4.5410003,13.7695 0,8.9843901 7.4218003,16.2597781 16.6015003,16.2597781 9.2774,0 16.6993,-7.275388 16.6993,-16.2597781 z m -16.6993,21.9726701 c -12.3047,0 -22.3144003,-9.76563 -22.3144003,-21.9726701 0,-6.4453 3.125,-12.4023 5.2246,-16.4062 l 8.3496003,-15.6739 c 1.9043,-3.5156 4.6387,-5.664 8.7402,-5.664 4.1504,0 6.8848,2.1484 8.7891,5.664 l 8.3984,15.625 c 2.0996,4.0528 5.1758,10.0098 5.1758,16.4551 0,12.2070401 -10.0098,21.9726701 -22.3633,21.9726701 z m 46.31122,-10.4926827 c 0,1.518 -1.23,2.75 -2.75,2.75 -1.51,0 -2.75,-1.232 -2.75,-2.75 0,-0.328 -0.01,-0.654 -0.03,-0.979 -0.1,-1.515 1.05,-2.825 2.57,-2.924 1.51,-0.1 2.82,1.049 2.92,2.564 0.03,0.444 0.04,0.891 0.04,1.339 z m -4.73,-13.4640004 c 0.91,1.217 0.66,2.941 -0.56,3.848 -1.22,0.906 -2.94,0.653 -3.85,-0.564 -0.41,-0.549 -0.85,-1.088 -1.32,-1.616 -1,-1.136 -0.9,-2.875 0.24,-3.882 1.13,-1.007 2.87,-0.903 3.88,0.232 0.57,0.648 1.11,1.309 1.61,1.982 z m -11.3,-9.748 c 1.31,0.768 1.75,2.454 0.98,3.763 -0.77,1.31 -2.45,1.75 -3.76,0.982 -0.63,-0.364 -1.26,-0.718 -1.92,-1.061 -1.34,-0.704 -1.86,-2.368 -1.16,-3.712 0.7,-1.345 2.37,-1.864 3.71,-1.16 0.74,0.384 1.45,0.78 2.15,1.188 z m -58.7500004,-1.188 c 1.34,-0.704 3.01,-0.185 3.71,1.16 0.71,1.344 0.19,3.008 -1.16,3.712 -0.65,0.343 -1.29,0.697 -1.91,1.061 -1.31,0.768 -3,0.328 -3.77,-0.982 -0.76,-1.309 -0.32,-2.995 0.98,-3.763 0.7,-0.408 1.42,-0.804 2.15,-1.188 z m -11.8399996,8.954 c 1.01,-1.135 2.75,-1.239 3.89,-0.232 1.13,1.007 1.24,2.746 0.23,3.882 -0.47,0.528 -0.91,1.067 -1.32,1.616 -0.9,1.217 -2.63,1.47 -3.85,0.564 -1.21,-0.907 -1.47,-2.631 -0.56,-3.848 0.5,-0.673 1.04,-1.334 1.61,-1.982 z m -6.3,14.1070004 c 0.1,-1.515 1.41,-2.664 2.93,-2.564 1.51,0.099 2.66,1.409 2.56,2.924 -0.02,0.325 -0.03,0.651 -0.03,0.979 0,1.518 -1.23,2.75 -2.75,2.75 -1.52,0 -2.75,-1.232 -2.75,-2.75 0,-0.448 0.01,-0.895 0.04,-1.339 z m 46.60878,0.4059027 c -6.0547,0 -10.8886,-4.63867 -10.8886,-10.5468901 0,-3.6621 1.8554,-7.1777 3.9062,-11.084 l 6.6895,-12.4023 c 0.1953,-0.3418 0.4882,-0.3418 0.6836,0 l 6.5918,12.4023 c 2.0507,3.9063 4.0039,7.4219 4.0039,11.084 0,5.9082201 -4.8829,10.5468901 -10.9864,10.5468901 z" />
|
||||
</g>
|
||||
<g
|
||||
id="Ultralight-S"
|
||||
transform="matrix(1 0 0 1 531.717 696)"
|
||||
style="fill:#000000">
|
||||
<path
|
||||
id="path1817"
|
||||
style="fill:#000000;stroke:#000000;stroke-width:0.5;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 8.8343368,-13.746838 c 0,-5.673299 2.7617002,-11.221699 5.0883002,-15.497999 l 9.1216,-17.0816 c 1.0869,-1.9716 2.8223,-3.0303 4.9712,-3.0303 2.1524,0 3.8878,1.0587 4.9747,3.1212 l 9.0796,16.9873 c 2.372,4.2797 5.0849,9.8281 5.0849,15.501399 0,10.4814908 -8.5112,18.7940007 -19.1392,18.7940007 -10.6245,0 -19.1811002,-8.3125099 -19.1811002,-18.7940007 z M 28.015437,1.9680428 c 8.9595,0 16.1089,-6.9575205 16.1089,-15.7148808 0,-4.934999 -2.4448,-9.863299 -4.726,-14.087399 l -9.125,-16.9838 c -0.5679,-1.0381 -1.2652,-1.4595 -2.2579,-1.4595 -1.0381,0 -1.7353,0.4214 -2.2578,1.414 l -9.1216,17.0293 c -2.2778,4.2241 -4.7226,9.1524 -4.7226,14.087399 0,8.7573603 7.1494,15.7148808 16.102,15.7148808 z m 0,-2.12551995 c -7.7802,0 -13.9765,-6.00096055 -13.9765,-13.58936085 0,-4.433999 2.2642,-8.903299 4.4966,-13.081999 l 8.9599,-16.7163 c 0.2407,-0.478 0.8062,-0.478 1.0469,0 l 8.9531,16.7163 c 2.2324,4.1787 4.458,8.648 4.458,13.081999 0,7.5884003 -6.1543,13.58936085 -13.938,13.58936085 z M 70.343736,-2.1090003 c 0,-0.4319999 -0.014,-0.8619999 -0.042,-1.2899999 -0.073,-1.102 -1.026,-1.937 -2.127,-1.865 -1.101,0.072 -1.937,1.025 -1.865,2.127 0.023,0.341 0.034,0.684 0.034,1.0279999 0,1.104 0.896,2.0000001 2,2.0000001 1.104,0 2,-0.8960001 2,-2.0000001 z M 65.759736,-15.126 c -0.488,-0.655 -1.013,-1.3 -1.573,-1.931 -0.732,-0.826 -1.997,-0.902 -2.823,-0.169 -0.826,0.732 -0.902,1.997 -0.169,2.823 0.483,0.545 0.936,1.1 1.357,1.666 0.659,0.885 1.913,1.069 2.798,0.41 0.886,-0.659 1.07,-1.913 0.41,-2.799 z m -11.082,-9.548 c -0.686,-0.402 -1.39,-0.792 -2.113,-1.171 -0.978,-0.512 -2.187,-0.134 -2.7,0.844 -0.512,0.978 -0.134,2.187 0.844,2.7 0.666,0.348 1.315,0.708 1.946,1.078 0.953,0.558 2.179,0.238 2.737,-0.714 0.558,-0.952 0.238,-2.179 -0.714,-2.737 z M 1.6577364,-25.845 c -0.72300001,0.379 -1.42800001,0.769 -2.11300001,1.171 -0.95299999,0.558 -1.27299999,1.785 -0.71399999,2.737 0.55799999,0.952 1.78399999,1.272 2.736,0.714 0.632,-0.37 1.281,-0.73 1.947,-1.078 0.978,-0.513 1.356,-1.722 0.843,-2.7 -0.512,-0.978 -1.722,-1.356 -2.699,-0.844 z m -11.622,8.788 c -0.5600004,0.631 -1.0850004,1.276 -1.5740004,1.931 -0.659,0.886 -0.475,2.14 0.41,2.799 0.886,0.659 2.1400004,0.476 2.7990004,-0.41 0.421,-0.566 0.874,-1.121 1.357,-1.666 0.733,-0.826 0.657,-2.091 -0.169,-2.823 -0.826,-0.733 -2.091,-0.657 -2.823,0.169 z m -6.1150004,13.6579998 c -0.028,0.428 -0.042,0.858 -0.042,1.2899999 0,1.104 0.896,2.0000001 2,2.0000001 1.104,0 2,-0.8960001 2,-2.0000001 0,-0.3439999 0.011,-0.6869999 0.033,-1.0279999 0.073,-1.102 -0.763,-2.055 -1.864,-2.127 -1.102,-0.072 -2.055,0.763 -2.127,1.865 z" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 24 KiB |
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"symbols" : [
|
||||
{
|
||||
"filename" : "soilTemp.variable.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,363 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!--Generator: Apple Native CoreSVG 341-->
|
||||
|
||||
<svg
|
||||
version="1.1"
|
||||
viewBox="0 0 3300 2200"
|
||||
id="svg681"
|
||||
sodipodi:docname="groundtemp.variable.svg"
|
||||
inkscape:version="1.2.1 (9c6d41e, 2022-07-14)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs685" />
|
||||
<sodipodi:namedview
|
||||
id="namedview683"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="false"
|
||||
inkscape:zoom="2.3396954"
|
||||
inkscape:cx="2915.5504"
|
||||
inkscape:cy="705.00629"
|
||||
inkscape:window-width="1390"
|
||||
inkscape:window-height="1205"
|
||||
inkscape:window-x="65"
|
||||
inkscape:window-y="150"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="Guides"
|
||||
showguides="false" />
|
||||
<g
|
||||
id="Notes">
|
||||
<rect
|
||||
height="2200"
|
||||
id="artboard"
|
||||
style="fill:white;opacity:1"
|
||||
width="3300"
|
||||
x="0"
|
||||
y="0" />
|
||||
<line
|
||||
style="fill:none;stroke:black;opacity:1;stroke-width:0.5;"
|
||||
x1="263"
|
||||
x2="3036"
|
||||
y1="292"
|
||||
y2="292"
|
||||
id="line562" />
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;font-weight:bold;"
|
||||
transform="matrix(1 0 0 1 263 322)"
|
||||
id="text564">Weight/Scale Variations</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;"
|
||||
transform="matrix(1 0 0 1 559.711 322)"
|
||||
id="text566">Ultralight</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;"
|
||||
transform="matrix(1 0 0 1 856.422 322)"
|
||||
id="text568">Thin</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;"
|
||||
transform="matrix(1 0 0 1 1153.13 322)"
|
||||
id="text570">Light</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;"
|
||||
transform="matrix(1 0 0 1 1449.84 322)"
|
||||
id="text572">Regular</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;"
|
||||
transform="matrix(1 0 0 1 1746.56 322)"
|
||||
id="text574">Medium</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;"
|
||||
transform="matrix(1 0 0 1 2043.27 322)"
|
||||
id="text576">Semibold</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;"
|
||||
transform="matrix(1 0 0 1 2339.98 322)"
|
||||
id="text578">Bold</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;"
|
||||
transform="matrix(1 0 0 1 2636.69 322)"
|
||||
id="text580">Heavy</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;"
|
||||
transform="matrix(1 0 0 1 2933.4 322)"
|
||||
id="text582">Black</text>
|
||||
<line
|
||||
style="fill:none;stroke:black;opacity:1;stroke-width:0.5;"
|
||||
x1="263"
|
||||
x2="3036"
|
||||
y1="1903"
|
||||
y2="1903"
|
||||
id="line584" />
|
||||
<g
|
||||
transform="matrix(0.2 0 0 0.2 263 1933)"
|
||||
id="g588">
|
||||
<path
|
||||
d="m46.2402 4.15039c21.7773 0 39.4531-17.627 39.4531-39.4043s-17.6758-39.4043-39.4531-39.4043c-21.7285 0-39.4043 17.627-39.4043 39.4043s17.6758 39.4043 39.4043 39.4043Zm0-7.42188c-17.6758 0-31.9336-14.3066-31.9336-31.9824s14.2578-31.9824 31.9336-31.9824 31.9824 14.3066 31.9824 31.9824-14.3066 31.9824-31.9824 31.9824Zm-17.9688-31.9824c0 2.14844 1.51367 3.61328 3.75977 3.61328h10.498v10.5957c0 2.19727 1.46484 3.71094 3.61328 3.71094 2.24609 0 3.71094-1.51367 3.71094-3.71094v-10.5957h10.5957c2.19727 0 3.71094-1.46484 3.71094-3.61328 0-2.19727-1.51367-3.71094-3.71094-3.71094h-10.5957v-10.5469c0-2.24609-1.46484-3.75977-3.71094-3.75977-2.14844 0-3.61328 1.51367-3.61328 3.75977v10.5469h-10.498c-2.24609 0-3.75977 1.51367-3.75977 3.71094Z"
|
||||
id="path586" />
|
||||
</g>
|
||||
<g
|
||||
transform="matrix(0.2 0 0 0.2 281.506 1933)"
|
||||
id="g592">
|
||||
<path
|
||||
d="m58.5449 14.5508c27.4902 0 49.8047-22.3145 49.8047-49.8047s-22.3145-49.8047-49.8047-49.8047-49.8047 22.3145-49.8047 49.8047 22.3145 49.8047 49.8047 49.8047Zm0-8.30078c-22.9492 0-41.5039-18.5547-41.5039-41.5039s18.5547-41.5039 41.5039-41.5039 41.5039 18.5547 41.5039 41.5039-18.5547 41.5039-41.5039 41.5039Zm-22.6562-41.5039c0 2.39258 1.66016 4.00391 4.15039 4.00391h14.3555v14.4043c0 2.44141 1.66016 4.15039 4.05273 4.15039 2.44141 0 4.15039-1.66016 4.15039-4.15039v-14.4043h14.4043c2.44141 0 4.15039-1.61133 4.15039-4.00391 0-2.44141-1.70898-4.15039-4.15039-4.15039h-14.4043v-14.3555c0-2.49023-1.70898-4.19922-4.15039-4.19922-2.39258 0-4.05273 1.70898-4.05273 4.19922v14.3555h-14.3555c-2.49023 0-4.15039 1.70898-4.15039 4.15039Z"
|
||||
id="path590" />
|
||||
</g>
|
||||
<g
|
||||
transform="matrix(0.2 0 0 0.2 304.924 1933)"
|
||||
id="g596">
|
||||
<path
|
||||
d="m74.8535 28.3203c35.1074 0 63.623-28.4668 63.623-63.5742s-28.5156-63.623-63.623-63.623-63.5742 28.5156-63.5742 63.623 28.4668 63.5742 63.5742 63.5742Zm0-9.08203c-30.127 0-54.4922-24.3652-54.4922-54.4922s24.3652-54.4922 54.4922-54.4922 54.4922 24.3652 54.4922 54.4922-24.3652 54.4922-54.4922 54.4922Zm-28.8574-54.4922c0 2.58789 1.85547 4.39453 4.58984 4.39453h19.7266v19.7754c0 2.68555 1.85547 4.58984 4.44336 4.58984 2.68555 0 4.54102-1.85547 4.54102-4.58984v-19.7754h19.7754c2.68555 0 4.58984-1.80664 4.58984-4.39453 0-2.73438-1.85547-4.58984-4.58984-4.58984h-19.7754v-19.7266c0-2.73438-1.85547-4.63867-4.54102-4.63867-2.58789 0-4.44336 1.9043-4.44336 4.63867v19.7266h-19.7266c-2.73438 0-4.58984 1.85547-4.58984 4.58984Z"
|
||||
id="path594" />
|
||||
</g>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;font-weight:bold;"
|
||||
transform="matrix(1 0 0 1 263 1953)"
|
||||
id="text598">Design Variations</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;"
|
||||
transform="matrix(1 0 0 1 263 1971)"
|
||||
id="text600">Symbols are supported in up to nine weights and three scales.</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;"
|
||||
transform="matrix(1 0 0 1 263 1989)"
|
||||
id="text602">For optimal layout with text and other symbols, vertically align</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;"
|
||||
transform="matrix(1 0 0 1 263 2007)"
|
||||
id="text604">symbols with the adjacent text.</text>
|
||||
<line
|
||||
style="fill:none;stroke:#00AEEF;stroke-width:0.5;opacity:1.0;"
|
||||
x1="776"
|
||||
x2="776"
|
||||
y1="1919"
|
||||
y2="1933"
|
||||
id="line606" />
|
||||
<g
|
||||
transform="matrix(0.2 0 0 0.2 776 1933)"
|
||||
id="g610">
|
||||
<path
|
||||
d="m16.5527 0.78125c2.58789 0 3.85742-0.976562 4.78516-3.71094l6.29883-17.2363h28.8086l6.29883 17.2363c0.927734 2.73438 2.19727 3.71094 4.73633 3.71094 2.58789 0 4.24805-1.5625 4.24805-4.00391 0-0.830078-0.146484-1.61133-0.537109-2.63672l-22.9004-60.9863c-1.12305-2.97852-3.125-4.49219-6.25-4.49219-3.02734 0-5.07812 1.46484-6.15234 4.44336l-22.9004 61.084c-0.390625 1.02539-0.537109 1.80664-0.537109 2.63672 0 2.44141 1.5625 3.95508 4.10156 3.95508Zm13.4766-28.3691 11.8652-32.8613h0.244141l11.8652 32.8613Z"
|
||||
id="path608" />
|
||||
</g>
|
||||
<line
|
||||
style="fill:none;stroke:#00AEEF;stroke-width:0.5;opacity:1.0;"
|
||||
x1="792.836"
|
||||
x2="792.836"
|
||||
y1="1919"
|
||||
y2="1933"
|
||||
id="line612" />
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;font-weight:bold;"
|
||||
transform="matrix(1 0 0 1 776 1953)"
|
||||
id="text614">Margins</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;"
|
||||
transform="matrix(1 0 0 1 776 1971)"
|
||||
id="text616">Leading and trailing margins on the left and right side of each symbol</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;"
|
||||
transform="matrix(1 0 0 1 776 1989)"
|
||||
id="text618">can be adjusted by modifying the x-location of the margin guidelines.</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;"
|
||||
transform="matrix(1 0 0 1 776 2007)"
|
||||
id="text620">Modifications are automatically applied proportionally to all</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;"
|
||||
transform="matrix(1 0 0 1 776 2025)"
|
||||
id="text622">scales and weights.</text>
|
||||
<g
|
||||
transform="matrix(0.2 0 0 0.2 1289 1933)"
|
||||
id="g626">
|
||||
<path
|
||||
d="m14.209 9.32617 8.49609 8.54492c4.29688 4.3457 9.22852 4.05273 13.8672-1.07422l53.4668-58.9355-4.83398-4.88281-53.0762 58.3984c-1.75781 2.00195-3.41797 2.49023-5.76172 0.146484l-5.85938-5.81055c-2.34375-2.29492-1.80664-4.00391 0.195312-5.81055l57.373-54.0039-4.88281-4.83398-57.959 54.4434c-4.93164 4.58984-5.32227 9.47266-1.02539 13.8184Zm32.0801-90.9668c-2.09961 2.05078-2.24609 4.93164-1.07422 6.88477 1.17188 1.80664 3.4668 2.97852 6.68945 2.14844 7.32422-1.70898 14.9414-2.00195 22.0703 2.68555l-2.92969 7.27539c-1.70898 4.15039-0.830078 7.08008 1.85547 9.81445l11.4746 11.5723c2.44141 2.44141 4.49219 2.53906 7.32422 2.05078l5.32227-0.976562 3.32031 3.36914-0.195312 2.7832c-0.195312 2.49023 0.439453 4.39453 2.88086 6.78711l3.80859 3.71094c2.39258 2.39258 5.46875 2.53906 7.8125 0.195312l14.5508-14.5996c2.34375-2.34375 2.24609-5.32227-0.146484-7.71484l-3.85742-3.80859c-2.39258-2.39258-4.24805-3.17383-6.64062-2.97852l-2.88086 0.244141-3.22266-3.17383 1.2207-5.61523c0.634766-2.83203-0.146484-5.0293-3.07617-7.95898l-10.9863-10.9375c-16.6992-16.6016-38.8672-16.2109-53.3203-1.75781Zm7.4707 1.85547c12.1582-8.88672 28.6133-7.37305 39.7461 3.75977l12.1582 12.0605c1.17188 1.17188 1.36719 2.09961 1.02539 3.80859l-1.61133 7.42188 7.51953 7.42188 4.93164-0.292969c1.26953-0.0488281 1.66016 0.0488281 2.63672 1.02539l2.88086 2.88086-12.207 12.207-2.88086-2.88086c-0.976562-0.976562-1.12305-1.36719-1.07422-2.68555l0.341797-4.88281-7.4707-7.42188-7.61719 1.26953c-1.61133 0.341797-2.34375 0.195312-3.56445-0.976562l-10.0098-10.0098c-1.26953-1.17188-1.41602-2.00195-0.634766-3.85742l4.39453-10.4492c-7.8125-7.27539-17.9688-10.4004-28.125-7.42188-0.78125 0.195312-1.07422-0.439453-0.439453-0.976562Z"
|
||||
id="path624" />
|
||||
</g>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;font-weight:bold;"
|
||||
transform="matrix(1 0 0 1 1289 1953)"
|
||||
id="text628">Exporting</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;"
|
||||
transform="matrix(1 0 0 1 1289 1971)"
|
||||
id="text630">Symbols should be outlined when exporting to ensure the</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;"
|
||||
transform="matrix(1 0 0 1 1289 1989)"
|
||||
id="text632">design is preserved when submitting to Xcode.</text>
|
||||
<text
|
||||
id="template-version"
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:end;"
|
||||
transform="matrix(1 0 0 1 3036 1933)">Template v.6.0</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:end;"
|
||||
transform="matrix(1 0 0 1 3036 1951)"
|
||||
id="text635">Requires Xcode 16 or greater</text>
|
||||
<text
|
||||
id="descriptive-name"
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:end;"
|
||||
transform="matrix(1 0 0 1 3036 1969)">Generated from thermometer.variable</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:end;"
|
||||
transform="matrix(1 0 0 1 3036 1987)"
|
||||
id="text638">Typeset at 100.0 points</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;"
|
||||
transform="matrix(1 0 0 1 263 726)"
|
||||
id="text640">Small</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;"
|
||||
transform="matrix(1 0 0 1 263 1156)"
|
||||
id="text642">Medium</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;"
|
||||
transform="matrix(1 0 0 1 263 1586)"
|
||||
id="text644">Large</text>
|
||||
</g>
|
||||
<g
|
||||
id="Guides">
|
||||
<g
|
||||
id="H-reference"
|
||||
style="fill:#27AAE1;stroke:none;"
|
||||
transform="matrix(1 0 0 1 339 696)">
|
||||
<path
|
||||
d="M0.993654 0L3.63775 0L29.3281-67.1323L30.0303-67.1323L30.0303-70.459L28.1226-70.459ZM11.6885-24.4799L46.9815-24.4799L46.2315-26.7285L12.4385-26.7285ZM55.1196 0L57.7637 0L30.6382-70.459L29.4326-70.459L29.4326-67.1323Z"
|
||||
id="path647" />
|
||||
</g>
|
||||
<line
|
||||
id="Baseline-S"
|
||||
style="fill:none;stroke:#27AAE1;opacity:1;stroke-width:0.5;"
|
||||
x1="263"
|
||||
x2="3036"
|
||||
y1="696"
|
||||
y2="696" />
|
||||
<line
|
||||
id="Capline-S"
|
||||
style="fill:none;stroke:#27AAE1;opacity:1;stroke-width:0.5;"
|
||||
x1="263"
|
||||
x2="3036"
|
||||
y1="625.541"
|
||||
y2="625.541" />
|
||||
<g
|
||||
id="g654"
|
||||
style="fill:#27AAE1;stroke:none;"
|
||||
transform="matrix(1 0 0 1 339 1126)">
|
||||
<path
|
||||
d="M0.993654 0L3.63775 0L29.3281-67.1323L30.0303-67.1323L30.0303-70.459L28.1226-70.459ZM11.6885-24.4799L46.9815-24.4799L46.2315-26.7285L12.4385-26.7285ZM55.1196 0L57.7637 0L30.6382-70.459L29.4326-70.459L29.4326-67.1323Z"
|
||||
id="path652" />
|
||||
</g>
|
||||
<line
|
||||
id="Baseline-M"
|
||||
style="fill:none;stroke:#27AAE1;opacity:1;stroke-width:0.5;"
|
||||
x1="263"
|
||||
x2="3036"
|
||||
y1="1126"
|
||||
y2="1126" />
|
||||
<line
|
||||
id="Capline-M"
|
||||
style="fill:none;stroke:#27AAE1;opacity:1;stroke-width:0.5;"
|
||||
x1="263"
|
||||
x2="3036"
|
||||
y1="1055.54"
|
||||
y2="1055.54" />
|
||||
<g
|
||||
id="g660"
|
||||
style="fill:#27AAE1;stroke:none;"
|
||||
transform="matrix(1 0 0 1 339 1556)">
|
||||
<path
|
||||
d="M0.993654 0L3.63775 0L29.3281-67.1323L30.0303-67.1323L30.0303-70.459L28.1226-70.459ZM11.6885-24.4799L46.9815-24.4799L46.2315-26.7285L12.4385-26.7285ZM55.1196 0L57.7637 0L30.6382-70.459L29.4326-70.459L29.4326-67.1323Z"
|
||||
id="path658" />
|
||||
</g>
|
||||
<line
|
||||
id="Baseline-L"
|
||||
style="fill:none;stroke:#27AAE1;opacity:1;stroke-width:0.5;"
|
||||
x1="263"
|
||||
x2="3036"
|
||||
y1="1556"
|
||||
y2="1556" />
|
||||
<line
|
||||
id="Capline-L"
|
||||
style="fill:none;stroke:#27AAE1;opacity:1;stroke-width:0.5;"
|
||||
x1="263"
|
||||
x2="3036"
|
||||
y1="1485.54"
|
||||
y2="1485.54" />
|
||||
<line
|
||||
id="right-margin-Black-S"
|
||||
style="opacity:1;fill:none;stroke:#00aeef;stroke-width:0.5"
|
||||
x1="2994.5601"
|
||||
x2="2994.5601"
|
||||
y1="600.78497"
|
||||
y2="720.12097" />
|
||||
<line
|
||||
id="left-margin-Black-S"
|
||||
style="opacity:1;fill:none;stroke:#00aeef;stroke-width:0.5"
|
||||
x1="2882.24"
|
||||
x2="2882.24"
|
||||
y1="600.78497"
|
||||
y2="720.12097" />
|
||||
<line
|
||||
id="right-margin-Regular-S"
|
||||
style="opacity:1;fill:none;stroke:#00aeef;stroke-width:0.5"
|
||||
x1="1492"
|
||||
x2="1492"
|
||||
y1="600.78497"
|
||||
y2="720.12097" />
|
||||
<line
|
||||
id="left-margin-Regular-S"
|
||||
style="opacity:1;fill:none;stroke:#00aeef;stroke-width:0.5"
|
||||
x1="1395.6899"
|
||||
x2="1395.6899"
|
||||
y1="600.78497"
|
||||
y2="720.12097" />
|
||||
<line
|
||||
id="right-margin-Ultralight-S"
|
||||
style="opacity:1;fill:none;stroke:#00aeef;stroke-width:0.5"
|
||||
x1="605.70599"
|
||||
x2="605.70599"
|
||||
y1="600.78497"
|
||||
y2="720.12097" />
|
||||
<line
|
||||
id="left-margin-Ultralight-S"
|
||||
style="opacity:1;fill:none;stroke:#00aeef;stroke-width:0.5"
|
||||
x1="513.71698"
|
||||
x2="513.71698"
|
||||
y1="600.78497"
|
||||
y2="720.12097" />
|
||||
</g>
|
||||
<g
|
||||
id="Symbols">
|
||||
<g
|
||||
id="Black-S"
|
||||
transform="matrix(1 0 0 1 2898.24 696)">
|
||||
<path
|
||||
d="m 93.819567,-0.549 c 0,2.346 -1.91,4.25 -4.25,4.25 -2.35,0 -4.25,-1.904 -4.25,-4.25 0,-0.295 -0.01,-0.589 -0.03,-0.881 -0.15,-2.341 1.62,-4.366 3.96,-4.519 2.34,-0.154 4.37,1.621 4.52,3.962 0.03,0.477 0.05,0.956 0.05,1.438 z m -5.03,-14.361 c 1.4,1.882 1.01,4.547 -0.87,5.947 -1.88,1.401 -4.55,1.011 -5.95,-0.871 -0.38,-0.515 -0.79,-1.02 -1.23,-1.516 -1.56,-1.755 -1.4,-4.443 0.36,-6 1.75,-1.556 4.44,-1.395 6,0.36 0.6,0.68 1.16,1.374 1.69,2.08 z m -11.75,-10.145 c 2.02,1.186 2.7,3.792 1.52,5.815 -1.19,2.024 -3.79,2.704 -5.82,1.518 -0.6,-0.352 -1.22,-0.695 -1.85,-1.027 -2.08,-1.088 -2.88,-3.659 -1.79,-5.737 1.09,-2.077 3.66,-2.881 5.73,-1.792 0.76,0.395 1.5,0.803 2.21,1.223 z M 5.8395672,-26.278 c 2.08,-1.089 4.6499998,-0.285 5.7399998,1.792 1.08,2.078 0.28,4.649 -1.7999998,5.737 -0.63,0.332 -1.25,0.675 -1.85,1.027 -2.02,1.186 -4.63,0.506 -5.82,-1.518 -1.17999998,-2.023 -0.5,-4.629 1.52,-5.815 0.72,-0.42 1.45,-0.828 2.21,-1.223 z m -12.26,9.288 c 1.55,-1.755 4.24,-1.916 6.00000002,-0.36 1.74999998,1.557 1.90999998,4.245 0.36,6 -0.44,0.496 -0.86,1.001 -1.24000002,1.516 -1.4,1.882 -4.06,2.272 -5.95,0.871 -1.88,-1.4 -2.27,-4.065 -0.87,-5.946 0.53,-0.707 1.09,-1.401 1.7,-2.081 z m -6.6800002,15.003 c 0.15,-2.341 2.18,-4.116 4.5200002,-3.962 2.34,0.153 4.12,2.178 3.96,4.519 -0.02,0.292 -0.03,0.586 -0.03,0.881 0,2.346 -1.9,4.25 -4.25,4.25 -2.3400002,0 -4.2500002,-1.904 -4.2500002,-4.25 0,-0.482 0.02,-0.961 0.05,-1.438 z m 53.49,13.315 c 13.96,0 25.34,-10.937 25.34,-24.951 0,-6.592 -2.44,-12.256 -6.99,-16.797 -0.73,-0.732 -0.83,-1.074 -0.83,-2.051 v -29.638 c 0,-11.182 -7.12,-18.702 -17.52,-18.702 -10.55,0 -17.63,7.52 -17.63,18.702 v 29.638 c 0,0.977 -0.1,1.367 -0.83,2.051 -4.69,4.346 -6.98,10.205 -6.98,16.797 0,14.014 11.37,24.951 25.44,24.951 z m 0,-11.474 c -7.72,0 -13.92,-6.25 -13.92,-13.965 0,-5.176 2.34,-8.985 6.3,-11.573 1.12,-0.732 1.56,-1.318 1.56,-2.832 v -33.3 c 0,-4.493 2.44,-7.471 6.06,-7.471 3.51,0 5.95,2.978 5.95,7.471 v 33.3 c 0,1.514 0.44,2.1 1.57,2.832 3.95,2.588 6.29,6.397 6.29,11.573 0,7.715 -6.2,13.965 -13.81,13.965 z m -0.05,-5.176 c 4.83,0 8.74,-3.907 8.74,-8.789 0,-3.369 -1.91,-6.153 -4.69,-7.666 -1.17,-0.635 -1.56,-1.075 -1.56,-2.832 v -10.254 c 0,-1.367 -1.13,-2.49 -2.49,-2.49 -1.37,0 -2.49,1.123 -2.49,2.49 v 10.254 c 0,1.757 -0.4,2.197 -1.57,2.832 -2.78,1.513 -4.68,4.297 -4.68,7.666 0,4.882 3.9,8.789 8.74,8.789 z m 0,-36.035 c 1.36,0 2.49,-1.123 2.49,-2.491 0,-1.367 -1.13,-2.49 -2.49,-2.49 -1.37,0 -2.49,1.123 -2.49,2.49 0,1.368 1.12,2.491 2.49,2.491 z m 0,-8.985 c 1.36,0 2.49,-1.123 2.49,-2.49 0,-1.367 -1.13,-2.49 -2.49,-2.49 -1.37,0 -2.49,1.123 -2.49,2.49 0,1.367 1.12,2.49 2.49,2.49 z m 0,-8.984 c 1.36,0 2.49,-1.123 2.49,-2.49 0,-1.368 -1.13,-2.491 -2.49,-2.491 -1.37,0 -2.49,1.123 -2.49,2.491 0,1.367 1.12,2.49 2.49,2.49 z"
|
||||
style="clip-rule:evenodd;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.5px;stroke-linejoin:round;stroke-miterlimit:2"
|
||||
id="path4426" />
|
||||
</g>
|
||||
<g
|
||||
id="Regular-S"
|
||||
transform="matrix(1 0 0 1 1419.69 696)">
|
||||
<path
|
||||
d="m 70.441741,-2.1089996 c 0,1.518 -1.23,2.75 -2.75,2.75 -1.51,0 -2.75,-1.232 -2.75,-2.75 0,-0.328 -0.01,-0.654 -0.03,-0.979 -0.1,-1.515 1.05,-2.825 2.57,-2.924 1.51,-0.1 2.82,1.049 2.92,2.564 0.03,0.444 0.04,0.891 0.04,1.339 z m -4.73,-13.4640004 c 0.91,1.217 0.66,2.941 -0.56,3.848 -1.22,0.906 -2.94,0.653 -3.85,-0.564 -0.41,-0.549 -0.85,-1.088 -1.32,-1.616 -1,-1.136 -0.9,-2.875 0.24,-3.882 1.13,-1.007 2.87,-0.903 3.88,0.232 0.57,0.648 1.11,1.309 1.61,1.982 z m -11.3,-9.748 c 1.31,0.768 1.75,2.454 0.98,3.763 -0.77,1.31 -2.45,1.75 -3.76,0.982 -0.63,-0.364 -1.26,-0.718 -1.92,-1.061 -1.34,-0.704 -1.86,-2.368 -1.16,-3.712 0.7,-1.345 2.37,-1.864 3.71,-1.16 0.74,0.384 1.45,0.78 2.15,1.188 z m -58.7500004,-1.188 c 1.34,-0.704 3.01,-0.185 3.71,1.16 0.71,1.344 0.19,3.008 -1.16,3.712 -0.65,0.343 -1.29,0.697 -1.91,1.061 -1.31,0.768 -3,0.328 -3.77,-0.982 -0.76,-1.309 -0.32,-2.995 0.98,-3.763 0.7,-0.408 1.42,-0.804 2.15,-1.188 z m -11.8399996,8.954 c 1.01,-1.135 2.75,-1.239 3.89,-0.232 1.13,1.007 1.24,2.746 0.23,3.882 -0.47,0.528 -0.91,1.067 -1.32,1.616 -0.9,1.217 -2.63,1.47 -3.85,0.564 -1.21,-0.907 -1.47,-2.631 -0.56,-3.848 0.5,-0.673 1.04,-1.334 1.61,-1.982 z m -6.3,14.1070004 c 0.1,-1.515 1.41,-2.664 2.93,-2.564 1.51,0.099 2.66,1.409 2.56,2.924 -0.02,0.325 -0.03,0.651 -0.03,0.979 0,1.518 -1.23,2.75 -2.75,2.75 -1.52,0 -2.75,-1.232 -2.75,-2.75 0,-0.448 0.01,-0.895 0.04,-1.339 z m 46.47,10.772 c 11.23,0 20.36,-9.131 20.36,-20.3610004 0,-5.908 -2.44,-11.084 -6.98,-15.283 -0.79,-0.733 -0.93,-1.123 -0.93,-2.149 v -33.789 c 0,-8.154 -5.03,-13.623 -12.45,-13.623 -7.48,0 -12.55,5.469 -12.55,13.623 v 33.789 c 0,1.026 -0.15,1.416 -0.88,2.149 -4.5400004,4.199 -6.9800004,9.375 -6.9800004,15.283 0,11.2300004 9.1300004,20.3610004 20.4100004,20.3610004 z m 0,-6.347 c -7.77,0 -14.0200004,-6.299 -14.0200004,-14.0140004 0,-4.736 2.3000004,-9.033 6.3000004,-11.67 1.12,-0.732 1.56,-1.367 1.56,-2.881 v -36.377 c 0,-4.541 2.49,-7.519 6.16,-7.519 3.61,0 6.05,2.978 6.05,7.519 v 36.377 c 0,1.514 0.49,2.149 1.61,2.881 3.96,2.637 6.3,6.934 6.3,11.67 0,7.7150004 -6.25,14.0140004 -13.96,14.0140004 z m -0.05,-5.176 c 4.93,0 8.88,-3.955 8.88,-8.8870004 0,-3.418 -1.95,-6.25 -4.73,-7.764 -1.22,-0.683 -1.61,-1.123 -1.61,-2.88 v -13.819 c 0,-1.416 -1.13,-2.539 -2.54,-2.539 -1.37,0 -2.49,1.123 -2.49,2.539 v 13.819 c 0,1.757 -0.39,2.197 -1.62,2.88 -2.83,1.514 -4.73,4.346 -4.73,7.764 0,4.9320004 3.95,8.8870004 8.84,8.8870004 z m 0,-39.6000004 c 1.41,0 2.54,-1.172 2.54,-2.539 0,-1.416 -1.13,-2.539 -2.54,-2.539 -1.37,0 -2.49,1.123 -2.49,2.539 0,1.367 1.12,2.539 2.49,2.539 z m 0,-8.838 c 1.41,0 2.54,-1.172 2.54,-2.539 0,-1.416 -1.13,-2.539 -2.54,-2.539 -1.37,0 -2.49,1.123 -2.49,2.539 0,1.367 1.12,2.539 2.49,2.539 z m 0,-8.789 c 1.41,0 2.54,-1.172 2.54,-2.539 0,-1.416 -1.13,-2.539 -2.54,-2.539 -1.37,0 -2.49,1.123 -2.49,2.539 0,1.367 1.12,2.539 2.49,2.539 z"
|
||||
style="clip-rule:evenodd;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.5px;stroke-linejoin:round;stroke-miterlimit:2"
|
||||
id="path4429" />
|
||||
</g>
|
||||
<g
|
||||
id="Ultralight-S"
|
||||
transform="matrix(1 0 0 1 531.717 696)">
|
||||
<path
|
||||
d="m 70.343736,-2.1090003 c 0,-0.4319999 -0.014,-0.8619999 -0.042,-1.2899999 -0.073,-1.102 -1.026,-1.937 -2.127,-1.865 -1.101,0.072 -1.937,1.025 -1.865,2.127 0.023,0.341 0.034,0.684 0.034,1.0279999 0,1.104 0.896,2.0000001 2,2.0000001 1.104,0 2,-0.8960001 2,-2.0000001 z M 65.759736,-15.126 c -0.488,-0.655 -1.013,-1.3 -1.573,-1.931 -0.732,-0.826 -1.997,-0.902 -2.823,-0.169 -0.826,0.732 -0.902,1.997 -0.169,2.823 0.483,0.545 0.936,1.1 1.357,1.666 0.659,0.885 1.913,1.069 2.798,0.41 0.886,-0.659 1.07,-1.913 0.41,-2.799 z m -11.082,-9.548 c -0.686,-0.402 -1.39,-0.792 -2.113,-1.171 -0.978,-0.512 -2.187,-0.134 -2.7,0.844 -0.512,0.978 -0.134,2.187 0.844,2.7 0.666,0.348 1.315,0.708 1.946,1.078 0.953,0.558 2.179,0.238 2.737,-0.714 0.558,-0.952 0.238,-2.179 -0.714,-2.737 z M 1.6577364,-25.845 c -0.72300001,0.379 -1.42800001,0.769 -2.11300001,1.171 -0.95299999,0.558 -1.27299999,1.785 -0.71399999,2.737 0.55799999,0.952 1.78399999,1.272 2.736,0.714 0.632,-0.37 1.281,-0.73 1.947,-1.078 0.978,-0.513 1.356,-1.722 0.843,-2.7 -0.512,-0.978 -1.722,-1.356 -2.699,-0.844 z m -11.622,8.788 c -0.5600004,0.631 -1.0850004,1.276 -1.5740004,1.931 -0.659,0.886 -0.475,2.14 0.41,2.799 0.886,0.659 2.1400004,0.476 2.7990004,-0.41 0.421,-0.566 0.874,-1.121 1.357,-1.666 0.733,-0.826 0.657,-2.091 -0.169,-2.823 -0.826,-0.733 -2.091,-0.657 -2.823,0.169 z m -6.1150004,13.6579998 c -0.028,0.428 -0.042,0.858 -0.042,1.2899999 0,1.104 0.896,2.0000001 2,2.0000001 1.104,0 2,-0.8960001 2,-2.0000001 0,-0.3439999 0.011,-0.6869999 0.033,-1.0279999 0.073,-1.102 -0.763,-2.055 -1.864,-2.127 -1.102,-0.072 -2.055,0.763 -2.127,1.865 z m 43.192,10.087 c 10.095,0 18.227,-8.1770001 18.227,-18.1809998 0,-5.409 -2.214,-10.131 -6.891,-13.876 -1.145,-0.914 -1.382,-1.577 -1.382,-3.192 v -38.103 c 0,-6.202 -4.121,-10.581 -9.954,-10.581 -5.836,0 -9.96,4.379 -9.96,10.581 v 38.103 c 0,1.615 -0.238,2.278 -1.379,3.192 -4.677,3.745 -6.8909996,8.467 -6.8909996,13.876 0,10.0039997 8.1319996,18.1809998 18.2299996,18.1809998 z m 0,-2.033 c -8.899,0 -16.148,-7.298 -16.148,-16.1479998 0,-4.873 2.023,-9.306 6.39,-12.487 1.395,-1.005 1.926,-2.23 1.926,-3.971 v -38.693 c 0,-4.95 3.262,-8.473 7.832,-8.473 4.567,0 7.826,3.523 7.826,8.473 v 38.693 c 0,1.741 0.534,2.966 1.929,3.971 4.364,3.181 6.39,7.614 6.39,12.487 0,8.8499998 -7.249,16.1479998 -16.145,16.1479998 z m -0.003,-5.13100005 c 6.112,0 10.975,-4.90799995 10.975,-11.02099975 0,-4.28 -2.362,-7.793 -5.917,-9.67 -1.447,-0.775 -1.929,-1.351 -1.929,-3.517 v -10.503 c 0,-1.734 -1.395,-3.13 -3.129,-3.13 -1.731,0 -3.126,1.396 -3.126,3.13 v 10.503 c 0,2.166 -0.482,2.742 -1.929,3.517 -3.559,1.877 -5.917,5.39 -5.917,9.67 0,6.1129998 4.863,11.02099975 10.972,11.02099975 z m 0,-42.05099975 c 1.734,0 3.129,-1.445 3.129,-3.175 0,-1.734 -1.395,-3.129 -3.129,-3.129 -1.731,0 -3.126,1.395 -3.126,3.129 0,1.73 1.395,3.175 3.126,3.175 z m 0,-10.473 c 1.734,0 3.129,-1.399 3.129,-3.129 0,-1.78 -1.395,-3.175 -3.129,-3.175 -1.731,0 -3.126,1.395 -3.126,3.175 0,1.73 1.395,3.129 3.126,3.129 z m 0,-10.515 c 1.734,0 3.129,-1.399 3.129,-3.129 0,-1.734 -1.395,-3.175 -3.129,-3.175 -1.731,0 -3.126,1.441 -3.126,3.175 0,1.73 1.395,3.129 3.126,3.129 z"
|
||||
style="clip-rule:evenodd;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.5px;stroke-linejoin:round;stroke-miterlimit:2"
|
||||
id="path4432" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 25 KiB |
|
|
@ -29,6 +29,8 @@ public struct ManagedAttribute<Value: Numeric> {
|
|||
converter = { $0.int32Value as? Value }
|
||||
} else if Value.self == Int64.self {
|
||||
converter = { $0.int64Value as? Value }
|
||||
} else if Value.self == UInt32.self {
|
||||
converter = { $0.uint32Value as? Value }
|
||||
} else {
|
||||
fatalError("Unsupported type: \(Value.self)")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,17 +10,17 @@ import Foundation
|
|||
extension Date {
|
||||
|
||||
var lastHeard: String {
|
||||
if timeIntervalSince1970 > 0 {
|
||||
if self.timeIntervalSince1970 > 0 && self < Calendar.current.date(byAdding: .year, value: 1, to: Date())! {
|
||||
formatted()
|
||||
} else {
|
||||
"unknown"
|
||||
"unknown.age".localized
|
||||
}
|
||||
}
|
||||
|
||||
func formattedDate(format: String) -> String {
|
||||
let dateformat = DateFormatter()
|
||||
dateformat.dateFormat = format
|
||||
if self > Calendar.current.date(byAdding: .year, value: -5, to: Date())! {
|
||||
if self.timeIntervalSince1970 > 0 && self < Calendar.current.date(byAdding: .year, value: 1, to: Date())! {
|
||||
return dateformat.string(from: self)
|
||||
} else {
|
||||
return "unknown.age".localized
|
||||
|
|
|
|||
|
|
@ -93,12 +93,51 @@ extension String {
|
|||
|
||||
// Filter out variation selectors from the string
|
||||
var withoutVariationSelectors: String {
|
||||
return self.unicodeScalars
|
||||
.filter { scalar in
|
||||
return !scalar.properties.isVariationSelector
|
||||
var scalars: [UnicodeScalar] = []
|
||||
var previousWasASCII = false
|
||||
|
||||
for scalar in self.unicodeScalars {
|
||||
if scalar.properties.isVariationSelector {
|
||||
// Only keep variation selector if the previous character was ASCII
|
||||
if previousWasASCII {
|
||||
scalars.append(scalar)
|
||||
}
|
||||
// No need to update previousWasASCII since variation selectors aren't characters
|
||||
// Shouldn't have 2 in a row
|
||||
} else {
|
||||
scalars.append(scalar)
|
||||
previousWasASCII = scalar.isASCII
|
||||
}
|
||||
.compactMap { UnicodeScalar($0) }
|
||||
}
|
||||
|
||||
return scalars.compactMap { UnicodeScalar($0) }
|
||||
.map { String($0) }
|
||||
.joined()
|
||||
}
|
||||
|
||||
// Adds variation selectors to prefer the graphical form of emoji.
|
||||
// Looks ahead to make sure that the variation selector is not already applied.
|
||||
var addingVariationSelectors: String {
|
||||
var result = ""
|
||||
let scalars = self.unicodeScalars
|
||||
var index = scalars.startIndex
|
||||
while index < scalars.endIndex {
|
||||
let currentScalar = scalars[index]
|
||||
result += String(currentScalar)
|
||||
if currentScalar.properties.isEmoji && !currentScalar.properties.isEmojiPresentation && !currentScalar.isASCII {
|
||||
// Check if the next scalar is U+FE0F
|
||||
let nextIndex = scalars.index(after: index)
|
||||
if nextIndex < scalars.endIndex && scalars[nextIndex].value == 0xFE0F {
|
||||
// Already has variation selector; skip the next scalar
|
||||
index = nextIndex
|
||||
} else {
|
||||
// Append variation selector
|
||||
result += String(UnicodeScalar(0xFE0F)!)
|
||||
}
|
||||
}
|
||||
// Move to the next scalar
|
||||
index = scalars.index(after: index)
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
self.isConnected = false
|
||||
self.isConnecting = false
|
||||
self.lastConnectionError = "🚨 " + String.localizedStringWithFormat("Connection failed after %d attempts to connect to %@. You may need to forget your device under Settings > Bluetooth.".localized, timeoutTimerCount, name)
|
||||
Logger.services.error("\(self.lastConnectionError)")
|
||||
Logger.services.error("\(self.lastConnectionError, privacy: .public)")
|
||||
self.timeoutTimerCount = 0
|
||||
self.startScanning()
|
||||
} else {
|
||||
|
|
@ -295,7 +295,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
} else {
|
||||
// Disconnected without error which indicates user intent to disconnect
|
||||
// Happens when swiping to disconnect
|
||||
Logger.services.info("ℹ️ [BLE] Disconnected: \(peripheral.name ?? "Unknown".localized, privacy: .public): \(String(describing: "User Initiated Disconnect".localized))")
|
||||
Logger.services.info("ℹ️ [BLE] Disconnected: \(peripheral.name ?? "Unknown".localized, privacy: .public): \(String(describing: "User Initiated Disconnect".localized), privacy: .public)")
|
||||
}
|
||||
// Start a scan so the disconnected peripheral is moved to the peripherals[] if it is awake
|
||||
self.startScanning()
|
||||
|
|
@ -485,7 +485,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
}
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.traceroute.sent %@".localized, destNum.toHex())
|
||||
Logger.mesh.info("🪧 \(logString)")
|
||||
Logger.mesh.info("🪧 \(logString, privacy: .public)")
|
||||
|
||||
} catch {
|
||||
|
||||
|
|
@ -498,14 +498,14 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
guard connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected else { return }
|
||||
|
||||
if FROMRADIO_characteristic == nil {
|
||||
Logger.mesh.error("🚨 \("firmware.version.unsupported".localized)")
|
||||
Logger.mesh.error("🚨 \("firmware.version.unsupported".localized, privacy: .public)")
|
||||
invalidVersion = true
|
||||
return
|
||||
} else {
|
||||
|
||||
let nodeName = connectedPeripheral?.peripheral.name ?? "unknown".localized
|
||||
let logString = String.localizedStringWithFormat("mesh.log.wantconfig %@".localized, nodeName)
|
||||
Logger.mesh.info("🛎️ \(logString)")
|
||||
Logger.mesh.info("🛎️ \(logString, privacy: .public)")
|
||||
// BLE Characteristics discovered, issue wantConfig
|
||||
var toRadio: ToRadio = ToRadio()
|
||||
configNonce += 1
|
||||
|
|
@ -690,24 +690,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
connectedPeripheral.longName = myInfo?.bleName ?? "unknown".localized
|
||||
let newConnection = Int64(UserDefaults.preferredPeripheralNum) != Int64(decodedInfo.myInfo.myNodeNum)
|
||||
if newConnection {
|
||||
let container = NSPersistentContainer(name: "Meshtastic")
|
||||
if let url = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first {
|
||||
let databasePath = url.appendingPathComponent("backup")
|
||||
.appendingPathComponent("\(UserDefaults.preferredPeripheralNum)")
|
||||
.appendingPathComponent("Meshtastic.sqlite")
|
||||
if FileManager.default.fileExists(atPath: databasePath.path) {
|
||||
do {
|
||||
disconnectPeripheral(reconnect: false)
|
||||
try container.restorePersistentStore(from: databasePath)
|
||||
UserDefaults.preferredPeripheralNum = Int(myInfo?.myNodeNum ?? 0)
|
||||
context.refreshAllObjects()
|
||||
Logger.data.notice("🗂️ Restored Core data for /\(UserDefaults.preferredPeripheralNum, privacy: .public)")
|
||||
connectTo(peripheral: peripheral)
|
||||
} catch {
|
||||
Logger.data.error("🗂️ Restore Core data copy error: \(error, privacy: .public)")
|
||||
}
|
||||
}
|
||||
}
|
||||
// Onboard a new device connection here
|
||||
}
|
||||
}
|
||||
tryClearExistingChannels()
|
||||
|
|
@ -767,7 +750,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
}
|
||||
}
|
||||
// Log any other unknownApp calls
|
||||
if !nowKnown { Logger.mesh.info("🕸️ MESH PACKET received for Unknown App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") }
|
||||
if !nowKnown { Logger.mesh.info("🕸️ MESH PACKET received for Unknown App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure", privacy: .public)") }
|
||||
case .textMessageApp, .detectionSensorApp:
|
||||
textMessageAppPacket(
|
||||
packet: decodedInfo.packet,
|
||||
|
|
@ -786,7 +769,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
appState: appState
|
||||
)
|
||||
case .remoteHardwareApp:
|
||||
Logger.mesh.info("🕸️ MESH PACKET received for Remote Hardware App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")")
|
||||
Logger.mesh.info("🕸️ MESH PACKET received for Remote Hardware App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure", privacy: .public)")
|
||||
case .positionApp:
|
||||
upsertPositionPacket(packet: decodedInfo.packet, context: context)
|
||||
case .waypointApp:
|
||||
|
|
@ -984,31 +967,33 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
Logger.data.error("Error Updating Core Data TraceRouteHop: \(nsError, privacy: .public)")
|
||||
}
|
||||
let logString = String.localizedStringWithFormat("mesh.log.traceroute.received.route %@".localized, routeString)
|
||||
Logger.mesh.info("🪧 \(logString)")
|
||||
Logger.mesh.info("🪧 \(logString, privacy: .public)")
|
||||
}
|
||||
case .neighborinfoApp:
|
||||
if let neighborInfo = try? NeighborInfo(serializedBytes: decodedInfo.packet.decoded.payload) {
|
||||
Logger.mesh.info("🕸️ MESH PACKET received for Neighbor Info App UNHANDLED \((try? neighborInfo.jsonString()) ?? "JSON Decode Failure")")
|
||||
Logger.mesh.info("🕸️ MESH PACKET received for Neighbor Info App UNHANDLED \((try? neighborInfo.jsonString()) ?? "JSON Decode Failure", privacy: .public)")
|
||||
}
|
||||
case .paxcounterApp:
|
||||
paxCounterPacket(packet: decodedInfo.packet, context: context)
|
||||
case .mapReportApp:
|
||||
Logger.mesh.info("🕸️ MESH PACKET received Map Report App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")")
|
||||
Logger.mesh.info("🕸️ MESH PACKET received Map Report App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure", privacy: .public)")
|
||||
case .UNRECOGNIZED:
|
||||
Logger.mesh.info("🕸️ MESH PACKET received UNRECOGNIZED App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")")
|
||||
Logger.mesh.info("🕸️ MESH PACKET received UNRECOGNIZED App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure", privacy: .public)")
|
||||
case .max:
|
||||
Logger.services.info("MAX PORT NUM OF 511")
|
||||
case .atakPlugin:
|
||||
Logger.mesh.info("🕸️ MESH PACKET received for ATAK Plugin App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")")
|
||||
Logger.mesh.info("🕸️ MESH PACKET received for ATAK Plugin App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure", privacy: .public)")
|
||||
case .powerstressApp:
|
||||
Logger.mesh.info("🕸️ MESH PACKET received for Power Stress App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")")
|
||||
Logger.mesh.info("🕸️ MESH PACKET received for Power Stress App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure", privacy: .public)")
|
||||
case .reticulumTunnelApp:
|
||||
Logger.mesh.info("🕸️ MESH PACKET received for Reticulum Tunnel App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure", privacy: .public)")
|
||||
}
|
||||
|
||||
if decodedInfo.configCompleteID != 0 && decodedInfo.configCompleteID == configNonce {
|
||||
invalidVersion = false
|
||||
lastConnectionError = ""
|
||||
isSubscribed = true
|
||||
Logger.mesh.info("🤜 [BLE] Want Config Complete. ID:\(decodedInfo.configCompleteID)")
|
||||
Logger.mesh.info("🤜 [BLE] Want Config Complete. ID:\(decodedInfo.configCompleteID, privacy: .public)")
|
||||
if sendTime() {
|
||||
}
|
||||
peripherals.removeAll(where: { $0.peripheral.state == CBPeripheralState.disconnected })
|
||||
|
|
@ -1042,7 +1027,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
}
|
||||
|
||||
} catch {
|
||||
Logger.data.error("Failed to find a node info for the connected node \(error.localizedDescription)")
|
||||
Logger.data.error("Failed to find a node info for the connected node \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1086,7 +1071,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
}
|
||||
let nodeName = connectedPeripheral?.peripheral.name ?? "unknown".localized
|
||||
let logString = String.localizedStringWithFormat("mesh.log.textmessage.send.failed %@".localized, nodeName)
|
||||
Logger.mesh.info("🚫 \(logString)")
|
||||
Logger.mesh.info("🚫 \(logString, privacy: .public)")
|
||||
|
||||
success = false
|
||||
} else if message.count < 1 {
|
||||
|
|
@ -1173,7 +1158,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse)
|
||||
let logString = String.localizedStringWithFormat("mesh.log.textmessage.sent %@ %@ %@".localized, String(newMessage.messageId), fromUserNum.toHex(), toUserNum.toHex())
|
||||
|
||||
Logger.mesh.info("💬 \(logString)")
|
||||
Logger.mesh.info("💬 \(logString, privacy: .public)")
|
||||
do {
|
||||
try context.save()
|
||||
Logger.data.info("💾 Saved a new sent message from \(self.connectedPeripheral.num.toHex(), privacy: .public) to \(toUserNum.toHex(), privacy: .public)")
|
||||
|
|
@ -1220,7 +1205,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
return false
|
||||
}
|
||||
let logString = String.localizedStringWithFormat("mesh.log.waypoint.sent %@".localized, String(fromNodeNum))
|
||||
Logger.mesh.info("📍 \(logString)")
|
||||
Logger.mesh.info("📍 \(logString, privacy: .public)")
|
||||
if connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected {
|
||||
connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse)
|
||||
success = true
|
||||
|
|
@ -1384,7 +1369,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
if connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected {
|
||||
connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse)
|
||||
let logString = String.localizedStringWithFormat("mesh.log.sharelocation %@".localized, String(fromNodeNum))
|
||||
Logger.services.debug("📍 \(logString)")
|
||||
Logger.services.debug("📍 \(logString, privacy: .public)")
|
||||
return true
|
||||
} else {
|
||||
Logger.services.error("Device no longer connected. Unable to send position information.")
|
||||
|
|
@ -1700,7 +1685,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
}
|
||||
}
|
||||
} catch {
|
||||
Logger.data.error("Failed to find a node MyInfo to save these channels to: \(error.localizedDescription)")
|
||||
Logger.data.error("Failed to find a node MyInfo to save these channels to: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1739,7 +1724,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
if connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected {
|
||||
self.connectedPeripheral.peripheral.writeValue(binaryData, for: self.TORADIO_characteristic, type: .withResponse)
|
||||
let logString = String.localizedStringWithFormat("mesh.log.channel.sent %@ %d".localized, String(connectedPeripheral.num), chan.index)
|
||||
Logger.mesh.info("🎛️ \(logString)")
|
||||
Logger.mesh.info("🎛️ \(logString, privacy: .public)")
|
||||
}
|
||||
}
|
||||
// Save the LoRa Config and the device will reboot
|
||||
|
|
@ -1768,7 +1753,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
if connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected {
|
||||
self.connectedPeripheral.peripheral.writeValue(binaryData, for: self.TORADIO_characteristic, type: .withResponse)
|
||||
let logString = String.localizedStringWithFormat("mesh.log.lora.config.sent %@".localized, String(connectedPeripheral.num))
|
||||
Logger.mesh.info("📻 \(logString)")
|
||||
Logger.mesh.info("📻 \(logString, privacy: .public)")
|
||||
}
|
||||
|
||||
if self.connectedPeripheral != nil {
|
||||
|
|
@ -1845,7 +1830,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
} catch {
|
||||
context.rollback()
|
||||
let nsError = error as NSError
|
||||
Logger.data.error("🚫 Error deleting node from core data: \(nsError)")
|
||||
Logger.data.error("🚫 Error deleting node from core data: \(nsError, privacy: .public)")
|
||||
}
|
||||
}
|
||||
return false
|
||||
|
|
@ -2688,7 +2673,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
if connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected {
|
||||
connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse)
|
||||
let logString = String.localizedStringWithFormat("mesh.log.cannedmessages.messages.get %@".localized, String(connectedPeripheral.num))
|
||||
Logger.mesh.info("🥫 \(logString)")
|
||||
Logger.mesh.info("🥫 \(logString, privacy: .public)")
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
@ -3268,7 +3253,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
|
||||
if connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected {
|
||||
connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse)
|
||||
Logger.mesh.debug("\(adminDescription)")
|
||||
Logger.mesh.debug("\(adminDescription, privacy: .public)")
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
|
@ -3465,7 +3450,7 @@ extension BLEManager: CBCentralManagerDelegate {
|
|||
default:
|
||||
status = "default"
|
||||
}
|
||||
Logger.services.info("📜 [BLE] Bluetooth status: \(status)")
|
||||
Logger.services.info("📜 [BLE] Bluetooth status: \(status, privacy: .public)")
|
||||
}
|
||||
|
||||
// Called each time a peripheral is discovered
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ class LocalNotificationManager {
|
|||
|
||||
UNUserNotificationCenter.current().add(request) { error in
|
||||
if let error {
|
||||
Logger.services.error("Error Scheduling Notification: \(error.localizedDescription)")
|
||||
Logger.services.error("Error Scheduling Notification: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ import OSLog
|
|||
}
|
||||
}
|
||||
} catch {
|
||||
Logger.services.error("💥 [App] Could not start location updates: \(error.localizedDescription)")
|
||||
Logger.services.error("💥 [App] Could not start location updates: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ func moduleConfig (config: ModuleConfig, context: NSManagedObjectContext, nodeNu
|
|||
func myInfoPacket (myInfo: MyNodeInfo, peripheralId: String, context: NSManagedObjectContext) -> MyInfoEntity? {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.myinfo %@".localized, String(myInfo.myNodeNum))
|
||||
Logger.mesh.info("ℹ️ \(logString)")
|
||||
Logger.mesh.info("ℹ️ \(logString, privacy: .public)")
|
||||
|
||||
let fetchMyInfoRequest = MyInfoEntity.fetchRequest()
|
||||
fetchMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %lld", Int64(myInfo.myNodeNum))
|
||||
|
|
@ -155,7 +155,7 @@ func channelPacket (channel: Channel, fromNum: Int64, context: NSManagedObjectCo
|
|||
if channel.isInitialized && channel.hasSettings && channel.role != Channel.Role.disabled {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.channel.received %d %@".localized, channel.index, String(fromNum))
|
||||
Logger.mesh.info("🎛️ \(logString)")
|
||||
Logger.mesh.info("🎛️ \(logString, privacy: .public)")
|
||||
|
||||
let fetchedMyInfoRequest = MyInfoEntity.fetchRequest()
|
||||
fetchedMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %lld", fromNum)
|
||||
|
|
@ -194,7 +194,7 @@ func channelPacket (channel: Channel, fromNum: Int64, context: NSManagedObjectCo
|
|||
} catch {
|
||||
Logger.data.error("💥 Failed to save channel: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
Logger.data.info("💾 Updated MyInfo channel \(channel.index) from Channel App Packet For: \(fetchedMyInfo[0].myNodeNum)")
|
||||
Logger.data.info("💾 Updated MyInfo channel \(channel.index, privacy: .public) from Channel App Packet For: \(fetchedMyInfo[0].myNodeNum, privacy: .public)")
|
||||
} else if channel.role.rawValue > 0 {
|
||||
Logger.data.error("💥Trying to save a channel to a MyInfo that does not exist: \(fromNum.toHex(), privacy: .public)")
|
||||
}
|
||||
|
|
@ -210,7 +210,7 @@ func deviceMetadataPacket (metadata: DeviceMetadata, fromNum: Int64, sessionPass
|
|||
|
||||
if metadata.isInitialized {
|
||||
let logString = String.localizedStringWithFormat("mesh.log.device.metadata.received %@".localized, fromNum.toHex())
|
||||
Logger.mesh.info("🏷️ \(logString)")
|
||||
Logger.mesh.info("🏷️ \(logString, privacy: .public)")
|
||||
|
||||
let fetchedNodeRequest = NodeInfoEntity.fetchRequest()
|
||||
fetchedNodeRequest.predicate = NSPredicate(format: "num == %lld", fromNum)
|
||||
|
|
@ -226,6 +226,7 @@ func deviceMetadataPacket (metadata: DeviceMetadata, fromNum: Int64, sessionPass
|
|||
newMetadata.hasEthernet = metadata.hasEthernet_p
|
||||
newMetadata.role = Int32(metadata.role.rawValue)
|
||||
newMetadata.positionFlags = Int32(metadata.positionFlags)
|
||||
newMetadata.excludedModules = Int32(metadata.excludedModules)
|
||||
// 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))]
|
||||
|
|
@ -261,7 +262,7 @@ func deviceMetadataPacket (metadata: DeviceMetadata, fromNum: Int64, sessionPass
|
|||
func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObjectContext) -> NodeInfoEntity? {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.nodeinfo.received %@".localized, String(nodeInfo.num))
|
||||
Logger.mesh.info("📟 \(logString)")
|
||||
Logger.mesh.info("📟 \(logString, privacy: .public)")
|
||||
|
||||
guard nodeInfo.num > 0 else { return nil }
|
||||
|
||||
|
|
@ -349,12 +350,12 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje
|
|||
}
|
||||
do {
|
||||
try context.save()
|
||||
Logger.data.info("💾 Saved a new Node Info For: \(String(nodeInfo.num))")
|
||||
Logger.data.info("💾 Saved a new Node Info For: \(String(nodeInfo.num), privacy: .public)")
|
||||
return newNode
|
||||
} catch {
|
||||
context.rollback()
|
||||
let nsError = error as NSError
|
||||
Logger.data.error("Error Saving Core Data NodeInfoEntity: \(nsError)")
|
||||
Logger.data.error("Error Saving Core Data NodeInfoEntity: \(nsError, privacy: .public)")
|
||||
}
|
||||
} catch {
|
||||
Logger.data.error("Fetch MyInfo Error")
|
||||
|
|
@ -472,7 +473,7 @@ func adminAppPacket (packet: MeshPacket, context: NSManagedObjectContext) {
|
|||
if !cmmc.messages.isEmpty {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.cannedmessages.messages.received %@".localized, packet.from.toHex())
|
||||
Logger.mesh.info("🥫 \(logString)")
|
||||
Logger.mesh.info("🥫 \(logString, privacy: .public)")
|
||||
|
||||
let fetchNodeRequest = NodeInfoEntity.fetchRequest()
|
||||
fetchNodeRequest.predicate = NSPredicate(format: "num == %lld", Int64(packet.from))
|
||||
|
|
@ -547,7 +548,7 @@ func adminAppPacket (packet: MeshPacket, context: NSManagedObjectContext) {
|
|||
let ringtone = adminMessage.getRingtoneResponse
|
||||
upsertRtttlConfigPacket(ringtone: ringtone, nodeNum: Int64(packet.from), context: context)
|
||||
} else {
|
||||
Logger.mesh.error("🕸️ MESH PACKET received Admin App UNHANDLED \((try? packet.decoded.jsonString()) ?? "JSON Decode Failure")")
|
||||
Logger.mesh.error("🕸️ MESH PACKET received Admin App UNHANDLED \((try? packet.decoded.jsonString()) ?? "JSON Decode Failure", privacy: .public)")
|
||||
}
|
||||
// Save an ack for the admin message log for each admin message response received as we stopped sending acks if there is also a response to reduce airtime.
|
||||
adminResponseAck(packet: packet, context: context)
|
||||
|
|
@ -572,17 +573,17 @@ func adminResponseAck (packet: MeshPacket, context: NSManagedObjectContext) {
|
|||
do {
|
||||
try context.save()
|
||||
} catch {
|
||||
Logger.data.error("Failed to save admin message response as an ack: \(error.localizedDescription)")
|
||||
Logger.data.error("Failed to save admin message response as an ack: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
Logger.data.error("Failed to fetch admin message by requestID: \(error.localizedDescription)")
|
||||
Logger.data.error("Failed to fetch admin message by requestID: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
func paxCounterPacket (packet: MeshPacket, context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.paxcounter %@".localized, String(packet.from))
|
||||
Logger.mesh.info("🧑🤝🧑 \(logString)")
|
||||
Logger.mesh.info("🧑🤝🧑 \(logString, privacy: .public)")
|
||||
|
||||
let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest()
|
||||
fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(packet.from))
|
||||
|
|
@ -607,7 +608,7 @@ func paxCounterPacket (packet: MeshPacket, context: NSManagedObjectContext) {
|
|||
do {
|
||||
try context.save()
|
||||
} catch {
|
||||
Logger.data.error("Failed to save pax: \(error.localizedDescription)")
|
||||
Logger.data.error("Failed to save pax: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
} else {
|
||||
Logger.data.info("Node Info Not Found")
|
||||
|
|
@ -626,7 +627,7 @@ func routingPacket (packet: MeshPacket, connectedNodeNum: Int64, context: NSMana
|
|||
|
||||
let routingErrorString = routingError?.display ?? "unknown".localized
|
||||
let logString = String.localizedStringWithFormat("mesh.log.routing.message %@ %@".localized, String(packet.decoded.requestID), routingErrorString)
|
||||
Logger.mesh.info("🕸️ \(logString)")
|
||||
Logger.mesh.info("🕸️ \(logString, privacy: .public)")
|
||||
|
||||
let fetchMessageRequest = MessageEntity.fetchRequest()
|
||||
fetchMessageRequest.predicate = NSPredicate(format: "messageId == %lld", Int64(packet.decoded.requestID))
|
||||
|
|
@ -673,11 +674,11 @@ func routingPacket (packet: MeshPacket, connectedNodeNum: Int64, context: NSMana
|
|||
return
|
||||
}
|
||||
try context.save()
|
||||
Logger.data.info("💾 ACK Saved for Message: \(packet.decoded.requestID)")
|
||||
Logger.data.info("💾 ACK Saved for Message: \(packet.decoded.requestID, privacy: .public)")
|
||||
} catch {
|
||||
context.rollback()
|
||||
let nsError = error as NSError
|
||||
Logger.data.error("Error Saving ACK for message: \(packet.id) Error: \(nsError)")
|
||||
Logger.data.error("Error Saving ACK for message: \(packet.id, privacy: .public) Error: \(nsError, privacy: .public)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -687,7 +688,7 @@ func telemetryPacket(packet: MeshPacket, connectedNode: Int64, context: NSManage
|
|||
if let telemetryMessage = try? Telemetry(serializedBytes: packet.decoded.payload) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.telemetry.received %@".localized, String(packet.from))
|
||||
Logger.mesh.info("📈 \(logString)")
|
||||
Logger.mesh.info("📈 \(logString, privacy: .public)")
|
||||
|
||||
if telemetryMessage.variant != Telemetry.OneOf_Variant.deviceMetrics(telemetryMessage.deviceMetrics) && telemetryMessage.variant != Telemetry.OneOf_Variant.environmentMetrics(telemetryMessage.environmentMetrics) && telemetryMessage.variant != Telemetry.OneOf_Variant.localStats(telemetryMessage.localStats) && telemetryMessage.variant != Telemetry.OneOf_Variant.powerMetrics(telemetryMessage.powerMetrics) {
|
||||
/// Other unhandled telemetry packets
|
||||
|
|
@ -723,10 +724,20 @@ func telemetryPacket(packet: MeshPacket, connectedNode: Int64, context: NSManage
|
|||
telemetry.current = telemetryMessage.environmentMetrics.hasCurrent.then(telemetryMessage.environmentMetrics.current)
|
||||
telemetry.voltage = telemetryMessage.environmentMetrics.hasVoltage.then(telemetryMessage.environmentMetrics.voltage)
|
||||
telemetry.weight = telemetryMessage.environmentMetrics.hasWeight.then(telemetryMessage.environmentMetrics.weight)
|
||||
telemetry.distance = telemetryMessage.environmentMetrics.hasDistance.then(telemetryMessage.environmentMetrics.distance)
|
||||
telemetry.windSpeed = telemetryMessage.environmentMetrics.hasWindSpeed.then(telemetryMessage.environmentMetrics.windSpeed)
|
||||
telemetry.windGust = telemetryMessage.environmentMetrics.hasWindGust.then(telemetryMessage.environmentMetrics.windGust)
|
||||
telemetry.windLull = telemetryMessage.environmentMetrics.hasWindLull.then(telemetryMessage.environmentMetrics.windLull)
|
||||
telemetry.windDirection = telemetryMessage.environmentMetrics.hasWindDirection.then(Int32(truncatingIfNeeded: telemetryMessage.environmentMetrics.windDirection))
|
||||
telemetry.irLux = telemetryMessage.environmentMetrics.hasIrLux.then(telemetryMessage.environmentMetrics.irLux)
|
||||
telemetry.lux = telemetryMessage.environmentMetrics.hasLux.then(telemetryMessage.environmentMetrics.lux)
|
||||
telemetry.whiteLux = telemetryMessage.environmentMetrics.hasWhiteLux.then(telemetryMessage.environmentMetrics.whiteLux)
|
||||
telemetry.uvLux = telemetryMessage.environmentMetrics.hasUvLux.then(telemetryMessage.environmentMetrics.uvLux)
|
||||
telemetry.radiation = telemetryMessage.environmentMetrics.hasRadiation.then(telemetryMessage.environmentMetrics.radiation)
|
||||
telemetry.rainfall1H = telemetryMessage.environmentMetrics.hasRainfall1H.then(telemetryMessage.environmentMetrics.rainfall1H)
|
||||
telemetry.rainfall24H = telemetryMessage.environmentMetrics.hasRainfall24H.then(telemetryMessage.environmentMetrics.rainfall24H)
|
||||
telemetry.soilTemperature = telemetryMessage.environmentMetrics.hasSoilTemperature.then(telemetryMessage.environmentMetrics.soilTemperature)
|
||||
telemetry.soilMoisture = telemetryMessage.environmentMetrics.hasSoilMoisture.then(telemetryMessage.environmentMetrics.soilMoisture)
|
||||
telemetry.metricsType = 1
|
||||
} else if telemetryMessage.variant == Telemetry.OneOf_Variant.localStats(telemetryMessage.localStats) {
|
||||
// Local Stats for Live activity
|
||||
|
|
@ -771,7 +782,7 @@ func telemetryPacket(packet: MeshPacket, connectedNode: Int64, context: NSManage
|
|||
}
|
||||
try context.save()
|
||||
|
||||
Logger.data.info("💾 [TelemetryEntity] of type \(MetricsTypes(rawValue: Int(telemetry.metricsType))?.name ?? "Unknown Metrics Type") Saved for Node: \(packet.from.toHex())")
|
||||
Logger.data.info("💾 [TelemetryEntity] of type \(MetricsTypes(rawValue: Int(telemetry.metricsType))?.name ?? "Unknown Metrics Type", privacy: .public) Saved for Node: \(packet.from.toHex(), privacy: .public)")
|
||||
if telemetry.metricsType == 0 {
|
||||
// Connected Device Metrics
|
||||
// ------------------------
|
||||
|
|
@ -872,7 +883,7 @@ func textMessageAppPacket(
|
|||
}
|
||||
|
||||
if messageText?.count ?? 0 > 0 {
|
||||
Logger.mesh.info("💬 \("mesh.log.textmessage.received".localized)")
|
||||
Logger.mesh.info("💬 \("mesh.log.textmessage.received".localized, privacy: .public)")
|
||||
let messageUsers = UserEntity.fetchRequest()
|
||||
messageUsers.predicate = NSPredicate(format: "num IN %@", [packet.to, packet.from])
|
||||
do {
|
||||
|
|
@ -951,7 +962,7 @@ func textMessageAppPacket(
|
|||
var messageSaved = false
|
||||
do {
|
||||
try context.save()
|
||||
Logger.data.info("💾 Saved a new message for \(newMessage.messageId)")
|
||||
Logger.data.info("💾 Saved a new message for \(newMessage.messageId, privacy: .public)")
|
||||
messageSaved = true
|
||||
|
||||
if messageSaved {
|
||||
|
|
@ -981,7 +992,7 @@ func textMessageAppPacket(
|
|||
)
|
||||
]
|
||||
manager.schedule()
|
||||
Logger.services.debug("iOS Notification Scheduled for text message from \(newMessage.fromUser?.longName ?? "unknown".localized)")
|
||||
Logger.services.debug("iOS Notification Scheduled for text message from \(newMessage.fromUser?.longName ?? "unknown".localized, privacy: .public)")
|
||||
}
|
||||
} else if newMessage.fromUser != nil && newMessage.toUser == nil {
|
||||
let fetchMyInfoRequest = MyInfoEntity.fetchRequest()
|
||||
|
|
@ -1012,7 +1023,7 @@ func textMessageAppPacket(
|
|||
)
|
||||
]
|
||||
manager.schedule()
|
||||
Logger.services.debug("iOS Notification Scheduled for text message from \(newMessage.fromUser?.longName ?? "unknown".localized)")
|
||||
Logger.services.debug("iOS Notification Scheduled for text message from \(newMessage.fromUser?.longName ?? "unknown".localized, privacy: .public)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1024,7 +1035,7 @@ func textMessageAppPacket(
|
|||
} catch {
|
||||
context.rollback()
|
||||
let nsError = error as NSError
|
||||
Logger.data.error("Failed to save new MessageEntity \(nsError)")
|
||||
Logger.data.error("Failed to save new MessageEntity \(nsError, privacy: .public)")
|
||||
}
|
||||
} catch {
|
||||
Logger.data.error("Fetch Message To and From Users Error")
|
||||
|
|
@ -1035,7 +1046,7 @@ func textMessageAppPacket(
|
|||
func waypointPacket (packet: MeshPacket, context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.waypoint.received %@".localized, String(packet.from))
|
||||
Logger.mesh.info("📍 \(logString)")
|
||||
Logger.mesh.info("📍 \(logString, privacy: .public)")
|
||||
|
||||
let fetchWaypointRequest = WaypointEntity.fetchRequest()
|
||||
fetchWaypointRequest.predicate = NSPredicate(format: "id == %lld", Int64(packet.id))
|
||||
|
|
@ -1062,7 +1073,7 @@ func waypointPacket (packet: MeshPacket, context: NSManagedObjectContext) {
|
|||
waypoint.created = Date()
|
||||
do {
|
||||
try context.save()
|
||||
Logger.data.info("💾 Added Node Waypoint App Packet For: \(waypoint.id)")
|
||||
Logger.data.info("💾 Added Node Waypoint App Packet For: \(waypoint.id, privacy: .public)")
|
||||
let manager = LocalNotificationManager()
|
||||
let icon = String(UnicodeScalar(Int(waypoint.icon)) ?? "📍")
|
||||
let latitude = Double(waypoint.latitudeI) / 1e7
|
||||
|
|
@ -1077,12 +1088,12 @@ func waypointPacket (packet: MeshPacket, context: NSManagedObjectContext) {
|
|||
path: "meshtastic:///map?waypointid=\(waypoint.id)"
|
||||
)
|
||||
]
|
||||
Logger.data.debug("meshtastic:///map?waypointid=\(waypoint.id)")
|
||||
Logger.data.debug("meshtastic:///map?waypointid=\(waypoint.id, privacy: .public)")
|
||||
manager.schedule()
|
||||
} catch {
|
||||
context.rollback()
|
||||
let nsError = error as NSError
|
||||
Logger.data.error("Error Saving WaypointEntity from WAYPOINT_APP \(nsError)")
|
||||
Logger.data.error("Error Saving WaypointEntity from WAYPOINT_APP \(nsError, privacy: .public)")
|
||||
}
|
||||
} else {
|
||||
fetchedWaypoint[0].id = Int64(packet.id)
|
||||
|
|
@ -1100,11 +1111,11 @@ func waypointPacket (packet: MeshPacket, context: NSManagedObjectContext) {
|
|||
fetchedWaypoint[0].lastUpdated = Date()
|
||||
do {
|
||||
try context.save()
|
||||
Logger.data.info("💾 Updated Node Waypoint App Packet For: \(fetchedWaypoint[0].id)")
|
||||
Logger.data.info("💾 Updated Node Waypoint App Packet For: \(fetchedWaypoint[0].id, privacy: .public)")
|
||||
} catch {
|
||||
context.rollback()
|
||||
let nsError = error as NSError
|
||||
Logger.data.error("Error Saving WaypointEntity from WAYPOINT_APP \(nsError)")
|
||||
Logger.data.error("Error Saving WaypointEntity from WAYPOINT_APP \(nsError, privacy: .public)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,6 @@
|
|||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>_XCCurrentVersionName</key>
|
||||
<string>MeshtasticDataModelV 49.xcdatamodel</string>
|
||||
<string>MeshtasticDataModelV 50.xcdatamodel</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,504 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="23605" systemVersion="24D81" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
||||
<entity name="AmbientLightingConfigEntity" representedClassName="AmbientLightingConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="blue" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="current" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="green" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="ledState" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="red" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="ambientLightingConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="ambientLightingConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="BluetoothConfigEntity" representedClassName="BluetoothConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="deviceLoggingEnabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="fixedPin" optional="YES" attributeType="Integer 32" defaultValueString="123456" usesScalarValueType="YES"/>
|
||||
<attribute name="mode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="bluetoothConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="bluetoothConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="CannedMessageConfigEntity" representedClassName="CannedMessageConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerEventCcw" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerEventCw" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerEventPress" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerPinA" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerPinB" optional="YES" attributeType="Integer 32" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerPinPress" optional="YES" attributeType="Integer 32" usesScalarValueType="YES"/>
|
||||
<attribute name="messages" optional="YES" attributeType="String" minValueString="0" maxValueString="198"/>
|
||||
<attribute name="rotary1Enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="sendBell" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="updown1Enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<relationship name="cannedMessagesConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="cannedMessageConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="ChannelEntity" representedClassName="ChannelEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="downlinkEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="id" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="index" attributeType="Integer 32" minValueString="0" maxValueString="13" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="mute" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="name" optional="YES" attributeType="String"/>
|
||||
<attribute name="positionPrecision" optional="YES" attributeType="Integer 32" defaultValueString="32" usesScalarValueType="YES"/>
|
||||
<attribute name="psk" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="role" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="uplinkEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<relationship name="myInfoChannel" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MyInfoEntity" inverseName="channels" inverseEntity="MyInfoEntity"/>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="index"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="DetectionSensorConfigEntity" representedClassName="DetectionSensorConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="minimumBroadcastSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="monitorPin" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="name" optional="YES" attributeType="String"/>
|
||||
<attribute name="sendBell" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="stateBroadcastSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="triggerType" optional="YES" attributeType="Integer 32" usesScalarValueType="YES"/>
|
||||
<attribute name="usePullup" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<relationship name="detectionSensorConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="detectionSensorConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="DeviceConfigEntity" representedClassName="DeviceConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="buttonGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="buzzerGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="debugLogEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="disableTripleClick" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="doubleTapAsButtonPress" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="isManaged" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="ledHeartbeatEnabled" optional="YES" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<attribute name="nodeInfoBroadcastSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="rebroadcastMode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="role" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="serialEnabled" optional="YES" attributeType="Boolean" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="tripleClickAsAdHocPing" optional="YES" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<attribute name="tzdef" optional="YES" attributeType="String"/>
|
||||
<relationship name="deviceConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="deviceConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="DeviceMetadataEntity" representedClassName="DeviceMetadataEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="canShutdown" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="deviceStateVersion" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="excludedModules" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="firmwareVersion" optional="YES" attributeType="String"/>
|
||||
<attribute name="hasBluetooth" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="hasEthernet" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="hasWifi" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="hwModel" optional="YES" attributeType="String"/>
|
||||
<attribute name="positionFlags" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="role" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="time" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<relationship name="metadataNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="metadata" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="DisplayConfigEntity" representedClassName="DisplayConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="compassNorthTop" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="displayMode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="flipScreen" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="gpsFormat" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="headingBold" optional="YES" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<attribute name="oledType" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="screenCarouselInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="screenOnSeconds" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="units" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="wakeOnTapOrMotion" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<relationship name="displayConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="displayConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="ExternalNotificationConfigEntity" representedClassName="ExternalNotificationConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="active" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="alertBell" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="alertBellBuzzer" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="alertBellVibra" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="alertMessage" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="alertMessageBuzzer" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="alertMessageVibra" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="nagTimeout" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="output" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="outputBuzzer" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="outputMilliseconds" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="outputVibra" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="useI2SAsBuzzer" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="usePWM" optional="YES" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<relationship name="externalNotificationConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="externalNotificationConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="LocationEntity" representedClassName="LocationEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="altitude" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="heading" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="id" optional="YES" attributeType="Integer 32" usesScalarValueType="YES"/>
|
||||
<attribute name="latitudeI" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="longitudeI" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="speed" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="routeLocation" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="RouteEntity" inverseName="locations" inverseEntity="RouteEntity"/>
|
||||
</entity>
|
||||
<entity name="LoRaConfigEntity" representedClassName="LoRaConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="bandwidth" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="channelNum" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="codingRate" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="frequencyOffset" optional="YES" attributeType="Float" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="hopLimit" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="ignoreMqtt" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="modemPreset" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="okToMqtt" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="overrideDutyCycle" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="overrideFrequency" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="regionCode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="spreadFactor" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="sx126xRxBoostedGain" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="txEnabled" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<attribute name="txPower" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="usePreset" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<relationship name="loRaConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="loRaConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="MessageEntity" representedClassName="MessageEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="ackError" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="ackSNR" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="ackTimestamp" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="admin" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="adminDescription" optional="YES" attributeType="String"/>
|
||||
<attribute name="channel" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="isEmoji" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="messageId" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="messagePayload" optional="YES" attributeType="String" defaultValueString=""/>
|
||||
<attribute name="messagePayloadMarkdown" optional="YES" attributeType="String"/>
|
||||
<attribute name="messageTimestamp" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="pkiEncrypted" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="portNum" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="publicKey" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="read" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="realACK" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="receivedACK" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="replyID" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="rssi" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<relationship name="fromUser" optional="YES" maxCount="1" deletionRule="Nullify" ordered="YES" destinationEntity="UserEntity" inverseName="sentMessages" inverseEntity="UserEntity"/>
|
||||
<relationship name="toUser" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="UserEntity" inverseName="receivedMessages" inverseEntity="UserEntity"/>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="messageId"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="MQTTConfigEntity" representedClassName="MQTTConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="address" optional="YES" attributeType="String"/>
|
||||
<attribute name="enabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="encryptionEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="jsonEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="mapPositionPrecision" optional="YES" attributeType="Integer 32" defaultValueString="13" usesScalarValueType="YES"/>
|
||||
<attribute name="mapPublishIntervalSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="mapReportingEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="password" optional="YES" attributeType="String" maxValueString="30"/>
|
||||
<attribute name="proxyToClientEnabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="root" optional="YES" attributeType="String" defaultValueString="msh"/>
|
||||
<attribute name="tlsEnabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="username" optional="YES" attributeType="String" maxValueString="30"/>
|
||||
<relationship name="mqttConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="mqttConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="MyInfoEntity" representedClassName="MyInfoEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="adminIndex" optional="YES" attributeType="Integer 32" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="bleName" optional="YES" attributeType="String"/>
|
||||
<attribute name="deviceId" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="minAppVersion" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="myNodeNum" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="peripheralId" optional="YES" attributeType="String"/>
|
||||
<attribute name="rebootCount" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="registered" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<relationship name="channels" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="ChannelEntity" inverseName="myInfoChannel" inverseEntity="ChannelEntity"/>
|
||||
<relationship name="myInfoNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="myInfo" inverseEntity="NodeInfoEntity"/>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="myNodeNum"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="NetworkConfigEntity" representedClassName="NetworkConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="dns" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="enabledProtocols" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="ethEnabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="gateway" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="ip" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="ntpServer" optional="YES" attributeType="String"/>
|
||||
<attribute name="subnet" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="wifiEnabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="wifiMode" optional="YES" attributeType="Integer 32" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="wifiPsk" optional="YES" attributeType="String" minValueString="0" maxValueString="60"/>
|
||||
<attribute name="wifiSsid" optional="YES" attributeType="String" minValueString="0" maxValueString="30"/>
|
||||
<relationship name="networkConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="networkConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="NodeInfoEntity" representedClassName="NodeInfoEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="bleName" optional="YES" attributeType="String"/>
|
||||
<attribute name="channel" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="favorite" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="firstHeard" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="hopsAway" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="id" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="ignored" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="lastHeard" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="num" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="peripheralId" optional="YES" attributeType="String"/>
|
||||
<attribute name="rssi" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="sessionExpiration" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="sessionPasskey" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="viaMqtt" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<relationship name="ambientLightingConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="AmbientLightingConfigEntity" inverseName="ambientLightingConfigNode" inverseEntity="AmbientLightingConfigEntity"/>
|
||||
<relationship name="bluetoothConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="BluetoothConfigEntity" inverseName="bluetoothConfigNode" inverseEntity="BluetoothConfigEntity"/>
|
||||
<relationship name="cannedMessageConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="CannedMessageConfigEntity" inverseName="cannedMessagesConfigNode" inverseEntity="CannedMessageConfigEntity"/>
|
||||
<relationship name="detectionSensorConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="DetectionSensorConfigEntity" inverseName="detectionSensorConfigNode" inverseEntity="DetectionSensorConfigEntity"/>
|
||||
<relationship name="deviceConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="DeviceConfigEntity" inverseName="deviceConfigNode" inverseEntity="DeviceConfigEntity"/>
|
||||
<relationship name="displayConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="DisplayConfigEntity" inverseName="displayConfigNode" inverseEntity="DisplayConfigEntity"/>
|
||||
<relationship name="externalNotificationConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="ExternalNotificationConfigEntity" inverseName="externalNotificationConfigNode" inverseEntity="ExternalNotificationConfigEntity"/>
|
||||
<relationship name="loRaConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="LoRaConfigEntity" inverseName="loRaConfigNode" inverseEntity="LoRaConfigEntity"/>
|
||||
<relationship name="metadata" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="DeviceMetadataEntity" inverseName="metadataNode" inverseEntity="DeviceMetadataEntity"/>
|
||||
<relationship name="mqttConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MQTTConfigEntity" inverseName="mqttConfigNode" inverseEntity="MQTTConfigEntity"/>
|
||||
<relationship name="myInfo" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MyInfoEntity" inverseName="myInfoNode" inverseEntity="MyInfoEntity"/>
|
||||
<relationship name="networkConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NetworkConfigEntity" inverseName="networkConfigNode" inverseEntity="NetworkConfigEntity"/>
|
||||
<relationship name="pax" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="PaxCounterEntity" inverseName="paxNode" inverseEntity="PaxCounterEntity"/>
|
||||
<relationship name="paxCounterConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="PaxCounterConfigEntity" inverseName="paxCounterConfigNode" inverseEntity="PaxCounterConfigEntity"/>
|
||||
<relationship name="positionConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="PositionConfigEntity" inverseName="positionConfigNode" inverseEntity="PositionConfigEntity"/>
|
||||
<relationship name="positions" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="PositionEntity" inverseName="nodePosition" inverseEntity="PositionEntity"/>
|
||||
<relationship name="powerConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="PowerConfigEntity" inverseName="powerConfigNode" inverseEntity="PowerConfigEntity"/>
|
||||
<relationship name="rangeTestConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="RangeTestConfigEntity" inverseName="rangeTestConfigNode" inverseEntity="RangeTestConfigEntity"/>
|
||||
<relationship name="rtttlConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="RTTTLConfigEntity" inverseName="rtttlConfigNode" inverseEntity="RTTTLConfigEntity"/>
|
||||
<relationship name="securityConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="SecurityConfigEntity" inverseName="securityConfigNode" inverseEntity="SecurityConfigEntity"/>
|
||||
<relationship name="serialConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="SerialConfigEntity" inverseName="serialConfigNode" inverseEntity="SerialConfigEntity"/>
|
||||
<relationship name="storeForwardConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="StoreForwardConfigEntity" inverseName="storeForwardConfigNode" inverseEntity="StoreForwardConfigEntity"/>
|
||||
<relationship name="telemetries" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="TelemetryEntity" inverseName="nodeTelemetry" inverseEntity="TelemetryEntity"/>
|
||||
<relationship name="telemetryConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="TelemetryConfigEntity" inverseName="telemetryConfigNode" inverseEntity="TelemetryConfigEntity"/>
|
||||
<relationship name="traceRoutes" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="TraceRouteEntity" inverseName="node" inverseEntity="TraceRouteEntity"/>
|
||||
<relationship name="user" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="UserEntity" inverseName="userNode" inverseEntity="UserEntity"/>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="num"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="PaxCounterConfigEntity" representedClassName="PaxCounterConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="bleThreshold" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="updateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="wifiThreshold" optional="YES" attributeType="Integer 32" defaultValueString="-80" usesScalarValueType="YES"/>
|
||||
<relationship name="paxCounterConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="paxCounterConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="PaxCounterEntity" representedClassName="PaxCounterEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="ble" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="time" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="uptime" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="wifi" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="paxNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="pax" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="PositionConfigEntity" representedClassName="PositionConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="broadcastSmartMinimumDistance" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="broadcastSmartMinimumIntervalSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="deviceGpsEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="fixedPosition" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="gpsAttemptTime" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="gpsEnGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="gpsMode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="gpsUpdateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="positionBroadcastSeconds" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="positionFlags" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="rxGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="smartPositionEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="txGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="positionConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="positionConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="PositionEntity" representedClassName="PositionEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="altitude" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="heading" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="latest" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="latitudeI" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="longitudeI" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="precisionBits" optional="YES" attributeType="Integer 32" defaultValueString="32" usesScalarValueType="YES"/>
|
||||
<attribute name="rssi" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="satsInView" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="seqNo" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="speed" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="time" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<relationship name="nodePosition" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="positions" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="PowerConfigEntity" representedClassName="PowerConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="adcMultiplierOverride" optional="YES" attributeType="Float" usesScalarValueType="YES"/>
|
||||
<attribute name="deviceBatteryInaAddress" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="isPowerSaving" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="lsSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="minWakeSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="onBatteryShutdownAfterSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="waitBluetoothSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="powerConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="powerConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="RangeTestConfigEntity" representedClassName="RangeTestConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="save" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="sender" optional="YES" attributeType="Integer 32" usesScalarValueType="YES"/>
|
||||
<relationship name="rangeTestConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="rangeTestConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="RouteEntity" representedClassName="RouteEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="color" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="date" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="distance" optional="YES" attributeType="Double" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="elevationGain" optional="YES" attributeType="Double" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="endDate" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="id" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="name" optional="YES" attributeType="String"/>
|
||||
<attribute name="notes" optional="YES" attributeType="String"/>
|
||||
<relationship name="locations" optional="YES" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="LocationEntity" inverseName="routeLocation" inverseEntity="LocationEntity"/>
|
||||
</entity>
|
||||
<entity name="RTTTLConfigEntity" representedClassName="RTTTLConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="ringtone" optional="YES" attributeType="String" maxValueString="228" defaultValueString=""/>
|
||||
<relationship name="rtttlConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="rtttlConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="SecurityConfigEntity" representedClassName="SecurityConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="adminChannelEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="adminKey" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="adminKey2" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="adminKey3" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="bluetoothLoggingEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="debugLogApiEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="isManaged" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="privateKey" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="publicKey" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="serialEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<relationship name="securityConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="securityConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="SerialConfigEntity" representedClassName="SerialConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="baudRate" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="echo" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="mode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="overrideConsoleSerialPort" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="rxd" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="timeout" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="txd" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="serialConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="serialConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="StoreForwardConfigEntity" representedClassName="StoreForwardConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="enabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="heartbeat" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<attribute name="historyReturnMax" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="historyReturnWindow" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="isRouter" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="lastHeartbeat" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="lastRequest" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="records" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="storeForwardConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="storeForwardConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="TelemetryConfigEntity" representedClassName="TelemetryConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="deviceUpdateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="environmentDisplayFahrenheit" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="environmentMeasurementEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="environmentScreenEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="environmentUpdateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="powerMeasurementEnabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="powerScreenEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="powerUpdateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="telemetryConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="telemetryConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="TelemetryEntity" representedClassName="TelemetryEntity" syncable="YES">
|
||||
<attribute name="airUtilTx" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="barometricPressure" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="batteryLevel" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="channelUtilization" optional="YES" attributeType="Float" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="current" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="gasResistance" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="iaq" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="irLux" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="lux" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="metricsType" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="numOnlineNodes" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="numPacketsRx" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="numPacketsRxBad" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="numPacketsTx" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="numRxDupe" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="numTotalNodes" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="numTxRelay" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="numTxRelayCanceled" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="powerCh1Current" optional="YES" attributeType="Float" usesScalarValueType="YES"/>
|
||||
<attribute name="powerCh1Voltage" optional="YES" attributeType="Float" usesScalarValueType="YES"/>
|
||||
<attribute name="powerCh2Current" optional="YES" attributeType="Float" usesScalarValueType="YES"/>
|
||||
<attribute name="powerCh2Voltage" optional="YES" attributeType="Float" usesScalarValueType="YES"/>
|
||||
<attribute name="powerCh3Current" optional="YES" attributeType="Float" usesScalarValueType="YES"/>
|
||||
<attribute name="powerCh3Voltage" optional="YES" attributeType="Float" usesScalarValueType="YES"/>
|
||||
<attribute name="radiation" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="rainfall1H" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="rainfall24H" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="relativeHumidity" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="rssi" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="soilMoisture" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="soilTemperature" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="temperature" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="time" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="uptimeSeconds" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="uvLux" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="voltage" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="weight" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="whiteLux" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="windDirection" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="windGust" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="windLull" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="windSpeed" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<relationship name="nodeTelemetry" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="telemetries" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="TraceRouteEntity" representedClassName="TraceRouteEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="hasPositions" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="hopsBack" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="hopsTowards" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="id" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="response" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="routeBackText" optional="YES" attributeType="String"/>
|
||||
<attribute name="routeText" optional="YES" attributeType="String"/>
|
||||
<attribute name="sent" optional="YES" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="time" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<relationship name="hops" optional="YES" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="TraceRouteHopEntity" inverseName="traceRoute" inverseEntity="TraceRouteHopEntity"/>
|
||||
<relationship name="node" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="traceRoutes" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="TraceRouteHopEntity" representedClassName="TraceRouteHopEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="altitude" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="back" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="latitudeI" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="longitudeI" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="name" optional="YES" attributeType="String"/>
|
||||
<attribute name="num" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="time" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<relationship name="traceRoute" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="TraceRouteEntity" inverseName="hops" inverseEntity="TraceRouteEntity"/>
|
||||
</entity>
|
||||
<entity name="UserEntity" representedClassName="UserEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="hwDisplayName" optional="YES" attributeType="String"/>
|
||||
<attribute name="hwModel" attributeType="String"/>
|
||||
<attribute name="hwModelId" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="isLicensed" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="keyMatch" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<attribute name="lastMessage" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="longName" attributeType="String"/>
|
||||
<attribute name="mute" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="newPublicKey" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="num" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="numString" optional="YES" attributeType="String"/>
|
||||
<attribute name="pkiEncrypted" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="publicKey" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="role" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="shortName" attributeType="String"/>
|
||||
<attribute name="userId" attributeType="String"/>
|
||||
<relationship name="receivedMessages" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="MessageEntity" inverseName="toUser" inverseEntity="MessageEntity"/>
|
||||
<relationship name="sentMessages" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="MessageEntity" inverseName="fromUser" inverseEntity="MessageEntity"/>
|
||||
<relationship name="userNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="user" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="WaypointEntity" representedClassName="WaypointEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="created" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="expire" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="icon" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="id" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="lastUpdated" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="latitudeI" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="locked" attributeType="Integer 64" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="longDescription" optional="YES" attributeType="String" maxValueString="100"/>
|
||||
<attribute name="longitudeI" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="name" attributeType="String" minValueString="1" maxValueString="30"/>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="id"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
</model>
|
||||
|
|
@ -55,7 +55,7 @@ struct MeshtasticAppleApp: App {
|
|||
.presentationDragIndicator(.visible)
|
||||
}
|
||||
.onContinueUserActivity(NSUserActivityTypeBrowsingWeb) { userActivity in
|
||||
Logger.mesh.debug("URL received \(userActivity)")
|
||||
Logger.mesh.debug("URL received \(userActivity, privacy: .public)")
|
||||
self.incomingUrl = userActivity.webpageURL
|
||||
|
||||
if (self.incomingUrl?.absoluteString.lowercased().contains("meshtastic.org/e/#")) != nil {
|
||||
|
|
@ -72,18 +72,18 @@ struct MeshtasticAppleApp: App {
|
|||
}
|
||||
self.channelSettings = cs
|
||||
}
|
||||
Logger.services.debug("Add Channel \(self.addChannels)")
|
||||
Logger.services.debug("Add Channel \(self.addChannels, privacy: .public)")
|
||||
}
|
||||
self.saveChannels = true
|
||||
Logger.mesh.debug("User wants to open a Channel Settings URL: \(self.incomingUrl?.absoluteString ?? "No QR Code Link")")
|
||||
}
|
||||
if self.saveChannels {
|
||||
Logger.mesh.debug("User wants to open Channel Settings URL: \(String(describing: self.incomingUrl!.relativeString))")
|
||||
Logger.mesh.debug("User wants to open Channel Settings URL: \(String(describing: self.incomingUrl!.relativeString), privacy: .public)")
|
||||
}
|
||||
}
|
||||
.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: .public)")
|
||||
self.incomingUrl = url
|
||||
if url.absoluteString.lowercased().contains("meshtastic.org/e/#") {
|
||||
if let components = self.incomingUrl?.absoluteString.components(separatedBy: "#") {
|
||||
|
|
@ -99,10 +99,10 @@ struct MeshtasticAppleApp: App {
|
|||
}
|
||||
self.channelSettings = cs
|
||||
}
|
||||
Logger.services.debug("Add Channel \(self.addChannels)")
|
||||
Logger.services.debug("Add Channel \(self.addChannels, privacy: .public)")
|
||||
}
|
||||
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: .public)")
|
||||
} else if url.absoluteString.lowercased().contains("meshtastic:///") {
|
||||
appState.router.route(url: url)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -97,10 +97,10 @@ class MeshtasticAppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificat
|
|||
if let targetValue = userInfo["target"] as? String,
|
||||
let deepLink = userInfo["path"] as? String,
|
||||
let url = URL(string: deepLink) {
|
||||
Logger.services.info("userNotificationCenter didReceiveResponse \(targetValue) \(deepLink)")
|
||||
Logger.services.info("userNotificationCenter didReceiveResponse \(targetValue, privacy: .public) \(deepLink, privacy: .public)")
|
||||
router?.route(url: url)
|
||||
} else {
|
||||
Logger.services.error("Failed to handle notification response: \(userInfo)")
|
||||
Logger.services.error("Failed to handle notification response: \(userInfo, privacy: .public)")
|
||||
}
|
||||
completionHandler()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,5 +42,14 @@ public class TelemetryEntity: NSManagedObject, Identifiable {
|
|||
@ManagedAttribute<Float>(attributeName: "windGust") public var windGust: Float?
|
||||
@ManagedAttribute<Float>(attributeName: "windLull") public var windLull: Float?
|
||||
@ManagedAttribute<Float>(attributeName: "windSpeed") public var windSpeed: Float?
|
||||
@ManagedAttribute<Float>(attributeName: "irLux") public var irLux: Float?
|
||||
@ManagedAttribute<Float>(attributeName: "lux") public var lux: Float?
|
||||
@ManagedAttribute<Float>(attributeName: "uvLux") public var uvLux: Float?
|
||||
@ManagedAttribute<Float>(attributeName: "whiteLux") public var whiteLux: Float?
|
||||
@ManagedAttribute<Float>(attributeName: "radiation") public var radiation: Float?
|
||||
@ManagedAttribute<Float>(attributeName: "rainfall1H") public var rainfall1H: Float?
|
||||
@ManagedAttribute<Float>(attributeName: "rainfall24H") public var rainfall24H: Float?
|
||||
@ManagedAttribute<Float>(attributeName: "soilTemperature") public var soilTemperature: Float?
|
||||
@ManagedAttribute<UInt32>(attributeName: "soilMoisture") public var soilMoisture: UInt32?
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,12 +38,18 @@ class MetricsChartSeries: ObservableObject {
|
|||
// Possibly converted to the proper units
|
||||
let valueClosure: (TelemetryEntity) -> Float?
|
||||
|
||||
// Used for scaling the Y-axis
|
||||
let initialYAxisRange: ClosedRange<Float>?
|
||||
let minumumYAxisSpan: Float?
|
||||
|
||||
// Main initializer
|
||||
init<Value, ChartBody: ChartContent, ForegroundStyle: ShapeStyle>(
|
||||
id: String,
|
||||
keyPath: KeyPath<TelemetryEntity, Value>,
|
||||
name: String,
|
||||
abbreviatedName: String,
|
||||
initialYAxisRange: ClosedRange<Float>? = nil,
|
||||
minumumYAxisSpan: Float? = nil,
|
||||
conversion: ((Value) -> Value)? = nil,
|
||||
visible: Bool = true,
|
||||
foregroundStyle: @escaping ((ClosedRange<Float>?) -> ForegroundStyle?) = { _ in nil },
|
||||
|
|
@ -54,6 +60,8 @@ class MetricsChartSeries: ObservableObject {
|
|||
self.id = id
|
||||
self.name = name
|
||||
self.abbreviatedName = abbreviatedName
|
||||
self.initialYAxisRange = initialYAxisRange
|
||||
self.minumumYAxisSpan = minumumYAxisSpan
|
||||
self.visible = visible
|
||||
|
||||
// By saving these closures, MetricsChartSeries can be type agnostic
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
class MetricsSeriesList: ObservableObject, RandomAccessCollection, RangeReplaceableCollection {
|
||||
|
||||
@Published var series: [MetricsChartSeries]
|
||||
|
|
@ -38,23 +39,82 @@ class MetricsSeriesList: ObservableObject, RandomAccessCollection, RangeReplacea
|
|||
return nil
|
||||
}
|
||||
|
||||
// Calculates the chartRange based on the series configuration and data provided
|
||||
// Besides checkign the range of the data, this function also obeys some series-level
|
||||
// configuraiton, such as:
|
||||
// 1. starting with a desired fixed range
|
||||
// 2. obeying a minimum span
|
||||
func chartRange(forData data: [TelemetryEntity]) -> ClosedRange<Float> {
|
||||
var lower: Float?
|
||||
var upper: Float?
|
||||
var globalLower: Float = .infinity
|
||||
var globalUpper: Float = -.infinity
|
||||
|
||||
// Keep track of the range of each series
|
||||
var range: [MetricsChartSeries: ClosedRange<Float>] = [:]
|
||||
|
||||
// Determine if there is an initial fixed range.
|
||||
// The range might exapand past this initial range if the data goes beyond.
|
||||
for aSeries in self.visible {
|
||||
if let thisRange = aSeries.initialYAxisRange {
|
||||
range[aSeries] = thisRange
|
||||
if thisRange.upperBound > globalUpper {globalUpper = thisRange.upperBound}
|
||||
if thisRange.lowerBound < globalLower {globalLower = thisRange.lowerBound}
|
||||
}
|
||||
}
|
||||
|
||||
// Iterate through all the data. It would be easier to iterate
|
||||
// the series then the data, but this way we only iterate the data once
|
||||
for te in data {
|
||||
for aSeries in self.visible {
|
||||
var seriesUpper = range[aSeries]?.upperBound ?? -.infinity
|
||||
var seriesLower = range[aSeries]?.lowerBound ?? .infinity
|
||||
|
||||
if let value = aSeries.valueFor(te) {
|
||||
if value > (upper ?? -.infinity) {upper = value}
|
||||
if value < (lower ?? .infinity) {lower = value}
|
||||
// Update the global bounds
|
||||
if value > globalUpper {globalUpper = value}
|
||||
if value < globalLower {globalLower = value}
|
||||
|
||||
// Update the series bounds if necessary
|
||||
if value > seriesUpper || value < seriesLower {
|
||||
if value > seriesUpper {
|
||||
seriesUpper = value
|
||||
}
|
||||
if value < seriesLower {
|
||||
seriesLower = value
|
||||
}
|
||||
if seriesUpper.isFinite && seriesLower.isFinite {
|
||||
range[aSeries] = seriesLower...seriesUpper
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return default range if no data or nil
|
||||
guard let lower, let upper else {
|
||||
|
||||
// Go through each series one last time to obey the minimum span
|
||||
for aSeries in self.visible {
|
||||
if let minimumSpan = aSeries.minumumYAxisSpan,
|
||||
let currentRange = range[aSeries] {
|
||||
let currentSpan = currentRange.upperBound - currentRange.lowerBound
|
||||
if currentSpan < minimumSpan {
|
||||
// Calculate the center of the range
|
||||
let centerOfRange = currentRange.lowerBound + (currentSpan / 2)
|
||||
let newLower = centerOfRange - (minimumSpan / 2.0)
|
||||
let newUpper = centerOfRange + (minimumSpan / 2.0)
|
||||
|
||||
if newUpper > globalUpper {
|
||||
globalUpper = newUpper
|
||||
}
|
||||
if newLower < globalLower {
|
||||
globalLower = newLower
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return default range if no data
|
||||
if !globalLower.isFinite || !globalUpper.isFinite {
|
||||
return 0.0...100.0
|
||||
}
|
||||
return lower...upper
|
||||
return globalLower...globalUpper
|
||||
}
|
||||
|
||||
// Collection conformance
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ class PersistenceController {
|
|||
|
||||
if let error = error as NSError? {
|
||||
|
||||
Logger.data.error("CoreData Error: \(error.localizedDescription). Now attempting to truncate CoreData database. All app data will be lost.")
|
||||
Logger.data.error("CoreData Error: \(error.localizedDescription, privacy: .public). Now attempting to truncate CoreData database. All app data will be lost.")
|
||||
self.clearDatabase()
|
||||
}
|
||||
})
|
||||
|
|
@ -65,11 +65,11 @@ class PersistenceController {
|
|||
do {
|
||||
try persistentStoreCoordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: url, options: nil)
|
||||
} catch let error {
|
||||
Logger.data.error("Failed to re-create CoreData database: \(error.localizedDescription)")
|
||||
Logger.data.error("Failed to re-create CoreData database: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
|
||||
} catch let error {
|
||||
Logger.data.error("Failed to destroy CoreData database, delete the app and re-install to clear data. Attempted to clear persistent store: \(error.localizedDescription)")
|
||||
Logger.data.error("Failed to destroy CoreData database, delete the app and re-install to clear data. Attempted to clear persistent store: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -103,123 +103,4 @@ extension NSPersistentContainer {
|
|||
case invalidSource(String)
|
||||
}
|
||||
|
||||
/// Restore backup persistent stores located in the directory referenced by `backupURL`.
|
||||
/// **Be very careful with this**. To restore a persistent store, the current persistent store must be removed from the container. When that happens, **all currently loaded Core Data objects** will become invalid. Using them after restoring will cause your app to crash.
|
||||
/// When calling this method you **must** ensure that you do not continue to use any previously fetched managed objects or existing fetched results controllers. **If this method does not throw, that does not mean your app is safe.** You need to take extra steps to prevent crashes. The details vary depending on the nature of your app.
|
||||
/// - Parameter backupURL: A file URL containing backup copies of all currently loaded persistent stores.
|
||||
/// - Throws: `CopyPersistentStoreError` in various situations.
|
||||
/// - Returns: Nothing. If no errors are thrown, the restore is complete.
|
||||
func restorePersistentStore(from backupURL: URL) throws {
|
||||
guard backupURL.isFileURL else {
|
||||
throw CopyPersistentStoreErrors.invalidSource("Backup URL must be a file URL")
|
||||
}
|
||||
|
||||
var isDirectory: ObjCBool = false
|
||||
if FileManager.default.fileExists(atPath: backupURL.path, isDirectory: &isDirectory) {
|
||||
if !isDirectory.boolValue {
|
||||
throw CopyPersistentStoreErrors.invalidSource("Source URL must be a directory")
|
||||
}
|
||||
} else {
|
||||
throw CopyPersistentStoreErrors.invalidSource("Source URL must exist")
|
||||
}
|
||||
|
||||
for persistentStoreDescription in persistentStoreDescriptions {
|
||||
guard let loadedStoreURL = persistentStoreDescription.url else {
|
||||
continue
|
||||
}
|
||||
let backupStoreURL = backupURL.appendingPathComponent(loadedStoreURL.lastPathComponent)
|
||||
guard FileManager.default.fileExists(atPath: backupStoreURL.path) else {
|
||||
throw CopyPersistentStoreErrors.invalidSource("Missing backup store for \(backupStoreURL)")
|
||||
}
|
||||
do {
|
||||
let storeOptions = persistentStoreDescription.options
|
||||
let configurationName = persistentStoreDescription.configuration
|
||||
let storeType = persistentStoreDescription.type
|
||||
// Replace the current store with the backup copy. This has a side effect of removing the current store from the Core Data stack.
|
||||
// When restoring, it's necessary to use the current persistent store coordinator.
|
||||
try persistentStoreCoordinator.replacePersistentStore(at: loadedStoreURL, destinationOptions: storeOptions, withPersistentStoreFrom: backupStoreURL, sourceOptions: storeOptions, ofType: storeType)
|
||||
// Add the persistent store at the same location we've been using, because it was removed in the previous step.
|
||||
try persistentStoreCoordinator.addPersistentStore(ofType: storeType, configurationName: configurationName, at: loadedStoreURL, options: storeOptions)
|
||||
} catch {
|
||||
throw CopyPersistentStoreErrors.copyStoreError("Could not restore: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Copy all loaded persistent stores to a new directory. Each currently loaded file-based persistent store will be copied (including journal files, external binary storage, and anything else Core Data needs) into the destination directory to a persistent store with the same name and type as the existing store. In-memory stores, if any, are skipped.
|
||||
/// - Parameters:
|
||||
/// - destinationURL: Destination for new persistent store files. Must be a file URL. If `overwriting` is `false` and `destinationURL` exists, it must be a directory.
|
||||
/// - overwriting: If `true`, any existing copies of the persistent store will be replaced or updated. If `false`, existing copies will not be changed or remoted. When this is `false`, the destination persistent store file must not already exist.
|
||||
/// - Throws: `CopyPersistentStoreError`
|
||||
/// - Returns: Nothing. If no errors are thrown, all loaded persistent stores will be copied to the destination directory.
|
||||
func copyPersistentStores(to destinationURL: URL, overwriting: Bool = false) throws {
|
||||
|
||||
guard !destinationURL.relativeString.contains("/0/") else {
|
||||
throw CopyPersistentStoreErrors.invalidDestination("Invalid 0 Node Id")
|
||||
}
|
||||
|
||||
guard destinationURL.isFileURL else {
|
||||
throw CopyPersistentStoreErrors.invalidDestination("Destination URL must be a file URL")
|
||||
}
|
||||
// If the destination exists and we aren't overwriting it, then it must be a directory. (If we are overwriting, we'll remove it anyway, so it doesn't matter whether it's a directory).
|
||||
var isDirectory: ObjCBool = false
|
||||
if !overwriting && FileManager.default.fileExists(atPath: destinationURL.path, isDirectory: &isDirectory) {
|
||||
if !isDirectory.boolValue {
|
||||
throw CopyPersistentStoreErrors.invalidDestination("Destination URL must be a directory")
|
||||
}
|
||||
// Don't check if destination stores exist in the destination dir, that comes later on a per-store basis.
|
||||
}
|
||||
// If we're overwriting, remove the destination.
|
||||
if overwriting && FileManager.default.fileExists(atPath: destinationURL.path) {
|
||||
do {
|
||||
try FileManager.default.removeItem(at: destinationURL)
|
||||
} catch {
|
||||
throw CopyPersistentStoreErrors.destinationNotRemoved("Can't overwrite destination at \(destinationURL)")
|
||||
}
|
||||
}
|
||||
// Create the destination directory
|
||||
do {
|
||||
try FileManager.default.createDirectory(at: destinationURL, withIntermediateDirectories: true, attributes: nil)
|
||||
} catch {
|
||||
throw CopyPersistentStoreErrors.destinationError("Could not create destination directory at \(destinationURL)")
|
||||
}
|
||||
|
||||
for persistentStoreDescription in persistentStoreDescriptions {
|
||||
guard let storeURL = persistentStoreDescription.url else {
|
||||
continue
|
||||
}
|
||||
guard persistentStoreDescription.type != NSInMemoryStoreType else {
|
||||
continue
|
||||
}
|
||||
let destinationStoreURL = destinationURL.appendingPathComponent(storeURL.lastPathComponent)
|
||||
|
||||
if !overwriting && FileManager.default.fileExists(atPath: destinationStoreURL.path) {
|
||||
// If the destination exists, the replacePersistentStore call will update it in place. That's fine unless we're not overwriting.
|
||||
throw CopyPersistentStoreErrors.destinationError("Destination already exists at \(destinationStoreURL)")
|
||||
}
|
||||
do {
|
||||
// Replace an existing backup, if any, with a new one with the same options and type. This doesn't affect the current Core Data stack.
|
||||
// The function name says "replace", but it works if there's nothing at the destination yet. In that case it creates a new persistent store.
|
||||
// Note that for backup, it doesn't matter if the persistent store coordinator is the one currently in use or a different one. It could be a class function, for this use.
|
||||
try persistentStoreCoordinator.replacePersistentStore(at: destinationStoreURL, destinationOptions: persistentStoreDescription.options, withPersistentStoreFrom: storeURL, sourceOptions: persistentStoreDescription.options, ofType: persistentStoreDescription.type)
|
||||
/// Cleanup extra files
|
||||
let directory = destinationStoreURL.deletingLastPathComponent()
|
||||
/// Delete -wal file
|
||||
do {
|
||||
try FileManager.default.removeItem(at: directory.appendingPathComponent("Meshtastic.sqlite-wal"))
|
||||
/// Delete -shm file
|
||||
do {
|
||||
try FileManager.default.removeItem(at: directory.appendingPathComponent("Meshtastic.sqlite-shm"))
|
||||
} catch {
|
||||
Logger.services.error("🗄 Error Deleting Meshtastic.sqlite-shm file \(error, privacy: .public)")
|
||||
}
|
||||
} catch {
|
||||
Logger.services.error("🗄 Error Deleting Meshtastic.sqlite-wal file \(error, privacy: .public)")
|
||||
}
|
||||
} catch {
|
||||
Logger.services.error("🗄 Error Deleting Meshtastic.sqlite file \(error, privacy: .public)")
|
||||
throw CopyPersistentStoreErrors.copyStoreError("\(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ public func deleteChannelMessages(channel: ChannelEntity, context: NSManagedObje
|
|||
}
|
||||
try context.save()
|
||||
} catch let error as NSError {
|
||||
Logger.data.error("\(error.localizedDescription)")
|
||||
Logger.data.error("\(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -98,7 +98,7 @@ public func deleteUserMessages(user: UserEntity, context: NSManagedObjectContext
|
|||
}
|
||||
try context.save()
|
||||
} catch let error as NSError {
|
||||
Logger.data.error("\(error.localizedDescription)")
|
||||
Logger.data.error("\(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -122,7 +122,7 @@ public func clearCoreDataDatabase(context: NSManagedObjectContext, includeRoutes
|
|||
do {
|
||||
try context.executeAndMergeChanges(using: deleteRequest)
|
||||
} catch {
|
||||
Logger.data.error("\(error.localizedDescription)")
|
||||
Logger.data.error("\(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -130,7 +130,7 @@ public func clearCoreDataDatabase(context: NSManagedObjectContext, includeRoutes
|
|||
func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.nodeinfo.received %@".localized, packet.from.toHex())
|
||||
Logger.mesh.info("📟 \(logString)")
|
||||
Logger.mesh.info("📟 \(logString, privacy: .public)")
|
||||
|
||||
guard packet.from > 0 else { return }
|
||||
|
||||
|
|
@ -313,7 +313,7 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext)
|
|||
func upsertPositionPacket (packet: MeshPacket, context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.position.received %@".localized, String(packet.from))
|
||||
Logger.mesh.info("📍 \(logString)")
|
||||
Logger.mesh.info("📍 \(logString, privacy: .public)")
|
||||
|
||||
let fetchNodePositionRequest = NodeInfoEntity.fetchRequest()
|
||||
fetchNodePositionRequest.predicate = NSPredicate(format: "num == %lld", Int64(packet.from))
|
||||
|
|
@ -407,7 +407,7 @@ func upsertPositionPacket (packet: MeshPacket, context: NSManagedObjectContext)
|
|||
func upsertBluetoothConfigPacket(config: Config.BluetoothConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.bluetooth.config %@".localized, String(nodeNum))
|
||||
Logger.mesh.info("📶 \(logString)")
|
||||
Logger.mesh.info("📶 \(logString, privacy: .public)")
|
||||
|
||||
let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest()
|
||||
fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum))
|
||||
|
|
@ -451,7 +451,7 @@ func upsertBluetoothConfigPacket(config: Config.BluetoothConfig, nodeNum: Int64,
|
|||
func upsertDeviceConfigPacket(config: Config.DeviceConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.device.config %@".localized, String(nodeNum))
|
||||
Logger.mesh.info("📟 \(logString)")
|
||||
Logger.mesh.info("📟 \(logString, privacy: .public)")
|
||||
let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest()
|
||||
fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum))
|
||||
|
||||
|
|
@ -506,7 +506,7 @@ func upsertDeviceConfigPacket(config: Config.DeviceConfig, nodeNum: Int64, sessi
|
|||
func upsertDisplayConfigPacket(config: Config.DisplayConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.display.config %@".localized, nodeNum.toHex())
|
||||
Logger.data.info("🖥️ \(logString)")
|
||||
Logger.data.info("🖥️ \(logString, privacy: .public)")
|
||||
|
||||
let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest()
|
||||
fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum))
|
||||
|
|
@ -551,19 +551,15 @@ func upsertDisplayConfigPacket(config: Config.DisplayConfig, nodeNum: Int64, ses
|
|||
Logger.data.info("💾 [DisplayConfigEntity] Updated for node: \(nodeNum.toHex(), privacy: .public)")
|
||||
|
||||
} catch {
|
||||
|
||||
context.rollback()
|
||||
|
||||
let nsError = error as NSError
|
||||
Logger.data.error("💥 [DisplayConfigEntity] Error Updating Core Data: \(nsError, privacy: .public)")
|
||||
}
|
||||
} else {
|
||||
|
||||
Logger.data.error("💥 [DisplayConfigEntity] No Nodes found in local database matching node \(nodeNum.toHex()) unable to save Display Config")
|
||||
Logger.data.error("💥 [DisplayConfigEntity] No Nodes found in local database matching node \(nodeNum.toHex(), privacy: .public) unable to save Display Config")
|
||||
}
|
||||
|
||||
} catch {
|
||||
|
||||
let nsError = error as NSError
|
||||
Logger.data.error("💥 [DisplayConfigEntity] Fetching node for core data failed: \(nsError, privacy: .public)")
|
||||
}
|
||||
|
|
@ -572,7 +568,7 @@ func upsertDisplayConfigPacket(config: Config.DisplayConfig, nodeNum: Int64, ses
|
|||
func upsertLoRaConfigPacket(config: Config.LoRaConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.lora.config %@".localized, nodeNum.toHex())
|
||||
Logger.data.info("📻 \(logString)")
|
||||
Logger.data.info("📻 \(logString, privacy: .public)")
|
||||
|
||||
let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest()
|
||||
fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", nodeNum)
|
||||
|
|
@ -643,7 +639,7 @@ func upsertLoRaConfigPacket(config: Config.LoRaConfig, nodeNum: Int64, sessionPa
|
|||
func upsertNetworkConfigPacket(config: Config.NetworkConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.network.config %@".localized, String(nodeNum))
|
||||
Logger.data.info("🌐 \(logString)")
|
||||
Logger.data.info("🌐 \(logString, privacy: .public)")
|
||||
|
||||
let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest()
|
||||
fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum))
|
||||
|
|
@ -658,12 +654,14 @@ func upsertNetworkConfigPacket(config: Config.NetworkConfig, nodeNum: Int64, ses
|
|||
newNetworkConfig.wifiSsid = config.wifiSsid
|
||||
newNetworkConfig.wifiPsk = config.wifiPsk
|
||||
newNetworkConfig.ethEnabled = config.ethEnabled
|
||||
newNetworkConfig.enabledProtocols = Int32(config.enabledProtocols)
|
||||
fetchedNode[0].networkConfig = newNetworkConfig
|
||||
} else {
|
||||
fetchedNode[0].networkConfig?.ethEnabled = config.ethEnabled
|
||||
fetchedNode[0].networkConfig?.wifiEnabled = config.wifiEnabled
|
||||
fetchedNode[0].networkConfig?.wifiSsid = config.wifiSsid
|
||||
fetchedNode[0].networkConfig?.wifiPsk = config.wifiPsk
|
||||
fetchedNode[0].networkConfig?.enabledProtocols = Int32(config.enabledProtocols)
|
||||
}
|
||||
if sessionPasskey != nil {
|
||||
fetchedNode[0].sessionPasskey = sessionPasskey
|
||||
|
|
@ -690,7 +688,7 @@ func upsertNetworkConfigPacket(config: Config.NetworkConfig, nodeNum: Int64, ses
|
|||
func upsertPositionConfigPacket(config: Config.PositionConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.position.config %@".localized, String(nodeNum))
|
||||
Logger.data.info("🗺️ \(logString)")
|
||||
Logger.data.info("🗺️ \(logString, privacy: .public)")
|
||||
|
||||
let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest()
|
||||
fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum))
|
||||
|
|
@ -753,7 +751,7 @@ func upsertPositionConfigPacket(config: Config.PositionConfig, nodeNum: Int64, s
|
|||
|
||||
func upsertPowerConfigPacket(config: Config.PowerConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) {
|
||||
let logString = String.localizedStringWithFormat("mesh.log.power.config %@".localized, String(nodeNum))
|
||||
Logger.data.info("🗺️ \(logString)")
|
||||
Logger.data.info("🗺️ \(logString, privacy: .public)")
|
||||
|
||||
let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest()
|
||||
fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum))
|
||||
|
|
@ -805,7 +803,7 @@ func upsertPowerConfigPacket(config: Config.PowerConfig, nodeNum: Int64, session
|
|||
func upsertSecurityConfigPacket(config: Config.SecurityConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.security.config %@".localized, String(nodeNum))
|
||||
Logger.data.info("🛡️ \(logString)")
|
||||
Logger.data.info("🛡️ \(logString, privacy: .public)")
|
||||
|
||||
let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest()
|
||||
fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum))
|
||||
|
|
@ -866,7 +864,7 @@ func upsertSecurityConfigPacket(config: Config.SecurityConfig, nodeNum: Int64, s
|
|||
func upsertAmbientLightingModuleConfigPacket(config: ModuleConfig.AmbientLightingConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.ambientlighting.config %@".localized, String(nodeNum))
|
||||
Logger.data.info("🏮 \(logString)")
|
||||
Logger.data.info("🏮 \(logString, privacy: .public)")
|
||||
|
||||
let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest()
|
||||
fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum))
|
||||
|
|
@ -919,7 +917,7 @@ func upsertAmbientLightingModuleConfigPacket(config: ModuleConfig.AmbientLightin
|
|||
func upsertCannedMessagesModuleConfigPacket(config: ModuleConfig.CannedMessageConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.cannedmessage.config %@".localized, String(nodeNum))
|
||||
Logger.data.info("🥫 \(logString)")
|
||||
Logger.data.info("🥫 \(logString, privacy: .public)")
|
||||
|
||||
let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest()
|
||||
fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum))
|
||||
|
|
@ -978,7 +976,7 @@ func upsertCannedMessagesModuleConfigPacket(config: ModuleConfig.CannedMessageCo
|
|||
func upsertDetectionSensorModuleConfigPacket(config: ModuleConfig.DetectionSensorConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.detectionsensor.config %@".localized, String(nodeNum))
|
||||
Logger.data.info("🕵️ \(logString)")
|
||||
Logger.data.info("🕵️ \(logString, privacy: .public)")
|
||||
|
||||
let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest()
|
||||
fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum))
|
||||
|
|
@ -1035,7 +1033,7 @@ func upsertDetectionSensorModuleConfigPacket(config: ModuleConfig.DetectionSenso
|
|||
func upsertExternalNotificationModuleConfigPacket(config: ModuleConfig.ExternalNotificationConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.externalnotification.config %@".localized, String(nodeNum))
|
||||
Logger.data.info("📣 \(logString)")
|
||||
Logger.data.info("📣 \(logString, privacy: .public)")
|
||||
|
||||
let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest()
|
||||
fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum))
|
||||
|
|
@ -1104,7 +1102,7 @@ func upsertExternalNotificationModuleConfigPacket(config: ModuleConfig.ExternalN
|
|||
func upsertPaxCounterModuleConfigPacket(config: ModuleConfig.PaxcounterConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.paxcounter.config %@".localized, String(nodeNum))
|
||||
Logger.data.info("🧑🤝🧑 \(logString)")
|
||||
Logger.data.info("🧑🤝🧑 \(logString, privacy: .public)")
|
||||
|
||||
let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest()
|
||||
fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum))
|
||||
|
|
@ -1146,7 +1144,7 @@ func upsertPaxCounterModuleConfigPacket(config: ModuleConfig.PaxcounterConfig, n
|
|||
func upsertRtttlConfigPacket(ringtone: String, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.ringtone.config %@".localized, String(nodeNum))
|
||||
Logger.data.info("⛰️ \(logString)")
|
||||
Logger.data.info("⛰️ \(logString, privacy: .public)")
|
||||
|
||||
let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest()
|
||||
fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum))
|
||||
|
|
@ -1186,7 +1184,7 @@ func upsertRtttlConfigPacket(ringtone: String, nodeNum: Int64, sessionPasskey: D
|
|||
func upsertMqttModuleConfigPacket(config: ModuleConfig.MQTTConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.mqtt.config %@".localized, String(nodeNum))
|
||||
Logger.data.info("🌉 \(logString)")
|
||||
Logger.data.info("🌉 \(logString, privacy: .public)")
|
||||
|
||||
let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest()
|
||||
fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum))
|
||||
|
|
@ -1248,7 +1246,7 @@ func upsertMqttModuleConfigPacket(config: ModuleConfig.MQTTConfig, nodeNum: Int6
|
|||
func upsertRangeTestModuleConfigPacket(config: ModuleConfig.RangeTestConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.rangetest.config %@".localized, String(nodeNum))
|
||||
Logger.data.info("⛰️ \(logString)")
|
||||
Logger.data.info("⛰️ \(logString, privacy: .public)")
|
||||
|
||||
let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest()
|
||||
fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum))
|
||||
|
|
@ -1292,7 +1290,7 @@ func upsertRangeTestModuleConfigPacket(config: ModuleConfig.RangeTestConfig, nod
|
|||
func upsertSerialModuleConfigPacket(config: ModuleConfig.SerialConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.serial.config %@".localized, String(nodeNum))
|
||||
Logger.data.info("🤖 \(logString)")
|
||||
Logger.data.info("🤖 \(logString, privacy: .public)")
|
||||
|
||||
let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest()
|
||||
fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum))
|
||||
|
|
@ -1347,7 +1345,7 @@ func upsertSerialModuleConfigPacket(config: ModuleConfig.SerialConfig, nodeNum:
|
|||
func upsertStoreForwardModuleConfigPacket(config: ModuleConfig.StoreForwardConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.storeforward.config %@".localized, String(nodeNum))
|
||||
Logger.data.info("📬 \(logString)")
|
||||
Logger.data.info("📬 \(logString, privacy: .public)")
|
||||
|
||||
let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest()
|
||||
fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum))
|
||||
|
|
@ -1396,7 +1394,7 @@ func upsertStoreForwardModuleConfigPacket(config: ModuleConfig.StoreForwardConfi
|
|||
func upsertTelemetryModuleConfigPacket(config: ModuleConfig.TelemetryConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.telemetry.config %@".localized, String(nodeNum))
|
||||
Logger.data.info("📈 \(logString)")
|
||||
Logger.data.info("📈 \(logString, privacy: .public)")
|
||||
|
||||
let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest()
|
||||
fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum))
|
||||
|
|
|
|||
|
|
@ -87,7 +87,8 @@
|
|||
"images": [
|
||||
"t-echo.svg"
|
||||
],
|
||||
"requiresDfu": true
|
||||
"requiresDfu": true,
|
||||
"hasInkHud": true
|
||||
},
|
||||
{
|
||||
"hwModel": 8,
|
||||
|
|
@ -153,17 +154,21 @@
|
|||
"images": [
|
||||
"tbeam-s3-core.svg"
|
||||
],
|
||||
"requiresDfu": true
|
||||
"requiresDfu": true,
|
||||
"partitionScheme": "8MB"
|
||||
},
|
||||
{
|
||||
"hwModel": 13,
|
||||
"hwModelSlug": "RAK11200",
|
||||
"platformioTarget": "rak11200",
|
||||
"architecture": "esp32",
|
||||
"activelySupported": false,
|
||||
"activelySupported": true,
|
||||
"displayName": "RAK WisBlock 11200",
|
||||
"tags": [
|
||||
"RAK"
|
||||
],
|
||||
"images": [
|
||||
"rak11200.svg"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -188,7 +193,7 @@
|
|||
"displayName": "LILYGO T-LoRa V2.1-1.8",
|
||||
"tags": [
|
||||
"LilyGo",
|
||||
"2.4G LoRA"
|
||||
"2.4GHz"
|
||||
],
|
||||
"images": [
|
||||
"tlora-v2-1-1_8.svg"
|
||||
|
|
@ -338,7 +343,8 @@
|
|||
"requiresDfu": true,
|
||||
"images": [
|
||||
"station-g2.svg"
|
||||
]
|
||||
],
|
||||
"partitionScheme": "16MB"
|
||||
},
|
||||
{
|
||||
"hwModel": 39,
|
||||
|
|
@ -404,7 +410,8 @@
|
|||
"images": [
|
||||
"heltec-v3.svg",
|
||||
"heltec-v3-case.svg"
|
||||
]
|
||||
],
|
||||
"partitionScheme": "8MB"
|
||||
},
|
||||
{
|
||||
"hwModel": 44,
|
||||
|
|
@ -419,7 +426,8 @@
|
|||
],
|
||||
"images": [
|
||||
"heltec-wsl-v3.svg"
|
||||
]
|
||||
],
|
||||
"partitionScheme": "8MB"
|
||||
},
|
||||
{
|
||||
"hwModel": 47,
|
||||
|
|
@ -469,7 +477,8 @@
|
|||
"images": [
|
||||
"heltec-wireless-tracker.svg"
|
||||
],
|
||||
"requiresDfu": true
|
||||
"requiresDfu": true,
|
||||
"partitionScheme": "8MB"
|
||||
},
|
||||
{
|
||||
"hwModel": 58,
|
||||
|
|
@ -482,7 +491,8 @@
|
|||
"images": [
|
||||
"heltec-wireless-tracker.svg"
|
||||
],
|
||||
"requiresDfu": true
|
||||
"requiresDfu": true,
|
||||
"partitionScheme": "8MB"
|
||||
},
|
||||
{
|
||||
"hwModel": 49,
|
||||
|
|
@ -497,7 +507,9 @@
|
|||
],
|
||||
"images": [
|
||||
"heltec-wireless-paper.svg"
|
||||
]
|
||||
],
|
||||
"hasInkHud": true,
|
||||
"partitionScheme": "8MB"
|
||||
},
|
||||
{
|
||||
"hwModel": 50,
|
||||
|
|
@ -513,7 +525,9 @@
|
|||
"images": [
|
||||
"t-deck.svg"
|
||||
],
|
||||
"requiresDfu": true
|
||||
"requiresDfu": true,
|
||||
"hasMui": true,
|
||||
"partitionScheme": "16MB"
|
||||
},
|
||||
{
|
||||
"hwModel": 51,
|
||||
|
|
@ -528,7 +542,8 @@
|
|||
],
|
||||
"images": [
|
||||
"t-watch-s3.svg"
|
||||
]
|
||||
],
|
||||
"partitionScheme": "16MB"
|
||||
},
|
||||
{
|
||||
"hwModel": 52,
|
||||
|
|
@ -537,7 +552,9 @@
|
|||
"architecture": "esp32-s3",
|
||||
"activelySupported": true,
|
||||
"supportLevel": 3,
|
||||
"displayName": "Pi Computer S3"
|
||||
"displayName": "Pi Computer S3",
|
||||
"hasMui": true,
|
||||
"partitionScheme": "8MB"
|
||||
},
|
||||
{
|
||||
"hwModel": 53,
|
||||
|
|
@ -567,7 +584,8 @@
|
|||
"displayName": "Heltec Wireless Paper V1.0",
|
||||
"images": [
|
||||
"heltec-wireless-paper-v1_0.svg"
|
||||
]
|
||||
],
|
||||
"partitionScheme": "8MB"
|
||||
},
|
||||
{
|
||||
"hwModel": 59,
|
||||
|
|
@ -577,36 +595,41 @@
|
|||
"activelySupported": true,
|
||||
"supportLevel": 3,
|
||||
"displayName": "unPhone",
|
||||
"requiresDfu": true
|
||||
"requiresDfu": true,
|
||||
"hasMui": true,
|
||||
"partitionScheme": "8MB"
|
||||
},
|
||||
{
|
||||
"hwModel": 48,
|
||||
"hwModelSlug": "HELTEC_WIRELESS_TRACKER",
|
||||
"platformioTarget": "tracksenger",
|
||||
"architecture": "esp32-s3",
|
||||
"activelySupported": true,
|
||||
"activelySupported": false,
|
||||
"supportLevel": 3,
|
||||
"displayName": "TrackSenger (small TFT)",
|
||||
"requiresDfu": true
|
||||
"requiresDfu": true,
|
||||
"partitionScheme": "8MB"
|
||||
},
|
||||
{
|
||||
"hwModel": 48,
|
||||
"hwModelSlug": "HELTEC_WIRELESS_TRACKER",
|
||||
"platformioTarget": "tracksenger-lcd",
|
||||
"architecture": "esp32-s3",
|
||||
"activelySupported": true,
|
||||
"activelySupported": false,
|
||||
"supportLevel": 3,
|
||||
"displayName": "TrackSenger (big TFT)",
|
||||
"requiresDfu": true
|
||||
"requiresDfu": true,
|
||||
"partitionScheme": "8MB"
|
||||
},
|
||||
{
|
||||
"hwModel": 48,
|
||||
"hwModelSlug": "HELTEC_WIRELESS_TRACKER",
|
||||
"platformioTarget": "tracksenger-oled",
|
||||
"architecture": "esp32-s3",
|
||||
"activelySupported": true,
|
||||
"activelySupported": false,
|
||||
"supportLevel": 3,
|
||||
"displayName": "TrackSenger (big OLED)"
|
||||
"displayName": "TrackSenger (big OLED)",
|
||||
"partitionScheme": "8MB"
|
||||
},
|
||||
{
|
||||
"hwModel": 61,
|
||||
|
|
@ -647,7 +670,8 @@
|
|||
"images": [
|
||||
"heltec-vision-master-t190.svg"
|
||||
],
|
||||
"requiresDfu": true
|
||||
"requiresDfu": true,
|
||||
"partitionScheme": "8MB"
|
||||
},
|
||||
{
|
||||
"hwModel": 67,
|
||||
|
|
@ -663,7 +687,9 @@
|
|||
"images": [
|
||||
"heltec-vision-master-e213.svg"
|
||||
],
|
||||
"requiresDfu": true
|
||||
"requiresDfu": true,
|
||||
"hasInkHud": true,
|
||||
"partitionScheme": "8MB"
|
||||
},
|
||||
{
|
||||
"hwModel": 68,
|
||||
|
|
@ -679,7 +705,9 @@
|
|||
"images": [
|
||||
"heltec-vision-master-e290.svg"
|
||||
],
|
||||
"requiresDfu": true
|
||||
"requiresDfu": true,
|
||||
"hasInkHud": true,
|
||||
"partitionScheme": "8MB"
|
||||
},
|
||||
{
|
||||
"hwModel": 69,
|
||||
|
|
@ -711,7 +739,9 @@
|
|||
],
|
||||
"images": [
|
||||
"seeed-sensecap-indicator.svg"
|
||||
]
|
||||
],
|
||||
"hasMui": true,
|
||||
"partitionScheme": "8MB"
|
||||
},
|
||||
{
|
||||
"hwModel": 71,
|
||||
|
|
@ -730,8 +760,8 @@
|
|||
"requiresDfu": true
|
||||
},
|
||||
{
|
||||
"hwModel": 72,
|
||||
"hwModelSlug": "Seeed_XIAO_S3",
|
||||
"hwModel": 81,
|
||||
"hwModelSlug": "SEEED_XIAO_S3",
|
||||
"platformioTarget": "seeed-xiao-s3",
|
||||
"architecture": "esp32-s3",
|
||||
"activelySupported": true,
|
||||
|
|
@ -743,14 +773,15 @@
|
|||
"images": [
|
||||
"seeed-xiao-s3.svg"
|
||||
],
|
||||
"requiresDfu": true
|
||||
"requiresDfu": true,
|
||||
"partitionScheme": "8MB"
|
||||
},
|
||||
{
|
||||
"hwModel": 84,
|
||||
"hwModelSlug": "WISMESH_TAP",
|
||||
"platformioTarget": "rak_wismeshtap",
|
||||
"architecture": "nrf52840",
|
||||
"activelySupported": false,
|
||||
"activelySupported": true,
|
||||
"supportLevel": 1,
|
||||
"displayName": "RAK WisMesh Tap",
|
||||
"tags": [
|
||||
|
|
@ -760,5 +791,79 @@
|
|||
"rak-wismeshtap.svg"
|
||||
],
|
||||
"requiresDfu": true
|
||||
},
|
||||
{
|
||||
"hwModel": 22,
|
||||
"hwModelSlug": "WISMESH_HUB",
|
||||
"platformioTarget": "rak2560",
|
||||
"architecture": "nrf52840",
|
||||
"activelySupported": true,
|
||||
"supportLevel": 1,
|
||||
"displayName": "RAK WisMesh Repeater",
|
||||
"tags": [
|
||||
"RAK"
|
||||
],
|
||||
"images": [
|
||||
"rak2560.svg"
|
||||
],
|
||||
"requiresDfu": true
|
||||
},
|
||||
{
|
||||
"hwModel": 63,
|
||||
"hwModelSlug": "NRF52_PROMICRO_DIY",
|
||||
"platformioTarget": "nrf52_promicro_diy_tcxo",
|
||||
"architecture": "nrf52840",
|
||||
"activelySupported": true,
|
||||
"supportLevel": 3,
|
||||
"displayName": "NRF52 Pro-micro DIY",
|
||||
"tags": [
|
||||
"DIY"
|
||||
],
|
||||
"images": [
|
||||
"promicro.svg"
|
||||
],
|
||||
"requiresDfu": true
|
||||
},
|
||||
{
|
||||
"hwModel": 88,
|
||||
"hwModelSlug": "XIAO_NRF52_KIT",
|
||||
"platformioTarget": "seeed_xiao_nrf52840_kit",
|
||||
"architecture": "nrf52840",
|
||||
"activelySupported": true,
|
||||
"supportLevel": 1,
|
||||
"displayName": "Seeed Xiao NRF52840 Kit",
|
||||
"tags": [
|
||||
"Seeed"
|
||||
],
|
||||
"requiresDfu": true,
|
||||
"images": [
|
||||
"seeed_xiao_nrf52_kit.svg"
|
||||
]
|
||||
},
|
||||
{
|
||||
"hwModel": 89,
|
||||
"hwModelSlug": "THINKNODE_M1",
|
||||
"platformioTarget": "thinknode_m1",
|
||||
"architecture": "nrf52840",
|
||||
"activelySupported": false,
|
||||
"supportLevel": 1,
|
||||
"displayName": "ThinkNode M1",
|
||||
"tags": [
|
||||
"Elecrow"
|
||||
],
|
||||
"requiresDfu": true
|
||||
},
|
||||
{
|
||||
"hwModel": 90,
|
||||
"hwModelSlug": "THINKNODE_M2",
|
||||
"platformioTarget": "thinknode_m2",
|
||||
"architecture": "esp32-s3",
|
||||
"activelySupported": false,
|
||||
"supportLevel": 1,
|
||||
"displayName": "ThinkNode M2",
|
||||
"tags": [
|
||||
"Elecrow"
|
||||
],
|
||||
"requiresDfu": false
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ class Router: ObservableObject {
|
|||
} else if components.path.hasPrefix("/settings") {
|
||||
routeSettings(components)
|
||||
} else {
|
||||
Logger.services.warning("Failed to route url: \(url)")
|
||||
Logger.services.warning("Failed to route url: \(url, privacy: .public)")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ struct Connect: View {
|
|||
@State var isUnsetRegion = false
|
||||
@State var invalidFirmwareVersion = false
|
||||
@State var liveActivityStarted = false
|
||||
@State var presentingSwitchPreferredPeripheral = false
|
||||
@State var selectedPeripherialId = ""
|
||||
|
||||
init () {
|
||||
|
|
@ -34,7 +35,7 @@ struct Connect: View {
|
|||
if success {
|
||||
Logger.services.info("Notifications are all set!")
|
||||
} else if let error = error {
|
||||
Logger.services.error("\(error.localizedDescription)")
|
||||
Logger.services.error("\(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -61,9 +62,9 @@ struct Connect: View {
|
|||
.padding(.trailing)
|
||||
VStack(alignment: .leading) {
|
||||
if node != nil {
|
||||
Text(connectedPeripheral.longName).font(.title2)
|
||||
Text(connectedPeripheral.longName.addingVariationSelectors).font(.title2)
|
||||
}
|
||||
Text("BLE Name").font(.callout)+Text(": \(bleManager.connectedPeripheral?.peripheral.name ?? "unknown".localized)")
|
||||
Text("BLE Name").font(.callout)+Text(": \(bleManager.connectedPeripheral?.peripheral.name?.addingVariationSelectors ?? "unknown".localized)")
|
||||
.font(.callout).foregroundColor(Color.gray)
|
||||
if node != nil {
|
||||
Text("firmware.version").font(.callout)+Text(": \(node?.metadata?.firmwareVersion ?? "unknown".localized)")
|
||||
|
|
@ -120,7 +121,7 @@ struct Connect: View {
|
|||
#endif
|
||||
Text("Num: \(String(node!.num))")
|
||||
Text("Short Name: \(node?.user?.shortName ?? "?")")
|
||||
Text("Long Name: \(node?.user?.longName ?? "unknown".localized)")
|
||||
Text("Long Name: \(node?.user?.longName?.addingVariationSelectors ?? "unknown".localized)")
|
||||
Text("BLE RSSI: \(connectedPeripheral.rssi)")
|
||||
|
||||
Button {
|
||||
|
|
@ -214,22 +215,11 @@ struct Connect: View {
|
|||
if let connectedPeripheral = bleManager.connectedPeripheral, connectedPeripheral.peripheral.state == CBPeripheralState.connected {
|
||||
bleManager.disconnectPeripheral()
|
||||
}
|
||||
let container = NSPersistentContainer(name: "Meshtastic")
|
||||
guard let url = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else {
|
||||
Logger.data.error("nil File path for back")
|
||||
return
|
||||
}
|
||||
do {
|
||||
try container.copyPersistentStores(to: url.appendingPathComponent("backup").appendingPathComponent("\(UserDefaults.preferredPeripheralNum)"), overwriting: true)
|
||||
Logger.data.notice("🗂️ Made a core data backup to backup/\(UserDefaults.preferredPeripheralNum)")
|
||||
|
||||
} catch {
|
||||
Logger.data.error("🗂️ Core data backup copy error: \(error, privacy: .public)")
|
||||
}
|
||||
clearCoreDataDatabase(context: context, includeRoutes: false)
|
||||
presentingSwitchPreferredPeripheral = true
|
||||
selectedPeripherialId = peripheral.peripheral.identifier.uuidString
|
||||
} else {
|
||||
self.bleManager.connectTo(peripheral: peripheral.peripheral)
|
||||
}
|
||||
UserDefaults.preferredPeripheralId = selectedPeripherialId
|
||||
self.bleManager.connectTo(peripheral: peripheral.peripheral)
|
||||
}) {
|
||||
Text(peripheral.name).font(.callout)
|
||||
}
|
||||
|
|
@ -240,6 +230,21 @@ struct Connect: View {
|
|||
}.padding([.bottom, .top])
|
||||
}
|
||||
}
|
||||
.confirmationDialog("Connecting to a new radio will clear all app data on the phone.", isPresented: $presentingSwitchPreferredPeripheral, titleVisibility: .visible) {
|
||||
Button("Connect to new radio?", role: .destructive) {
|
||||
UserDefaults.preferredPeripheralId = selectedPeripherialId
|
||||
UserDefaults.preferredPeripheralNum = 0
|
||||
if bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral.peripheral.state == CBPeripheralState.connected {
|
||||
bleManager.disconnectPeripheral()
|
||||
}
|
||||
clearCoreDataDatabase(context: context, includeRoutes: false)
|
||||
let radio = bleManager.peripherals.first(where: { $0.peripheral.identifier.uuidString == selectedPeripherialId })
|
||||
if radio != nil {
|
||||
bleManager.connectTo(peripheral: radio!.peripheral)
|
||||
}
|
||||
}
|
||||
}
|
||||
.textCase(nil)
|
||||
}
|
||||
|
||||
} else {
|
||||
|
|
@ -319,7 +324,7 @@ struct Connect: View {
|
|||
isUnsetRegion = false
|
||||
}
|
||||
} catch {
|
||||
Logger.data.error("💥 Error fetching node info: \(error.localizedDescription)")
|
||||
Logger.data.error("💥 Error fetching node info: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -333,7 +338,7 @@ struct Connect: View {
|
|||
let localStats = node?.telemetries?.filtered(using: NSPredicate(format: "metricsType == 4"))
|
||||
let mostRecent = localStats?.lastObject as? TelemetryEntity
|
||||
|
||||
let activityAttributes = MeshActivityAttributes(nodeNum: Int(node?.num ?? 0), name: node?.user?.longName ?? "unknown")
|
||||
let activityAttributes = MeshActivityAttributes(nodeNum: Int(node?.num ?? 0), name: node?.user?.longName?.addingVariationSelectors ?? "unknown")
|
||||
|
||||
let future = Date(timeIntervalSinceNow: Double(timerSeconds))
|
||||
let initialContentState = MeshActivityAttributes.ContentState(uptimeSeconds: UInt32(mostRecent?.uptimeSeconds ?? 0),
|
||||
|
|
@ -356,7 +361,7 @@ struct Connect: View {
|
|||
pushType: nil)
|
||||
Logger.services.info("Requested MyActivity live activity. ID: \(myActivity.id)")
|
||||
} catch {
|
||||
Logger.services.error("Error requesting live activity: \(error.localizedDescription)")
|
||||
Logger.services.error("Error requesting live activity: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -53,6 +53,5 @@ struct ContentView: View {
|
|||
}
|
||||
.tag(NavigationState.Tab.settings)
|
||||
}
|
||||
.toolbarBackground(.visible, for: .tabBar)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ struct CircleText: View {
|
|||
Circle()
|
||||
.fill(color)
|
||||
.frame(width: circleSize, height: circleSize)
|
||||
Text(text)
|
||||
Text(text.addingVariationSelectors)
|
||||
.frame(width: circleSize * 0.9, height: circleSize * 0.9, alignment: .center)
|
||||
.foregroundColor(color.isLight() ? .black : .white)
|
||||
.minimumScaleFactor(0.001)
|
||||
|
|
|
|||
47
Meshtastic/Views/Helpers/Compact Widgets/CompactWidget.swift
Normal file
47
Meshtastic/Views/Helpers/Compact Widgets/CompactWidget.swift
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
//
|
||||
// CompactWidget.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Created by Jake Bordens on 3/14/25.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
// This file was created for the purpose of previewing
|
||||
// all of the Compact Widgets in one place.
|
||||
|
||||
// In the future, it could be used for a CompactWidget superclass, if desired.
|
||||
|
||||
#Preview {
|
||||
|
||||
let gridItemLayout = Array(repeating: GridItem(.flexible(), spacing: 10), count: 2)
|
||||
Form {
|
||||
LazyVGrid(columns: gridItemLayout) {
|
||||
HumidityCompactWidget(humidity: 27, dewPoint: "32°")
|
||||
HumidityCompactWidget(humidity: 27, dewPoint: nil)
|
||||
WeatherConditionsCompactWidget(temperature: "24°F", symbolName: "sun.rain.fill", description: "Raining")
|
||||
PressureCompactWidget(pressure: "1004.76", unit: "hPA", low: true)
|
||||
PressureCompactWidget(pressure: "1004.76", unit: "hPA", low: false)
|
||||
WindCompactWidget(speed: "12 mph", gust: "15 mph", direction: "SW")
|
||||
WindCompactWidget(speed: "12 mph", gust: nil, direction: "SW")
|
||||
WindCompactWidget(speed: "12 mph", gust: "15 mph", direction: nil)
|
||||
WindCompactWidget(speed: "12 mph", gust: nil, direction: nil)
|
||||
RadiationCompactWidget(radiation: "15", unit: "µR/hr")
|
||||
DistanceCompactWidget(distance: "123", unit: "mm")
|
||||
WeightCompactWidget(weight: "123", unit: "kg")
|
||||
SoilTemperatureCompactWidget(temperature: "23", unit: "°C")
|
||||
SoilMoistureCompactWidget(moisture: "23", unit: "%")
|
||||
|
||||
let rain: Float = 10.1
|
||||
let locale = NSLocale.current as NSLocale
|
||||
let usesMetricSystem = locale.usesMetricSystem // Returns true for metric (mm), false for imperial (inches)
|
||||
let unit = usesMetricSystem ? UnitLength.millimeters : UnitLength.inches
|
||||
let unitLabel = usesMetricSystem ? "mm" : "in"
|
||||
let measurement = Measurement(value: Double(rain), unit: UnitLength.millimeters)
|
||||
let decimals = usesMetricSystem ? 0 : 1
|
||||
let formattedRain = measurement.converted(to: unit).value.formatted(.number.precision(.fractionLength(decimals)))
|
||||
RainfallCompactWidget(timespan: .rainfall1H, rainfall: formattedRain, unit: unitLabel)
|
||||
RainfallCompactWidget(timespan: .rainfall24H, rainfall: formattedRain, unit: unitLabel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -17,6 +17,7 @@ struct CurrentConditionsCompact: View {
|
|||
.symbolRenderingMode(.multicolor)
|
||||
}
|
||||
}
|
||||
|
||||
struct CurrentConditionsCompact_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
//
|
||||
// DistanceCompactWidget.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Created by Jake Bordens on 3/14/25.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct DistanceCompactWidget: View {
|
||||
let distance: String
|
||||
let unit: String
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
HStack(alignment: .firstTextBaseline) {
|
||||
Image(systemName: "ruler")
|
||||
.imageScale(.small)
|
||||
.foregroundColor(.accentColor)
|
||||
Text("Distance")
|
||||
.textCase(.uppercase)
|
||||
.font(.callout)
|
||||
}
|
||||
HStack {
|
||||
Text("\(distance)")
|
||||
.font(distance.length < 4 ? .system(size: 50) : .system(size: 40) )
|
||||
Text(unit)
|
||||
.font(.system(size: 14))
|
||||
}
|
||||
}
|
||||
.frame(minWidth: 100, idealWidth: 125, maxWidth: 150, minHeight: 120, idealHeight: 130, maxHeight: 140)
|
||||
.padding()
|
||||
.background(.tertiary, in: RoundedRectangle(cornerRadius: 20, style: .continuous))
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
DistanceCompactWidget(distance: "123", unit: "mm")
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
//
|
||||
// HumidityCompactWidget.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Created by Jake Bordens on 3/14/25.
|
||||
//
|
||||
import SwiftUI
|
||||
|
||||
struct HumidityCompactWidget: View {
|
||||
let humidity: Int
|
||||
let dewPoint: String?
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
HStack(spacing: 5.0) {
|
||||
Image(systemName: "humidity")
|
||||
.foregroundColor(.accentColor)
|
||||
.font(.callout)
|
||||
Text("Humidity")
|
||||
.textCase(.uppercase)
|
||||
.font(.caption)
|
||||
}
|
||||
Text("\(humidity)%")
|
||||
.font(.largeTitle)
|
||||
.padding(.bottom, 5)
|
||||
if let dewPoint {
|
||||
Text("The dew point is \(dewPoint) right now.")
|
||||
.lineLimit(3)
|
||||
.allowsTightening(true)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.font(.caption2)
|
||||
}
|
||||
}
|
||||
.frame(minWidth: 100, idealWidth: 125, maxWidth: 150, minHeight: 120, idealHeight: 130, maxHeight: 140)
|
||||
.padding()
|
||||
.background(.tertiary, in: RoundedRectangle(cornerRadius: 20, style: .continuous))
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
let gridItemLayout = Array(repeating: GridItem(.flexible(), spacing: 10), count: 2)
|
||||
Form {
|
||||
LazyVGrid(columns: gridItemLayout) {
|
||||
HumidityCompactWidget(humidity: 27, dewPoint: "32°")
|
||||
HumidityCompactWidget(humidity: 27, dewPoint: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
//
|
||||
// PressureCompactWidget.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Created by Jake Bordens on 3/14/25.
|
||||
//
|
||||
import SwiftUI
|
||||
|
||||
struct PressureCompactWidget: View {
|
||||
let pressure: String
|
||||
let unit: String
|
||||
let low: Bool
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
HStack(spacing: 5.0) {
|
||||
Image(systemName: "gauge")
|
||||
.foregroundColor(.accentColor)
|
||||
.font(.callout)
|
||||
Text("Pressure")
|
||||
.textCase(.uppercase)
|
||||
.font(.caption)
|
||||
}
|
||||
Text(pressure)
|
||||
.font(pressure.length < 7 ? .system(size: 35) : .system(size: 30) )
|
||||
Text(low ? "LOW" : "HIGH")
|
||||
.padding(.bottom, 10)
|
||||
Text(unit)
|
||||
}
|
||||
.frame(minWidth: 100, idealWidth: 125, maxWidth: 150, minHeight: 120, idealHeight: 130, maxHeight: 140)
|
||||
.padding()
|
||||
.background(.tertiary, in: RoundedRectangle(cornerRadius: 20, style: .continuous))
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
let gridItemLayout = Array(repeating: GridItem(.flexible(), spacing: 10), count: 2)
|
||||
Form {
|
||||
LazyVGrid(columns: gridItemLayout) {
|
||||
PressureCompactWidget(pressure: "1004.76", unit: "hPA", low: true)
|
||||
PressureCompactWidget(pressure: "1004.76", unit: "hPA", low: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
//
|
||||
// RadiationCompactWidget.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Created by Jake Bordens on 3/14/25.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct RadiationCompactWidget: View {
|
||||
let radiation: String
|
||||
let unit: String
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
HStack(alignment: .firstTextBaseline) {
|
||||
Text(verbatim: "☢")
|
||||
.font(.system(size: 30, design: .monospaced))
|
||||
.foregroundColor(.accentColor)
|
||||
Text("Radiation")
|
||||
.textCase(.uppercase)
|
||||
.font(.callout)
|
||||
}
|
||||
HStack {
|
||||
Text("\(radiation)")
|
||||
.font(radiation.length < 4 ? .system(size: 50) : .system(size: 34) )
|
||||
Text(unit)
|
||||
.font(.system(size: 14))
|
||||
}
|
||||
}
|
||||
.frame(minWidth: 100, idealWidth: 125, maxWidth: 150, minHeight: 120, idealHeight: 130, maxHeight: 140)
|
||||
.padding()
|
||||
.background(.tertiary, in: RoundedRectangle(cornerRadius: 20, style: .continuous))
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
RadiationCompactWidget(radiation: "15", unit: "µR/hr")
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
//
|
||||
// RainfallCompactWidgets.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Created by Jake Bordens on 3/15/25.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct RainfallCompactWidget: View {
|
||||
enum RainfallTimeSpan: String {
|
||||
case rainfall1H = "Rainfall 1H"
|
||||
case rainfall24H = "Rainfall 24H"
|
||||
}
|
||||
|
||||
let timespan: RainfallTimeSpan
|
||||
let rainfall: String
|
||||
let unit: String
|
||||
|
||||
private var icon: Image {
|
||||
if timespan == .rainfall1H {
|
||||
return Image(systemName: "cloud.rain.fill")
|
||||
}
|
||||
return Image(systemName: "cloud.heavyrain.fill")
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
HStack(alignment: .firstTextBaseline) {
|
||||
icon.imageScale(.small)
|
||||
.foregroundColor(.accentColor)
|
||||
Text(timespan.rawValue)
|
||||
.textCase(.uppercase)
|
||||
.font(.callout)
|
||||
}
|
||||
HStack {
|
||||
Text("\(rainfall)")
|
||||
.font(rainfall.length < 4 ? .system(size: 50) : .system(size: 40) )
|
||||
Text(unit)
|
||||
.font(.system(size: 14))
|
||||
}
|
||||
}
|
||||
.frame(minWidth: 100, idealWidth: 125, maxWidth: 150, minHeight: 120, idealHeight: 130, maxHeight: 140)
|
||||
.padding()
|
||||
.background(.tertiary, in: RoundedRectangle(cornerRadius: 20, style: .continuous))
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
let gridItemLayout = Array(repeating: GridItem(.flexible(), spacing: 10), count: 2)
|
||||
Form {
|
||||
LazyVGrid(columns: gridItemLayout) {
|
||||
let rain: Float = 10.1
|
||||
let locale = NSLocale.current as NSLocale
|
||||
let usesMetricSystem = locale.usesMetricSystem // Returns true for metric (mm), false for imperial (inches)
|
||||
let unit = usesMetricSystem ? UnitLength.millimeters : UnitLength.inches
|
||||
let unitLabel = usesMetricSystem ? "mm" : "in"
|
||||
let measurement = Measurement(value: Double(rain), unit: UnitLength.millimeters)
|
||||
let decimals = usesMetricSystem ? 0 : 1
|
||||
let formattedRain = measurement.converted(to: unit).value.formatted(.number.precision(.fractionLength(decimals)))
|
||||
|
||||
RainfallCompactWidget(timespan: .rainfall1H, rainfall: formattedRain, unit: unitLabel)
|
||||
RainfallCompactWidget(timespan: .rainfall24H, rainfall: formattedRain, unit: unitLabel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
//
|
||||
// SoilCompactWidgets.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Created by Jake Bordens on 3/14/25.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct SoilTemperatureCompactWidget: View {
|
||||
let temperature: String
|
||||
let unit: String
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
HStack(alignment: .firstTextBaseline) {
|
||||
Image("soil.temperature")
|
||||
.imageScale(.small)
|
||||
.foregroundColor(.accentColor)
|
||||
Text("Soil Temp")
|
||||
.textCase(.uppercase)
|
||||
.font(.callout)
|
||||
}
|
||||
HStack {
|
||||
Text("\(temperature)")
|
||||
.font(temperature.length < 4 ? .system(size: 50) : .system(size: 40) )
|
||||
Text(unit)
|
||||
.font(.system(size: 14))
|
||||
}
|
||||
}
|
||||
.frame(minWidth: 100, idealWidth: 125, maxWidth: 150, minHeight: 120, idealHeight: 130, maxHeight: 140)
|
||||
.padding()
|
||||
.background(.tertiary, in: RoundedRectangle(cornerRadius: 20, style: .continuous))
|
||||
}
|
||||
}
|
||||
|
||||
struct SoilMoistureCompactWidget: View {
|
||||
let moisture: String
|
||||
let unit: String
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
HStack(alignment: .firstTextBaseline) {
|
||||
Image("soil.moisture")
|
||||
.imageScale(.small)
|
||||
.foregroundColor(.accentColor)
|
||||
Text("Soil Moisture")
|
||||
.textCase(.uppercase)
|
||||
.font(.callout)
|
||||
}
|
||||
HStack {
|
||||
Text("\(moisture)")
|
||||
.font(moisture.length < 4 ? .system(size: 50) : .system(size: 40) )
|
||||
Text(unit)
|
||||
.font(.system(size: 14))
|
||||
}
|
||||
}
|
||||
.frame(minWidth: 100, idealWidth: 125, maxWidth: 150, minHeight: 120, idealHeight: 130, maxHeight: 140)
|
||||
.padding()
|
||||
.background(.tertiary, in: RoundedRectangle(cornerRadius: 20, style: .continuous))
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
let gridItemLayout = Array(repeating: GridItem(.flexible(), spacing: 10), count: 2)
|
||||
Form {
|
||||
LazyVGrid(columns: gridItemLayout) {
|
||||
SoilTemperatureCompactWidget(temperature: "23", unit: "°C")
|
||||
SoilMoistureCompactWidget(moisture: "23", unit: "%")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
//
|
||||
// WeatherConditionsCompactWidget.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Created by Jake Bordens on 3/14/25.
|
||||
//
|
||||
import SwiftUI
|
||||
|
||||
struct WeatherConditionsCompactWidget: View {
|
||||
let temperature: String
|
||||
let symbolName: String
|
||||
let description: String
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
HStack(spacing: 5.0) {
|
||||
Image(systemName: symbolName)
|
||||
.foregroundColor(.accentColor)
|
||||
.font(.callout)
|
||||
Text(description)
|
||||
.lineLimit(2)
|
||||
.allowsTightening(/*@START_MENU_TOKEN@*/true/*@END_MENU_TOKEN@*/)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.font(.caption)
|
||||
}
|
||||
Text(temperature)
|
||||
.font(temperature.length < 4 ? .system(size: 72) : .system(size: 54) )
|
||||
}
|
||||
.frame(minWidth: 100, idealWidth: 125, maxWidth: 150, minHeight: 120, idealHeight: 130, maxHeight: 140)
|
||||
.padding()
|
||||
.background(.tertiary, in: RoundedRectangle(cornerRadius: 20, style: .continuous))
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
let gridItemLayout = Array(repeating: GridItem(.flexible(), spacing: 10), count: 2)
|
||||
Form {
|
||||
LazyVGrid(columns: gridItemLayout) {
|
||||
WeatherConditionsCompactWidget(temperature: "24°F", symbolName: "sun.rain.fill", description: "Raining")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
//
|
||||
// WeightCompactWidget.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Created by Jake Bordens on 3/14/25.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct WeightCompactWidget: View {
|
||||
let weight: String
|
||||
let unit: String
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
HStack(alignment: .firstTextBaseline) {
|
||||
Image(systemName: "scalemass")
|
||||
.imageScale(.small)
|
||||
.foregroundColor(.accentColor)
|
||||
Text("Weight")
|
||||
.textCase(.uppercase)
|
||||
.font(.callout)
|
||||
}
|
||||
HStack {
|
||||
Text("\(weight)")
|
||||
.font(weight.length < 4 ? .system(size: 50) : .system(size: 40) )
|
||||
Text(unit)
|
||||
.font(.system(size: 14))
|
||||
}
|
||||
}
|
||||
.frame(minWidth: 100, idealWidth: 125, maxWidth: 150, minHeight: 120, idealHeight: 130, maxHeight: 140)
|
||||
.padding()
|
||||
.background(.tertiary, in: RoundedRectangle(cornerRadius: 20, style: .continuous))
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
WeightCompactWidget(weight: "123", unit: "kg")
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
//
|
||||
// WindCompactWidget.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Created by Jake Bordens on 3/14/25.
|
||||
//
|
||||
import SwiftUI
|
||||
|
||||
struct WindCompactWidget: View {
|
||||
let speed: String
|
||||
let gust: String?
|
||||
let direction: String?
|
||||
|
||||
var body: some View {
|
||||
let hasGust = ((gust ?? "").isEmpty == false)
|
||||
VStack(alignment: .leading) {
|
||||
Label { Text("Wind").textCase(.uppercase) } icon: { Image(systemName: "wind").foregroundColor(.accentColor) }
|
||||
if let direction {
|
||||
Text("\(direction)")
|
||||
.font(!hasGust ? .callout : .caption)
|
||||
.padding(.bottom, 10)
|
||||
}
|
||||
Text(speed)
|
||||
.font(.system(size: 35))
|
||||
if let gust, !gust.isEmpty {
|
||||
Text("Gusts \(gust)")
|
||||
}
|
||||
}
|
||||
.frame(minWidth: 100, idealWidth: 125, maxWidth: 150, minHeight: 120, idealHeight: 130, maxHeight: 140)
|
||||
.padding()
|
||||
.background(.tertiary, in: RoundedRectangle(cornerRadius: 20, style: .continuous))
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
let gridItemLayout = Array(repeating: GridItem(.flexible(), spacing: 10), count: 2)
|
||||
Form {
|
||||
LazyVGrid(columns: gridItemLayout) {
|
||||
WindCompactWidget(speed: "12 mph", gust: "15 mph", direction: "SW")
|
||||
WindCompactWidget(speed: "12 mph", gust: nil, direction: "SW")
|
||||
WindCompactWidget(speed: "12 mph", gust: "15 mph", direction: nil)
|
||||
WindCompactWidget(speed: "12 mph", gust: nil, direction: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -28,7 +28,7 @@ struct ConnectedDevice: View {
|
|||
.imageScale(.large)
|
||||
.foregroundColor(.green)
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text(name).font(name.isEmoji() ? .title : .callout).foregroundColor(.gray)
|
||||
Text(name.addingVariationSelectors).font(name.isEmoji() ? .title : .callout).foregroundColor(.gray)
|
||||
} else {
|
||||
Image(systemName: "antenna.radiowaves.left.and.right.slash")
|
||||
.imageScale(.medium)
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ struct LocalWeatherConditions: View {
|
|||
attributionLogo = colorScheme == .light ? attribution.combinedMarkLightURL : attribution.combinedMarkDarkURL
|
||||
}
|
||||
} catch {
|
||||
Logger.services.error("Could not gather weather information: \(error.localizedDescription)")
|
||||
Logger.services.error("Could not gather weather information: \(error.localizedDescription, privacy: .public)")
|
||||
condition = .clear
|
||||
symbolName = "cloud.fill"
|
||||
}
|
||||
|
|
@ -89,113 +89,6 @@ struct LocalWeatherConditions: View {
|
|||
}
|
||||
}
|
||||
|
||||
struct WeatherConditionsCompactWidget: View {
|
||||
let temperature: String
|
||||
let symbolName: String
|
||||
let description: String
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
HStack(spacing: 5.0) {
|
||||
Image(systemName: symbolName)
|
||||
.foregroundColor(.accentColor)
|
||||
.font(.callout)
|
||||
Text(description)
|
||||
.lineLimit(2)
|
||||
.allowsTightening(/*@START_MENU_TOKEN@*/true/*@END_MENU_TOKEN@*/)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.font(.caption)
|
||||
}
|
||||
Text(temperature)
|
||||
.font(temperature.length < 4 ? .system(size: 72) : .system(size: 54) )
|
||||
}
|
||||
.frame(minWidth: 100, idealWidth: 125, maxWidth: 150, minHeight: 120, idealHeight: 130, maxHeight: 140)
|
||||
.padding()
|
||||
.background(.tertiary, in: RoundedRectangle(cornerRadius: 20, style: .continuous))
|
||||
}
|
||||
}
|
||||
|
||||
struct HumidityCompactWidget: View {
|
||||
let humidity: Int
|
||||
let dewPoint: String?
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
HStack(spacing: 5.0) {
|
||||
Image(systemName: "humidity")
|
||||
.foregroundColor(.accentColor)
|
||||
.font(.callout)
|
||||
Text("Humidity")
|
||||
.textCase(.uppercase)
|
||||
.font(.caption)
|
||||
}
|
||||
Text("\(humidity)%")
|
||||
.font(.largeTitle)
|
||||
.padding(.bottom, 5)
|
||||
if let dewPoint {
|
||||
Text("The dew point is \(dewPoint) right now.")
|
||||
.lineLimit(3)
|
||||
.allowsTightening(true)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.font(.caption2)
|
||||
}
|
||||
}
|
||||
.frame(minWidth: 100, idealWidth: 125, maxWidth: 150, minHeight: 120, idealHeight: 130, maxHeight: 140)
|
||||
.padding()
|
||||
.background(.tertiary, in: RoundedRectangle(cornerRadius: 20, style: .continuous))
|
||||
}
|
||||
}
|
||||
|
||||
struct PressureCompactWidget: View {
|
||||
let pressure: String
|
||||
let unit: String
|
||||
let low: Bool
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
HStack(spacing: 5.0) {
|
||||
Image(systemName: "gauge")
|
||||
.foregroundColor(.accentColor)
|
||||
.font(.callout)
|
||||
Text("Pressure")
|
||||
.textCase(.uppercase)
|
||||
.font(.caption)
|
||||
}
|
||||
Text(pressure)
|
||||
.font(pressure.length < 7 ? .system(size: 35) : .system(size: 30) )
|
||||
Text(low ? "LOW" : "HIGH")
|
||||
.padding(.bottom, 10)
|
||||
Text(unit)
|
||||
}
|
||||
.frame(minWidth: 100, idealWidth: 125, maxWidth: 150, minHeight: 120, idealHeight: 130, maxHeight: 140)
|
||||
.padding()
|
||||
.background(.tertiary, in: RoundedRectangle(cornerRadius: 20, style: .continuous))
|
||||
}
|
||||
}
|
||||
|
||||
struct WindCompactWidget: View {
|
||||
let speed: String
|
||||
let gust: String?
|
||||
let direction: String?
|
||||
|
||||
var body: some View {
|
||||
let hasGust = ((gust ?? "").isEmpty == false)
|
||||
VStack(alignment: .leading) {
|
||||
Label { Text("Wind").textCase(.uppercase) } icon: { Image(systemName: "wind").foregroundColor(.accentColor) }
|
||||
if let direction {
|
||||
Text("\(direction)")
|
||||
.font(!hasGust ? .callout : .caption)
|
||||
.padding(.bottom, 10)
|
||||
}
|
||||
Text(speed)
|
||||
.font(!hasGust ? .system(size: 45) : .system(size: 35))
|
||||
if let gust, !gust.isEmpty {
|
||||
Text("Gusts \(gust)")
|
||||
}
|
||||
}
|
||||
.frame(minWidth: 100, idealWidth: 125, maxWidth: 150, minHeight: 120, idealHeight: 130, maxHeight: 140)
|
||||
.padding()
|
||||
.background(.tertiary, in: RoundedRectangle(cornerRadius: 20, style: .continuous))
|
||||
}
|
||||
}
|
||||
|
||||
/// Magnus Formula
|
||||
func calculateDewPoint(temp: Float, relativeHumidity: Float) -> Double {
|
||||
let a: Float = 17.27
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ struct NodeWeatherForecastView: View {
|
|||
)
|
||||
})
|
||||
} catch {
|
||||
Logger.services.error("Could not load weather: \(error.localizedDescription)")
|
||||
Logger.services.error("Could not load weather: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,64 +0,0 @@
|
|||
//
|
||||
//// MapButtons.swift
|
||||
//// Meshtastic
|
||||
////
|
||||
//// Copyright © Garth Vander Houwen 4/23/23.
|
||||
////
|
||||
//
|
||||
//import SwiftUI
|
||||
//
|
||||
//struct MapButtons: View {
|
||||
// let buttonWidth: CGFloat = 22
|
||||
// let width: CGFloat = 45
|
||||
// @Binding var tracking: UserTrackingModes
|
||||
// @Binding var isPresentingInfoSheet: Bool
|
||||
// var body: some View {
|
||||
// VStack {
|
||||
// let impactLight = UIImpactFeedbackGenerator(style: .light)
|
||||
// Button(action: {
|
||||
// self.isPresentingInfoSheet.toggle()
|
||||
// }) {
|
||||
// Image(systemName: isPresentingInfoSheet ? "info.circle.fill" : "info.circle")
|
||||
// .resizable()
|
||||
// .frame(width: buttonWidth, height: buttonWidth, alignment: .center)
|
||||
// .offset(y: -2)
|
||||
// }
|
||||
// Divider()
|
||||
// Button(action: {
|
||||
// switch self.tracking {
|
||||
// case .none:
|
||||
// self.tracking = .follow
|
||||
// case .follow:
|
||||
// self.tracking = .followWithHeading
|
||||
// case .followWithHeading:
|
||||
// self.tracking = .none
|
||||
// }
|
||||
// impactLight.impactOccurred()
|
||||
// }) {
|
||||
// Image(systemName: tracking.icon)
|
||||
// .frame(width: buttonWidth, height: buttonWidth, alignment: .center)
|
||||
// .offset(y: 3)
|
||||
// }
|
||||
// }
|
||||
// .frame(width: width, height: width*2, alignment: .center)
|
||||
// .background(Color(UIColor.systemBackground))
|
||||
// .cornerRadius(8)
|
||||
// .shadow(radius: 1)
|
||||
// .offset(x: 3, y: 25)
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//// MARK: Previews
|
||||
//// struct MapControl_Previews: PreviewProvider {
|
||||
//// @State static var tracking: UserTrackingModes = .none
|
||||
//// @State static var isPresentingInfoSheet = false
|
||||
//// static var previews: some View {
|
||||
//// Group {
|
||||
//// MapButtons(tracking: $tracking, isPresentingInfoSheet: $isPresentingInfoSheet)
|
||||
//// .environment(\.colorScheme, .light)
|
||||
//// MapButtons(tracking: $tracking, isPresentingInfoSheet: $isPresentingInfoSheet)
|
||||
//// .environment(\.colorScheme, .dark)
|
||||
//// }
|
||||
//// .previewLayout(.fixed(width: 60, height: 100))
|
||||
//// }
|
||||
//// }
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
//
|
||||
// MapViewFitExtension.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Created by Garth Vander Houwen on 1/15/23.
|
||||
//
|
||||
|
||||
import MapKit
|
||||
|
||||
extension MKMapView {
|
||||
|
||||
func fitAllAnnotations(with padding: UIEdgeInsets = UIEdgeInsets(top: 100, left: 100, bottom: 100, right: 100)) {
|
||||
var zoomRect: MKMapRect = .null
|
||||
annotations.forEach({
|
||||
let annotationPoint = MKMapPoint($0.coordinate)
|
||||
let pointRect = MKMapRect(x: annotationPoint.x, y: annotationPoint.y, width: 0.01, height: 0.01)
|
||||
zoomRect = zoomRect.union(pointRect)
|
||||
})
|
||||
|
||||
setVisibleMapRect(zoomRect, edgePadding: padding, animated: true)
|
||||
}
|
||||
|
||||
func fit(annotations: [MKAnnotation], andShow show: Bool, with padding: UIEdgeInsets = UIEdgeInsets(top: 100, left: 100, bottom: 100, right: 100)) {
|
||||
var zoomRect: MKMapRect = .null
|
||||
annotations.forEach({
|
||||
let aPoint = MKMapPoint($0.coordinate)
|
||||
let rect = MKMapRect(x: aPoint.x, y: aPoint.y, width: 0.1, height: 0.1)
|
||||
zoomRect = zoomRect.isNull ? rect : zoomRect.union(rect)
|
||||
})
|
||||
|
||||
if show {
|
||||
addAnnotations(annotations)
|
||||
}
|
||||
|
||||
setVisibleMapRect(zoomRect, edgePadding: padding, animated: true)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,434 +0,0 @@
|
|||
////
|
||||
//// MapViewSwitUI.swift
|
||||
//// Meshtastic
|
||||
////
|
||||
//// Copyright(c) Josh Pirihi & Garth Vander Houwen 1/16/22.
|
||||
//
|
||||
//import Foundation
|
||||
//import SwiftUI
|
||||
//import MapKit
|
||||
//import OSLog
|
||||
//
|
||||
//struct PolygonInfo: Codable {
|
||||
// let stroke: String?
|
||||
// let strokeWidth, strokeOpacity: Int?
|
||||
// let fill: String?
|
||||
// let fillOpacity: Double?
|
||||
// let title, subtitle: String?
|
||||
//}
|
||||
//
|
||||
//func degreesToRadians(_ number: Double) -> Double {
|
||||
// return number * .pi / 180
|
||||
//}
|
||||
//var currentMapLayer: MapLayer?
|
||||
//
|
||||
//struct MapViewSwiftUI: UIViewRepresentable {
|
||||
// var onLongPress: (_ waypointCoordinate: CLLocationCoordinate2D) -> Void
|
||||
// var onWaypointEdit: (_ waypointId: Int ) -> Void
|
||||
// let mapView = MKMapView()
|
||||
// // Parameters
|
||||
// let selectedMapLayer: MapLayer
|
||||
// let selectedWeatherLayer: MapOverlayServer = UserDefaults.mapOverlayServer
|
||||
// let positions: [PositionEntity]
|
||||
// let waypoints: [WaypointEntity]
|
||||
// let userTrackingMode: MKUserTrackingMode
|
||||
// let showNodeHistory: Bool
|
||||
// let showRouteLines: Bool
|
||||
// let mapViewType: MKMapType = MKMapType.standard
|
||||
// // Offline Map Tiles
|
||||
// @AppStorage("lastUpdatedLocalMapFile") private var lastUpdatedLocalMapFile = 0
|
||||
// @State private var loadedLastUpdatedLocalMapFile = 0
|
||||
// var customMapOverlay: CustomMapOverlay?
|
||||
// @State private var presentCustomMapOverlayHash: CustomMapOverlay?
|
||||
// // MARK: Private methods
|
||||
// private func configureMap(mapView: MKMapView) {
|
||||
// // Map View Parameters
|
||||
// mapView.mapType = mapViewType
|
||||
// mapView.addAnnotations(waypoints)
|
||||
// // Do the initial map centering
|
||||
// let latest = positions
|
||||
// .filter { $0.latest == true }
|
||||
// .sorted { $0.nodePosition?.num ?? 0 > $1.nodePosition?.num ?? -1 }
|
||||
// let span = MKCoordinateSpan(latitudeDelta: 0.003, longitudeDelta: 0.003)
|
||||
// let center = (latest.count > 0 && userTrackingMode == MKUserTrackingMode.none) ? latest[0].coordinate : LocationHelper.currentLocation
|
||||
// let region = MKCoordinateRegion(center: center, span: span)
|
||||
// mapView.addAnnotations(showNodeHistory ? positions : latest)
|
||||
// mapView.setRegion(region, animated: true)
|
||||
// // Set user (phone gps) tracking options
|
||||
// mapView.setUserTrackingMode(userTrackingMode, animated: true)
|
||||
// if userTrackingMode == MKUserTrackingMode.none {
|
||||
// if latest.count == 1 {
|
||||
// mapView.fit(annotations: showNodeHistory ? positions: latest, andShow: false)
|
||||
// } else {
|
||||
// mapView.fitAllAnnotations()
|
||||
// }
|
||||
// mapView.showsUserLocation = false
|
||||
// } else {
|
||||
// mapView.showsUserLocation = true
|
||||
// }
|
||||
// // Other MKMapView Settings
|
||||
// mapView.preferredConfiguration.elevationStyle = .realistic// .flat
|
||||
// mapView.pointOfInterestFilter = MKPointOfInterestFilter.excludingAll
|
||||
// mapView.isPitchEnabled = true
|
||||
// mapView.isRotateEnabled = true
|
||||
// mapView.isScrollEnabled = true
|
||||
// mapView.isZoomEnabled = true
|
||||
// mapView.showsBuildings = true
|
||||
// mapView.showsScale = true
|
||||
// mapView.showsTraffic = true
|
||||
//
|
||||
// mapView.showsCompass = false
|
||||
// let compass = MKCompassButton(mapView: mapView)
|
||||
// compass.translatesAutoresizingMaskIntoConstraints = false
|
||||
// #if targetEnvironment(macCatalyst)
|
||||
// // Show the default always visible compass and the mac only controls
|
||||
// compass.compassVisibility = .visible
|
||||
// mapView.addSubview(compass)
|
||||
// mapView.showsZoomControls = true
|
||||
// mapView.showsPitchControl = true
|
||||
// compass.trailingAnchor.constraint(equalTo: mapView.trailingAnchor, constant: -115).isActive = true
|
||||
// compass.bottomAnchor.constraint(equalTo: mapView.bottomAnchor, constant: -5).isActive = true
|
||||
// #else
|
||||
// compass.compassVisibility = .adaptive
|
||||
// mapView.addSubview(compass)
|
||||
// compass.trailingAnchor.constraint(equalTo: mapView.trailingAnchor, constant: -5).isActive = true
|
||||
// compass.topAnchor.constraint(equalTo: mapView.topAnchor, constant: 145).isActive = true
|
||||
// #endif
|
||||
// }
|
||||
// private func setMapBaseLayer(mapView: MKMapView) {
|
||||
// // Avoid refreshing UI if selectedLayer has not changed
|
||||
// guard currentMapLayer != selectedMapLayer else { return }
|
||||
// currentMapLayer = selectedMapLayer
|
||||
// for overlay in mapView.overlays where overlay is MKTileOverlay {
|
||||
// mapView.removeOverlay(overlay)
|
||||
// }
|
||||
// switch selectedMapLayer {
|
||||
// case .offline:
|
||||
// mapView.mapType = .standard
|
||||
// let overlay = TileOverlay()
|
||||
// overlay.canReplaceMapContent = false
|
||||
// overlay.minimumZ = UserDefaults.mapTileServer.zoomRange.startIndex
|
||||
// overlay.maximumZ = UserDefaults.mapTileServer.zoomRange.endIndex
|
||||
// mapView.addOverlay(overlay, level: UserDefaults.mapTilesAboveLabels ? .aboveLabels : .aboveRoads)
|
||||
// case .satellite:
|
||||
// mapView.mapType = .satellite
|
||||
// case .hybrid:
|
||||
// mapView.mapType = .hybrid
|
||||
// default:
|
||||
// mapView.mapType = .standard
|
||||
// }
|
||||
// }
|
||||
// private func setMapOverlays(mapView: MKMapView) {
|
||||
// // Weather radar
|
||||
// if UserDefaults.enableOverlayServer {
|
||||
// let locale = Locale.current
|
||||
// if locale.region?.identifier ?? "no locale" == "US" {
|
||||
// let overlay = MKTileOverlay(urlTemplate: selectedWeatherLayer.tileUrl)
|
||||
// overlay.canReplaceMapContent = false
|
||||
// overlay.minimumZ = selectedWeatherLayer.zoomRange.startIndex
|
||||
// overlay.maximumZ = selectedWeatherLayer.zoomRange.endIndex
|
||||
// mapView.addOverlay(overlay, level: .aboveLabels)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// func makeUIView(context: Context) -> MKMapView {
|
||||
// currentMapLayer = nil
|
||||
// mapView.delegate = context.coordinator
|
||||
// self.configureMap(mapView: mapView)
|
||||
// return mapView
|
||||
// }
|
||||
// func updateUIView(_ mapView: MKMapView, context: Context) {
|
||||
// // Set selected map base layer
|
||||
// setMapBaseLayer(mapView: mapView)
|
||||
// // Set map tile server and weather overlay layers
|
||||
// setMapOverlays(mapView: mapView)
|
||||
// let latest = positions
|
||||
// .filter { $0.latest == true }
|
||||
// .sorted { $0.nodePosition?.num ?? 0 > $1.nodePosition?.num ?? -1 }
|
||||
// // Node Route Lines
|
||||
// if showRouteLines {
|
||||
// // Remove all existing PolyLine Overlays
|
||||
// for overlay in mapView.overlays where overlay is MKPolyline {
|
||||
// mapView.removeOverlay(overlay)
|
||||
// }
|
||||
// var lineIndex = 0
|
||||
// for position in latest {
|
||||
// let nodePositions = positions.filter { $0.nodeCoordinate != nil && $0.nodePosition?.num ?? 0 == position.nodePosition?.num ?? -1 }
|
||||
// let lineCoords = nodePositions.compactMap({(position) -> CLLocationCoordinate2D in
|
||||
// return position.nodeCoordinate ?? LocationHelper.DefaultLocation
|
||||
// })
|
||||
// let polyline = MKPolyline(coordinates: lineCoords, count: nodePositions.count)
|
||||
// polyline.title = "\(String(position.nodePosition?.num ?? 0))"
|
||||
// mapView.addOverlay(polyline, level: .aboveLabels)
|
||||
// lineIndex += 1
|
||||
// // There are 18 colors for lines, start over if we are at index 17
|
||||
// if lineIndex > 17 {
|
||||
// lineIndex = 0
|
||||
// }
|
||||
// }
|
||||
// } else {
|
||||
// // Remove all existing PolyLine Overlays
|
||||
// for overlay in mapView.overlays where overlay is MKPolyline {
|
||||
// mapView.removeOverlay(overlay)
|
||||
// }
|
||||
// }
|
||||
// let annotationCount = waypoints.count + (showNodeHistory ? positions.count : latest.count)
|
||||
// if annotationCount != mapView.annotations.count {
|
||||
// Logger.services.info("Annotation Count: \(annotationCount) Map Annotations: \(mapView.annotations.count)")
|
||||
// mapView.removeAnnotations(mapView.annotations)
|
||||
// mapView.addAnnotations(waypoints)
|
||||
// }
|
||||
// mapView.addAnnotations(showNodeHistory ? positions : latest)
|
||||
// if userTrackingMode == MKUserTrackingMode.none {
|
||||
// mapView.showsUserLocation = false
|
||||
// if UserDefaults.enableMapRecentering {
|
||||
// if latest.count == 1 {
|
||||
// mapView.fit(annotations: showNodeHistory ? positions : latest, andShow: true)
|
||||
// } else {
|
||||
// mapView.fitAllAnnotations()
|
||||
// }
|
||||
// }
|
||||
// } else {
|
||||
// mapView.showsUserLocation = true
|
||||
// }
|
||||
// mapView.setUserTrackingMode(userTrackingMode, animated: true)
|
||||
// }
|
||||
// func makeCoordinator() -> MapCoordinator {
|
||||
// return Coordinator(self)
|
||||
// }
|
||||
// final class MapCoordinator: NSObject, MKMapViewDelegate, UIGestureRecognizerDelegate {
|
||||
// var parent: MapViewSwiftUI
|
||||
// var longPressRecognizer = UILongPressGestureRecognizer()
|
||||
// init(_ parent: MapViewSwiftUI) {
|
||||
// self.parent = parent
|
||||
// super.init()
|
||||
// self.longPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(longPressHandler))
|
||||
// self.longPressRecognizer.minimumPressDuration = 0.5
|
||||
// self.longPressRecognizer.cancelsTouchesInView = true
|
||||
// self.longPressRecognizer.delegate = self
|
||||
// self.parent.mapView.addGestureRecognizer(longPressRecognizer)
|
||||
// }
|
||||
// func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
|
||||
// switch annotation {
|
||||
// case let positionAnnotation as PositionEntity:
|
||||
// let reuseID = String(positionAnnotation.nodePosition?.num ?? 0) + "-" + String(positionAnnotation.time?.timeIntervalSince1970 ?? 0)
|
||||
// let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "node") as? MKMarkerAnnotationView ?? MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: reuseID )
|
||||
// annotationView.tag = -1
|
||||
// annotationView.canShowCallout = true
|
||||
// if positionAnnotation.latest {
|
||||
// annotationView.markerTintColor = UIColor(hex: UInt32(positionAnnotation.nodePosition?.num ?? 0)).darker()
|
||||
// annotationView.displayPriority = .required
|
||||
// annotationView.titleVisibility = .visible
|
||||
// } else {
|
||||
// annotationView.markerTintColor = UIColor(hex: UInt32(positionAnnotation.nodePosition?.num ?? 0)).lighter()
|
||||
// annotationView.displayPriority = .defaultHigh
|
||||
// annotationView.titleVisibility = .adaptive
|
||||
// }
|
||||
// annotationView.tag = -1
|
||||
// annotationView.canShowCallout = true
|
||||
// annotationView.titleVisibility = .adaptive
|
||||
// let leftIcon = UIImageView(image: annotationView.glyphText?.image())
|
||||
// leftIcon.backgroundColor = UIColor(.indigo)
|
||||
// annotationView.leftCalloutAccessoryView = leftIcon
|
||||
// let subtitle = UILabel()
|
||||
// subtitle.text = "Long Name: \(positionAnnotation.nodePosition?.user?.longName ?? "Unknown") \n"
|
||||
// subtitle.text? += "Latitude: \(String(format: "%.5f", positionAnnotation.coordinate.latitude)) \n"
|
||||
// subtitle.text! += "Longitude: \(String(format: "%.5f", positionAnnotation.coordinate.longitude)) \n"
|
||||
// let distanceFormatter = MKDistanceFormatter()
|
||||
// subtitle.text! += "Altitude: \(distanceFormatter.string(fromDistance: Double(positionAnnotation.altitude))) \n"
|
||||
// if positionAnnotation.nodePosition?.metadata != nil {
|
||||
// if DeviceRoles(rawValue: Int(positionAnnotation.nodePosition!.metadata?.role ?? 0)) == DeviceRoles.client ||
|
||||
// DeviceRoles(rawValue: Int(positionAnnotation.nodePosition!.metadata?.role ?? 0)) == DeviceRoles.clientMute ||
|
||||
// DeviceRoles(rawValue: Int(positionAnnotation.nodePosition!.metadata?.role ?? 0)) == DeviceRoles.routerClient {
|
||||
// annotationView.glyphImage = UIImage(systemName: "flipphone")
|
||||
// } else if DeviceRoles(rawValue: Int(positionAnnotation.nodePosition!.metadata?.role ?? 0)) == DeviceRoles.repeater {
|
||||
// annotationView.glyphImage = UIImage(systemName: "repeat")
|
||||
// } else if DeviceRoles(rawValue: Int(positionAnnotation.nodePosition!.metadata?.role ?? 0)) == DeviceRoles.router {
|
||||
// annotationView.glyphImage = UIImage(systemName: "wifi.router.fill")
|
||||
// } else if DeviceRoles(rawValue: Int(positionAnnotation.nodePosition!.metadata?.role ?? 0)) == DeviceRoles.tracker {
|
||||
// annotationView.glyphImage = UIImage(systemName: "location.viewfinder")
|
||||
// } else if DeviceRoles(rawValue: Int(positionAnnotation.nodePosition!.metadata?.role ?? 0)) == DeviceRoles.sensor {
|
||||
// annotationView.glyphImage = UIImage(systemName: "sensor")
|
||||
// }
|
||||
// let pf = PositionFlags(rawValue: Int(positionAnnotation.nodePosition?.metadata?.positionFlags ?? 3))
|
||||
// if pf.contains(.Satsinview) {
|
||||
// subtitle.text! += "Sats in view: \(String(positionAnnotation.satsInView)) \n"
|
||||
// }
|
||||
// if pf.contains(.SeqNo) {
|
||||
// subtitle.text! += "Sequence: \(String(positionAnnotation.seqNo)) \n"
|
||||
// }
|
||||
// if pf.contains(.Heading) {
|
||||
// if parent.userTrackingMode != MKUserTrackingMode.followWithHeading {
|
||||
// annotationView.glyphImage = UIImage(systemName: "location.north.fill")?.rotate(radians: Float(degreesToRadians(Double(positionAnnotation.heading))))
|
||||
// subtitle.text! += "Heading: \(String(positionAnnotation.heading)) \n"
|
||||
// } else {
|
||||
// annotationView.glyphImage = UIImage(systemName: "flipphone")
|
||||
// }
|
||||
// }
|
||||
// if pf.contains(.Speed) {
|
||||
// let formatter = MeasurementFormatter()
|
||||
// formatter.locale = Locale.current
|
||||
// if positionAnnotation.speed <= 1 {
|
||||
// annotationView.glyphImage = UIImage(systemName: "hexagon")
|
||||
// }
|
||||
// subtitle.text! += "Speed: \(formatter.string(from: Measurement(value: Double(positionAnnotation.speed), unit: UnitSpeed.kilometersPerHour))) \n"
|
||||
// }
|
||||
// } else {
|
||||
// // node metadata is nil
|
||||
// annotationView.glyphImage = UIImage(systemName: "flipphone")
|
||||
// }
|
||||
// if LocationHelper.currentLocation.distance(from: LocationHelper.DefaultLocation) > 0.0 {
|
||||
// let metersAway = positionAnnotation.coordinate.distance(from: LocationHelper.currentLocation)
|
||||
// subtitle.text! += "distance".localized + ": \(distanceFormatter.string(fromDistance: Double(metersAway))) \n"
|
||||
// }
|
||||
// subtitle.text! += positionAnnotation.time?.formatted() ?? "Unknown \n"
|
||||
// subtitle.numberOfLines = 0
|
||||
// annotationView.detailCalloutAccessoryView = subtitle
|
||||
// let detailsIcon = UIButton(type: .detailDisclosure)
|
||||
// detailsIcon.setImage(UIImage(systemName: "trash"), for: .normal)
|
||||
// annotationView.rightCalloutAccessoryView = detailsIcon
|
||||
// return annotationView
|
||||
// case let waypointAnnotation as WaypointEntity:
|
||||
// let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "waypoint") as? MKMarkerAnnotationView ?? MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: String(waypointAnnotation.id))
|
||||
// annotationView.tag = Int(waypointAnnotation.id)
|
||||
// annotationView.isEnabled = true
|
||||
// annotationView.canShowCallout = true
|
||||
// if waypointAnnotation.icon == 0 {
|
||||
// annotationView.glyphText = "📍"
|
||||
// } else {
|
||||
// annotationView.glyphText = String(UnicodeScalar(Int(waypointAnnotation.icon)) ?? "📍")
|
||||
// }
|
||||
// annotationView.markerTintColor = UIColor(.accentColor)
|
||||
// annotationView.displayPriority = .required
|
||||
// annotationView.titleVisibility = .adaptive
|
||||
// let leftIcon = UIImageView(image: annotationView.glyphText?.image())
|
||||
// leftIcon.backgroundColor = UIColor(.accentColor)
|
||||
// annotationView.leftCalloutAccessoryView = leftIcon
|
||||
// let subtitle = UILabel()
|
||||
// if waypointAnnotation.longDescription?.count ?? 0 > 0 {
|
||||
// subtitle.text = (waypointAnnotation.longDescription ?? "") + "\n"
|
||||
// } else {
|
||||
// subtitle.text = ""
|
||||
// }
|
||||
// if LocationHelper.currentLocation.distance(from: LocationHelper.DefaultLocation) > 0.0 {
|
||||
// let metersAway = waypointAnnotation.coordinate.distance(from: LocationHelper.currentLocation)
|
||||
// let distanceFormatter = MKDistanceFormatter()
|
||||
// subtitle.text! += "distance".localized + ": \(distanceFormatter.string(fromDistance: Double(metersAway))) \n"
|
||||
// }
|
||||
// if waypointAnnotation.created != nil {
|
||||
// subtitle.text! += "Created: \(waypointAnnotation.created?.formatted() ?? "Unknown") \n"
|
||||
// }
|
||||
// if waypointAnnotation.lastUpdated != nil {
|
||||
// subtitle.text! += "Updated: \(waypointAnnotation.lastUpdated?.formatted() ?? "Unknown") \n"
|
||||
// }
|
||||
// if waypointAnnotation.expire != nil {
|
||||
// subtitle.text! += "Expires: \(waypointAnnotation.expire?.formatted() ?? "Unknown") \n"
|
||||
// }
|
||||
// subtitle.numberOfLines = 0
|
||||
// annotationView.detailCalloutAccessoryView = subtitle
|
||||
// let editIcon = UIButton(type: .detailDisclosure)
|
||||
// editIcon.setImage(UIImage(systemName: "square.and.pencil"), for: .normal)
|
||||
// annotationView.rightCalloutAccessoryView = editIcon
|
||||
// return annotationView
|
||||
// default: return nil
|
||||
// }
|
||||
// }
|
||||
// func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
|
||||
// switch view.annotation {
|
||||
// case _ as WaypointEntity:
|
||||
// // Only Allow Edit for waypoint annotations with a id
|
||||
// if view.tag > 0 {
|
||||
// parent.onWaypointEdit(view.tag)
|
||||
// }
|
||||
// default: break
|
||||
// }
|
||||
// }
|
||||
// @objc func longPressHandler(_ gesture: UILongPressGestureRecognizer) {
|
||||
// if gesture.state != UIGestureRecognizer.State.ended {
|
||||
// return
|
||||
// } else if gesture.state != UIGestureRecognizer.State.began {
|
||||
// // Screen Position - CGPoint
|
||||
// let location = longPressRecognizer.location(in: self.parent.mapView)
|
||||
// // Map Coordinate - CLLocationCoordinate2D
|
||||
// let coordinate = self.parent.mapView.convert(location, toCoordinateFrom: self.parent.mapView)
|
||||
// let annotation = MKPointAnnotation()
|
||||
// annotation.title = "Dropped Pin"
|
||||
// annotation.coordinate = coordinate
|
||||
// parent.mapView.addAnnotation(annotation)
|
||||
// UINotificationFeedbackGenerator().notificationOccurred(.success)
|
||||
// parent.onLongPress(coordinate)
|
||||
// }
|
||||
// }
|
||||
// public func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
|
||||
// if let tileOverlay = overlay as? MKTileOverlay {
|
||||
// return MKTileOverlayRenderer(tileOverlay: tileOverlay)
|
||||
// } else {
|
||||
// if let routePolyline = overlay as? MKPolyline {
|
||||
// let titleString = routePolyline.title ?? "0"
|
||||
// let renderer = MKPolylineRenderer(polyline: routePolyline)
|
||||
// renderer.strokeColor = UIColor(hex: UInt32(titleString) ?? 0).lighter()
|
||||
// renderer.lineWidth = 8
|
||||
// return renderer
|
||||
// }
|
||||
// if let polygon = overlay as? MKPolygon {
|
||||
// let renderer = MKPolygonRenderer(polygon: polygon)
|
||||
// renderer.fillColor = UIColor.purple.withAlphaComponent(0.2)
|
||||
// renderer.strokeColor = .purple.withAlphaComponent(0.7)
|
||||
// return renderer
|
||||
// }
|
||||
// return MKOverlayRenderer(overlay: overlay)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// /// is supposed to be located in the folder with the map name
|
||||
// public struct DefaultTile: Hashable {
|
||||
// let tileName: String
|
||||
// let tileType: String
|
||||
// public init(tileName: String, tileType: String) {
|
||||
// self.tileName = tileName
|
||||
// self.tileType = tileType
|
||||
// }
|
||||
// }
|
||||
// public struct CustomMapOverlay: Equatable, Hashable {
|
||||
// let mapName: String
|
||||
// let tileType: String
|
||||
// var canReplaceMapContent: Bool
|
||||
// var minimumZoomLevel: Int?
|
||||
// var maximumZoomLevel: Int?
|
||||
// let defaultTile: DefaultTile?
|
||||
// public init(
|
||||
// mapName: String,
|
||||
// tileType: String,
|
||||
// canReplaceMapContent: Bool = true, // false for transparent tiles
|
||||
// minimumZoomLevel: Int? = nil,
|
||||
// maximumZoomLevel: Int? = nil,
|
||||
// defaultTile: DefaultTile? = nil
|
||||
// ) {
|
||||
// self.mapName = mapName
|
||||
// self.tileType = tileType
|
||||
// self.canReplaceMapContent = canReplaceMapContent
|
||||
// self.minimumZoomLevel = minimumZoomLevel
|
||||
// self.maximumZoomLevel = maximumZoomLevel
|
||||
// self.defaultTile = defaultTile
|
||||
// }
|
||||
// public init?(
|
||||
// mapName: String?,
|
||||
// tileType: String,
|
||||
// canReplaceMapContent: Bool = true, // false for transparent tiles
|
||||
// minimumZoomLevel: Int? = nil,
|
||||
// maximumZoomLevel: Int? = nil,
|
||||
// defaultTile: DefaultTile? = nil
|
||||
// ) {
|
||||
// if mapName == nil || mapName! == "" {
|
||||
// return nil
|
||||
// }
|
||||
// self.mapName = mapName!
|
||||
// self.tileType = tileType
|
||||
// self.canReplaceMapContent = canReplaceMapContent
|
||||
// self.minimumZoomLevel = minimumZoomLevel
|
||||
// self.maximumZoomLevel = maximumZoomLevel
|
||||
// self.defaultTile = defaultTile
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
import SwiftUI
|
||||
|
||||
struct TileDownloadStatus: View {
|
||||
@ObservedObject var tileManager = OfflineTileManager.shared
|
||||
|
||||
var body: some View {
|
||||
if tileManager.status == .downloading {
|
||||
Image(systemName: "arrow.down.circle.fill")
|
||||
.foregroundColor(.gray)
|
||||
} else {
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,164 +0,0 @@
|
|||
////
|
||||
//// NodeMapControl.swift
|
||||
//// Meshtastic
|
||||
////
|
||||
//// Created by Garth Vander Houwen on 9/9/23.
|
||||
////
|
||||
//import SwiftUI
|
||||
//import CoreLocation
|
||||
//import MapKit
|
||||
//import WeatherKit
|
||||
//import OSLog
|
||||
//
|
||||
//struct NodeMapMapkit: View {
|
||||
//
|
||||
// @Environment(\.managedObjectContext) var context
|
||||
// @EnvironmentObject var bleManager: BLEManager
|
||||
// /// Weather
|
||||
// /// The current weather condition for the city.
|
||||
// @State private var condition: WeatherCondition?
|
||||
// @State private var temperature: Measurement<UnitTemperature>?
|
||||
// @State private var humidity: Int?
|
||||
// @State private var symbolName: String = "cloud.fill"
|
||||
// @State private var attributionLink: URL?
|
||||
// @State private var attributionLogo: URL?
|
||||
//
|
||||
// @Environment(\.colorScheme) var colorScheme: ColorScheme
|
||||
// @AppStorage("meshMapType") private var meshMapType = 0
|
||||
// @AppStorage("meshMapShowNodeHistory") private var meshMapShowNodeHistory = false
|
||||
// @AppStorage("meshMapShowRouteLines") private var meshMapShowRouteLines = false
|
||||
// @State private var selectedMapLayer: MapLayer = .standard
|
||||
// @State var waypointCoordinate: WaypointCoordinate?
|
||||
// @State var editingWaypoint: Int = 0
|
||||
// @State private var customMapOverlay: MapViewSwiftUI.CustomMapOverlay? = MapViewSwiftUI.CustomMapOverlay(
|
||||
// mapName: "offlinemap",
|
||||
// tileType: "png",
|
||||
// canReplaceMapContent: true
|
||||
// )
|
||||
// @FetchRequest(sortDescriptors: [NSSortDescriptor(key: "name", ascending: false)],
|
||||
// predicate: NSPredicate(
|
||||
// format: "expire == nil || expire >= %@", Date() as NSDate
|
||||
// ), animation: .none)
|
||||
// private var waypoints: FetchedResults<WaypointEntity>
|
||||
// @ObservedObject var node: NodeInfoEntity
|
||||
//
|
||||
// var body: some View {
|
||||
//
|
||||
// NavigationStack {
|
||||
// GeometryReader { bounds in
|
||||
// VStack {
|
||||
// if node.hasPositions {
|
||||
// ZStack {
|
||||
// let positionArray = node.positions?.array as? [PositionEntity] ?? []
|
||||
// let lastTenThousand = Array(positionArray.prefix(10000))
|
||||
// // let todaysPositions = positionArray.filter { $0.time! >= Calendar.current.startOfDay(for: Date()) }
|
||||
// ZStack {
|
||||
// MapViewSwiftUI(onLongPress: { coord in
|
||||
// waypointCoordinate = WaypointCoordinate(id: .init(), coordinate: coord, waypointId: 0)
|
||||
// }, onWaypointEdit: { wpId in
|
||||
// if wpId > 0 {
|
||||
// waypointCoordinate = WaypointCoordinate(id: .init(), coordinate: nil, waypointId: Int64(wpId))
|
||||
// }
|
||||
// },
|
||||
// selectedMapLayer: selectedMapLayer,
|
||||
// positions: lastTenThousand,
|
||||
// waypoints: Array(waypoints),
|
||||
// userTrackingMode: MKUserTrackingMode.none,
|
||||
// showNodeHistory: meshMapShowNodeHistory,
|
||||
// showRouteLines: meshMapShowRouteLines,
|
||||
// customMapOverlay: self.customMapOverlay
|
||||
// )
|
||||
// VStack(alignment: .leading) {
|
||||
// Spacer()
|
||||
// HStack(alignment: .bottom, spacing: 1) {
|
||||
// Picker("Map Type", selection: $selectedMapLayer) {
|
||||
// ForEach(MapLayer.allCases, id: \.self) { layer in
|
||||
// if layer == MapLayer.offline && UserDefaults.enableOfflineMaps {
|
||||
// Text(layer.localized)
|
||||
// } else if layer != MapLayer.offline {
|
||||
// Text(layer.localized)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// .onChange(of: (selectedMapLayer)) { newMapLayer in
|
||||
// UserDefaults.mapLayer = newMapLayer
|
||||
// }
|
||||
// .background(.thinMaterial, in: RoundedRectangle(cornerRadius: 12, style: .continuous))
|
||||
// .pickerStyle(.menu)
|
||||
// .padding(5)
|
||||
// VStack {
|
||||
// VStack {
|
||||
// Label(temperature?.formatted(.measurement(width: .narrow)) ?? "??", systemImage: symbolName)
|
||||
// .font(.caption)
|
||||
//
|
||||
// Label("\(humidity ?? 0)%", systemImage: "humidity")
|
||||
// .font(.caption2)
|
||||
//
|
||||
// AsyncImage(url: attributionLogo) { image in
|
||||
// image
|
||||
// .resizable()
|
||||
// .scaledToFit()
|
||||
// } placeholder: {
|
||||
// ProgressView()
|
||||
// .controlSize(.mini)
|
||||
// }
|
||||
// .frame(height: 10)
|
||||
//
|
||||
// Link("Other data sources", destination: attributionLink ?? URL(string: "https://weather-data.apple.com/legal-attribution.html")!)
|
||||
// .font(.caption2)
|
||||
// }
|
||||
// .padding(5)
|
||||
//
|
||||
// }
|
||||
// .background(.thinMaterial, in: RoundedRectangle(cornerRadius: 12, style: .continuous))
|
||||
// .padding(5)
|
||||
// .task {
|
||||
// do {
|
||||
// if node.hasPositions {
|
||||
// let mostRecent = node.positions?.lastObject as? PositionEntity
|
||||
// let weather = try await WeatherService.shared.weather(for: mostRecent?.nodeLocation ?? CLLocation(latitude: LocationHelper.currentLocation.latitude, longitude: LocationHelper.currentLocation.longitude))
|
||||
// condition = weather.currentWeather.condition
|
||||
// temperature = weather.currentWeather.temperature
|
||||
// humidity = Int(weather.currentWeather.humidity * 100)
|
||||
// symbolName = weather.currentWeather.symbolName
|
||||
// let attribution = try await WeatherService.shared.attribution
|
||||
// attributionLink = attribution.legalPageURL
|
||||
// attributionLogo = colorScheme == .light ? attribution.combinedMarkLightURL : attribution.combinedMarkDarkURL
|
||||
// }
|
||||
// } catch {
|
||||
// Logger.services.error("Could not gather weather information: \(error.localizedDescription)")
|
||||
// condition = .clear
|
||||
// symbolName = "cloud.fill"
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// .ignoresSafeArea(.all, edges: [.top, .leading, .trailing])
|
||||
// .frame(idealWidth: bounds.size.width, minHeight: bounds.size.height / 1.65)
|
||||
// }
|
||||
// } else {
|
||||
// HStack {
|
||||
// }
|
||||
// .padding([.top], 20)
|
||||
// }
|
||||
// }
|
||||
// .edgesIgnoringSafeArea([.leading, .trailing])
|
||||
// .sheet(item: $waypointCoordinate, content: { wpc in
|
||||
// WaypointFormMapKit(coordinate: wpc)
|
||||
// .presentationDetents([.medium, .large])
|
||||
// .presentationDragIndicator(.automatic)
|
||||
// })
|
||||
// .navigationBarTitle(String(node.user?.longName ?? "unknown".localized), displayMode: .inline)
|
||||
// .navigationBarItems(trailing:
|
||||
// ZStack {
|
||||
// ConnectedDevice(
|
||||
// bluetoothOn: bleManager.isSwitchedOn,
|
||||
// deviceConnected: bleManager.connectedPeripheral != nil,
|
||||
// name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
|
||||
// })
|
||||
// }
|
||||
// .padding(.bottom, 2)
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
|
@ -1,266 +0,0 @@
|
|||
////
|
||||
//// WaypointFormView.swift
|
||||
//// Meshtastic
|
||||
////
|
||||
//// Copyright Garth Vander Houwen 1/10/23.
|
||||
////
|
||||
//
|
||||
//import CoreLocation
|
||||
//import MeshtasticProtobufs
|
||||
//import OSLog
|
||||
//import SwiftUI
|
||||
//
|
||||
//struct WaypointFormMapKit: View {
|
||||
//
|
||||
// @EnvironmentObject var bleManager: BLEManager
|
||||
// @Environment(\.dismiss) private var dismiss
|
||||
// @State var coordinate: WaypointCoordinate
|
||||
// @FocusState private var iconIsFocused: Bool
|
||||
// @State private var name: String = ""
|
||||
// @State private var description: String = ""
|
||||
// @State private var icon: String = "📍"
|
||||
// @State private var latitude: Double = 0
|
||||
// @State private var longitude: Double = 0
|
||||
// @State private var expires: Bool = false
|
||||
// @State private var expire: Date = Date.now.addingTimeInterval(60 * 480) // 1 minute * 480 = 8 Hours
|
||||
// @State private var locked: Bool = false
|
||||
// @State private var lockedTo: Int64 = 0
|
||||
//
|
||||
// var body: some View {
|
||||
//
|
||||
// Form {
|
||||
// let distance = CLLocation(latitude: LocationHelper.currentLocation.latitude, longitude: LocationHelper.currentLocation.longitude).distance(from: CLLocation(latitude: coordinate.coordinate?.latitude ?? 0, longitude: coordinate.coordinate?.longitude ?? 0))
|
||||
// Section(header: Text((coordinate.waypointId > 0) ? "Editing Waypoint" : "Create Waypoint")) {
|
||||
// HStack {
|
||||
// Text("Location: \(String(format: "%.5f", latitude) + "," + String(format: "%.5f", longitude))")
|
||||
// .textSelection(.enabled)
|
||||
// .foregroundColor(Color.gray)
|
||||
// .font(.caption2)
|
||||
// if coordinate.coordinate?.latitude ?? 0 != 0 && coordinate.coordinate?.longitude ?? 0 != 0 {
|
||||
// DistanceText(meters: distance)
|
||||
// .foregroundColor(Color.gray)
|
||||
// .font(.caption2)
|
||||
// }
|
||||
// }
|
||||
// HStack {
|
||||
// Text("Name")
|
||||
// Spacer()
|
||||
// TextField(
|
||||
// "Name",
|
||||
// text: $name,
|
||||
// axis: .vertical
|
||||
// )
|
||||
// .foregroundColor(Color.gray)
|
||||
// .onChange(of: name) {
|
||||
// var totalBytes = name.utf8.count
|
||||
// // Only mess with the value if it is too big
|
||||
// while totalBytes > 30 {
|
||||
// name = String(name.dropLast())
|
||||
// totalBytes = name.utf8.count
|
||||
// }
|
||||
// if totalBytes > 30 {
|
||||
// name = String(name.dropLast())
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// HStack {
|
||||
// Text("Description")
|
||||
// Spacer()
|
||||
// TextField(
|
||||
// "Description",
|
||||
// text: $description,
|
||||
// axis: .vertical
|
||||
// )
|
||||
// .foregroundColor(Color.gray)
|
||||
// .onChange(of: description) {
|
||||
// var totalBytes = description.utf8.count
|
||||
// // Only mess with the value if it is too big
|
||||
// while totalBytes > 100 {
|
||||
// description = String(description.dropLast())
|
||||
// totalBytes = description.utf8.count
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// HStack {
|
||||
// Text("Icon")
|
||||
// Spacer()
|
||||
// EmojiOnlyTextField(text: $icon, placeholder: "Select an emoji")
|
||||
// .font(.title)
|
||||
// .focused($iconIsFocused)
|
||||
// .onChange(of: icon) { _, value in
|
||||
//
|
||||
// // If you have anything other than emojis in your string make it empty
|
||||
// if !value.onlyEmojis() {
|
||||
// icon = ""
|
||||
// }
|
||||
// // If a second emoji is entered delete the first one
|
||||
// if value.count >= 1 {
|
||||
//
|
||||
// if value.count > 1 {
|
||||
// let index = value.index(value.startIndex, offsetBy: 1)
|
||||
// icon = String(value[index])
|
||||
// }
|
||||
// iconIsFocused = false
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// }
|
||||
// Toggle(isOn: $expires) {
|
||||
// Label("Expires", systemImage: "clock.badge.xmark")
|
||||
// }
|
||||
// .toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
// if expires {
|
||||
// DatePicker("Expire", selection: $expire, in: Date.now...)
|
||||
// .datePickerStyle(.compact)
|
||||
// .font(.callout)
|
||||
// }
|
||||
// Toggle(isOn: $locked) {
|
||||
// Label("Locked", systemImage: "lock")
|
||||
// }
|
||||
// .toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
// }
|
||||
// }
|
||||
// HStack {
|
||||
// Button {
|
||||
//
|
||||
// var newWaypoint = Waypoint()
|
||||
// // Loading a waypoint from edit
|
||||
// if coordinate.waypointId > 0 {
|
||||
// newWaypoint.id = UInt32(coordinate.waypointId)
|
||||
// let waypoint = getWaypoint(id: Int64(coordinate.waypointId), context: bleManager.context)
|
||||
// newWaypoint.latitudeI = waypoint.latitudeI
|
||||
// newWaypoint.longitudeI = waypoint.longitudeI
|
||||
// } else {
|
||||
// // New waypoint
|
||||
// newWaypoint.id = UInt32.random(in: UInt32(UInt8.max)..<UInt32.max)
|
||||
// newWaypoint.latitudeI = Int32(Double(coordinate.coordinate?.latitude ?? 0) * 1e7)
|
||||
// newWaypoint.longitudeI = Int32(Double(coordinate.coordinate?.longitude ?? 0) * 1e7)
|
||||
// }
|
||||
// newWaypoint.name = name.count > 0 ? name : "Dropped Pin"
|
||||
// newWaypoint.description_p = description
|
||||
// // Unicode scalar value for the icon emoji string
|
||||
// let unicodeScalers = icon.unicodeScalars
|
||||
// // First element as an UInt32
|
||||
// let unicode = unicodeScalers[unicodeScalers.startIndex].value
|
||||
// newWaypoint.icon = unicode
|
||||
// if locked {
|
||||
// if lockedTo == 0 {
|
||||
// newWaypoint.lockedTo = UInt32(bleManager.connectedPeripheral!.num)
|
||||
// } else {
|
||||
// newWaypoint.lockedTo = UInt32(lockedTo)
|
||||
// }
|
||||
// }
|
||||
// if expires {
|
||||
// newWaypoint.expire = UInt32(expire.timeIntervalSince1970)
|
||||
// } else {
|
||||
// newWaypoint.expire = 0
|
||||
// }
|
||||
// if bleManager.sendWaypoint(waypoint: newWaypoint) {
|
||||
// dismiss()
|
||||
// } else {
|
||||
// dismiss()
|
||||
// Logger.mesh.error("Send waypoint failed")
|
||||
// }
|
||||
// } label: {
|
||||
// Label("Send", systemImage: "arrow.up")
|
||||
// }
|
||||
// .buttonStyle(.bordered)
|
||||
// .buttonBorderShape(.capsule)
|
||||
// .controlSize(.regular)
|
||||
// .disabled(bleManager.connectedPeripheral == nil)
|
||||
// .padding(.bottom)
|
||||
//
|
||||
// Button(role: .cancel) {
|
||||
// dismiss()
|
||||
// } label: {
|
||||
// Label("cancel", systemImage: "x.circle")
|
||||
// }
|
||||
// .buttonStyle(.bordered)
|
||||
// .buttonBorderShape(.capsule)
|
||||
// .controlSize(.regular)
|
||||
// .padding(.bottom)
|
||||
//
|
||||
// if coordinate.waypointId > 0 {
|
||||
//
|
||||
// Menu {
|
||||
// Button("For me", action: {
|
||||
// let waypoint = getWaypoint(id: Int64(coordinate.waypointId), context: bleManager.context)
|
||||
// bleManager.context.delete(waypoint)
|
||||
// do {
|
||||
// try bleManager.context.save()
|
||||
// } catch {
|
||||
// bleManager.context.rollback()
|
||||
// }
|
||||
// dismiss() })
|
||||
// Button("For everyone", action: {
|
||||
// var newWaypoint = Waypoint()
|
||||
//
|
||||
// if coordinate.waypointId > 0 {
|
||||
// newWaypoint.id = UInt32(coordinate.waypointId)
|
||||
// }
|
||||
// newWaypoint.name = name.count > 0 ? name : "Dropped Pin"
|
||||
// newWaypoint.description_p = description
|
||||
// newWaypoint.latitudeI = Int32(coordinate.coordinate?.latitude ?? 0 * 1e7)
|
||||
// newWaypoint.longitudeI = Int32(coordinate.coordinate?.longitude ?? 0 * 1e7)
|
||||
// // Unicode scalar value for the icon emoji string
|
||||
// let unicodeScalers = icon.unicodeScalars
|
||||
// // First element as an UInt32
|
||||
// let unicode = unicodeScalers[unicodeScalers.startIndex].value
|
||||
// newWaypoint.icon = unicode
|
||||
// if locked {
|
||||
// if lockedTo == 0 {
|
||||
// newWaypoint.lockedTo = UInt32(bleManager.connectedPeripheral!.num)
|
||||
// } else {
|
||||
// newWaypoint.lockedTo = UInt32(lockedTo)
|
||||
// }
|
||||
// }
|
||||
// newWaypoint.expire = 1
|
||||
// if bleManager.sendWaypoint(waypoint: newWaypoint) {
|
||||
// dismiss()
|
||||
// } else {
|
||||
// dismiss()
|
||||
// Logger.mesh.error("Send waypoint failed")
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
// label: {
|
||||
// Label("delete", systemImage: "trash")
|
||||
// .foregroundColor(.red)
|
||||
// }
|
||||
// .buttonStyle(.bordered)
|
||||
// .buttonBorderShape(.capsule)
|
||||
// .controlSize(.regular)
|
||||
// .padding(.bottom)
|
||||
// }
|
||||
// }
|
||||
// .onAppear {
|
||||
// if coordinate.waypointId > 0 {
|
||||
// let waypoint = getWaypoint(id: Int64(coordinate.waypointId), context: bleManager.context)
|
||||
// name = waypoint.name ?? "Dropped Pin"
|
||||
// description = waypoint.longDescription ?? ""
|
||||
// icon = String(UnicodeScalar(Int(waypoint.icon)) ?? "📍")
|
||||
// latitude = Double(waypoint.latitudeI) / 1e7
|
||||
// longitude = Double(waypoint.longitudeI) / 1e7
|
||||
// if waypoint.expire != nil {
|
||||
// expires = true
|
||||
// expire = waypoint.expire ?? Date()
|
||||
// } else {
|
||||
// expires = false
|
||||
// }
|
||||
// if waypoint.locked > 0 {
|
||||
// locked = true
|
||||
// lockedTo = waypoint.locked
|
||||
// }
|
||||
// } else {
|
||||
// name = ""
|
||||
// description = ""
|
||||
// locked = false
|
||||
// expires = false
|
||||
// expire = Date.now.addingTimeInterval(60 * 480)
|
||||
// icon = "📍"
|
||||
// latitude = coordinate.coordinate?.latitude ?? 0
|
||||
// longitude = coordinate.coordinate?.longitude ?? 0
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
|
@ -116,11 +116,11 @@ struct ChannelMessageList: View {
|
|||
message.read = true
|
||||
do {
|
||||
try context.save()
|
||||
Logger.data.info("📖 [App] Read message \(message.messageId) ")
|
||||
Logger.data.info("📖 [App] Read message \(message.messageId, privacy: .public) ")
|
||||
appState.unreadChannelMessages = myInfo.unreadMessages
|
||||
context.refresh(myInfo, mergeChanges: true)
|
||||
} catch {
|
||||
Logger.data.error("Failed to read message \(message.messageId): \(error.localizedDescription)")
|
||||
Logger.data.error("Failed to read message \(message.messageId, privacy: .public): \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ struct MessageText: View {
|
|||
do {
|
||||
try context.save()
|
||||
} catch {
|
||||
Logger.data.error("Failed to delete message \(message.messageId): \(error.localizedDescription)")
|
||||
Logger.data.error("Failed to delete message \(message.messageId, privacy: .public): \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
Button("Cancel", role: .cancel) {}
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ struct RetryButton: View {
|
|||
do {
|
||||
try context.save()
|
||||
} catch {
|
||||
Logger.data.error("Failed to delete message \(messageID): \(error.localizedDescription)")
|
||||
Logger.data.error("Failed to delete message \(messageID, privacy: .public): \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
if !bleManager.sendMessage(
|
||||
message: payload,
|
||||
|
|
@ -47,7 +47,7 @@ struct RetryButton: View {
|
|||
replyID: replyID
|
||||
) {
|
||||
// Best effort, unlikely since we already checked BLE state
|
||||
Logger.services.warning("Failed to resend message \(messageID)")
|
||||
Logger.services.warning("Failed to resend message \(messageID, privacy: .public)")
|
||||
} else {
|
||||
switch destination {
|
||||
case .user:
|
||||
|
|
|
|||
|
|
@ -31,10 +31,10 @@ struct TapbackResponses: View {
|
|||
tapback.read = true
|
||||
do {
|
||||
try context.save()
|
||||
Logger.data.info("📖 Read tapback \(tapback.messageId) ")
|
||||
Logger.data.info("📖 Read tapback \(tapback.messageId, privacy: .public) ")
|
||||
onRead()
|
||||
} catch {
|
||||
Logger.data.error("Failed to read tapback \(tapback.messageId): \(error.localizedDescription)")
|
||||
Logger.data.error("Failed to read tapback \(tapback.messageId, privacy: .public): \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -103,11 +103,11 @@ struct UserMessageList: View {
|
|||
message.read = true
|
||||
do {
|
||||
try context.save()
|
||||
Logger.data.info("📖 [App] Read message \(message.messageId) ")
|
||||
Logger.data.info("📖 [App] Read message \(message.messageId, privacy: .public) ")
|
||||
appState.unreadDirectMessages = user.unreadMessages
|
||||
|
||||
} catch {
|
||||
Logger.data.error("Failed to read message \(message.messageId): \(error.localizedDescription)")
|
||||
Logger.data.error("Failed to read message \(message.messageId, privacy: .public): \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -135,7 +135,7 @@ struct DetectionSensorLog: View {
|
|||
self.isExporting = false
|
||||
Logger.services.info("Detection Sensor metrics log download succeeded.")
|
||||
case .failure(let error):
|
||||
Logger.services.error("Detection Sensor log download failed: \(error.localizedDescription).")
|
||||
Logger.services.error("Detection Sensor log download failed: \(error.localizedDescription, privacy: .public).")
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -211,7 +211,7 @@ struct DeviceMetricsLog: View {
|
|||
) {
|
||||
Button("device.metrics.delete", role: .destructive) {
|
||||
if clearTelemetry(destNum: node.num, metricsType: 0, context: context) {
|
||||
Logger.data.notice("Cleared Device Metrics for \(node.num)")
|
||||
Logger.data.notice("Cleared Device Metrics for \(node.num, privacy: .public)")
|
||||
} else {
|
||||
Logger.data.error("Clear Device Metrics Log Failed")
|
||||
}
|
||||
|
|
@ -257,7 +257,7 @@ struct DeviceMetricsLog: View {
|
|||
self.isExporting = false
|
||||
Logger.services.info("Device metrics log download succeeded.")
|
||||
case .failure(let error):
|
||||
Logger.services.error("Device metrics log download failed: \(error.localizedDescription)")
|
||||
Logger.services.error("Device metrics log download failed: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -177,7 +177,7 @@ struct EnvironmentMetricsLog: View {
|
|||
self.isExporting = false
|
||||
Logger.services.info("Environment metrics log download succeeded.")
|
||||
case .failure(let error):
|
||||
Logger.services.error("Environment metrics log download failed: \(error.localizedDescription)")
|
||||
Logger.services.error("Environment metrics log download failed: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ struct DeleteNodeButton: View {
|
|||
id: node.num,
|
||||
context: context
|
||||
) else {
|
||||
Logger.data.error("Unable to find node info to delete node \(node.num)")
|
||||
Logger.data.error("Unable to find node info to delete node \(node.num, privacy: .public)")
|
||||
return
|
||||
}
|
||||
let success = bleManager.removeNode(
|
||||
|
|
@ -49,7 +49,7 @@ struct DeleteNodeButton: View {
|
|||
connectedNodeNum: connectedNode.num
|
||||
)
|
||||
if !success {
|
||||
Logger.data.error("Failed to delete node \(deleteNode.user?.longName ?? "unknown".localized)")
|
||||
Logger.data.error("Failed to delete node \(deleteNode.user?.longName ?? "unknown".localized, privacy: .public)")
|
||||
} else {
|
||||
dismiss()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,20 +19,20 @@ struct NavigateToButton: View {
|
|||
Logger.services.error("NavigateToAction: Selected node does not exist")
|
||||
return
|
||||
}
|
||||
|
||||
Logger.services.info("Fetching NodeInfoEntity for userNum: \(userNum)")
|
||||
|
||||
|
||||
Logger.services.info("Fetching NodeInfoEntity for userNum: \(userNum, privacy: .public)")
|
||||
|
||||
let fetchRequest: NSFetchRequest<NodeInfoEntity> = NSFetchRequest(entityName: "NodeInfoEntity")
|
||||
fetchRequest.predicate = NSPredicate(format: "num == %lld", Int64(userNum))
|
||||
|
||||
|
||||
do {
|
||||
let fetchedNodes = try PersistenceController.shared.container.viewContext.fetch(fetchRequest)
|
||||
|
||||
|
||||
guard let nodeInfo = fetchedNodes.first else {
|
||||
Logger.services.error("NavigateToAction: Node with userNum \(userNum) not found in Core Data")
|
||||
Logger.services.error("NavigateToAction: Node with userNum \(userNum, privacy: .public) not found in Core Data")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
if let latitude = nodeInfo.latestPosition?.latitude,
|
||||
let longitude = nodeInfo.latestPosition?.longitude {
|
||||
if let url = URL(string: "maps://?saddr=&daddr=\(latitude),\(longitude)") {
|
||||
|
|
@ -41,10 +41,10 @@ struct NavigateToButton: View {
|
|||
Logger.services.error("Failed to create URL for navigation")
|
||||
}
|
||||
} else {
|
||||
Logger.services.warning("NavigateToAction: Node \(userNum) has invalid or missing coordinates")
|
||||
Logger.services.warning("NavigateToAction: Node \(userNum, privacy: .public) has invalid or missing coordinates")
|
||||
}
|
||||
} catch {
|
||||
Logger.services.error("NavigateToAction: Failed to fetch node with userNum \(userNum): \(error.localizedDescription)")
|
||||
Logger.services.error("NavigateToAction: Failed to fetch node with userNum \(userNum, privacy: .public): \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
} label: {
|
||||
Label {
|
||||
|
|
|
|||
|
|
@ -1,233 +0,0 @@
|
|||
//
|
||||
// EnvironmentDefaultSeries.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Created by Jake Bordens on 12/11/24.
|
||||
//
|
||||
|
||||
import Charts
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
// This is the default configuration used by the EnvironmentMetricsLog view for the chart
|
||||
extension MetricsSeriesList {
|
||||
static var environmentDefaultChartSeries: MetricsSeriesList {
|
||||
MetricsSeriesList([
|
||||
// Temperature Series Configuration
|
||||
MetricsChartSeries(
|
||||
id: "temperature",
|
||||
keyPath: \.temperature,
|
||||
name: "Temperature",
|
||||
abbreviatedName: "Temp",
|
||||
conversion: { t in t.map { Float($0.localeTemperature()) } },
|
||||
foregroundStyle: { chartRange in
|
||||
let locale = NSLocale.current as NSLocale
|
||||
let localeUnit = locale.object(forKey: NSLocale.Key(rawValue: "kCFLocaleTemperatureUnitKey"))
|
||||
let format: UnitTemperature = localeUnit as? String ?? "Celsius" == "Fahrenheit" ? .fahrenheit : .celsius
|
||||
let lowerBound = chartRange.map { Double($0.lowerBound) } ?? 0.0
|
||||
let upperBound = chartRange.map { Double($0.upperBound) } ?? 100.0
|
||||
let stops: [Gradient.Stop] = generateStops(minTemp: lowerBound, maxTemp: upperBound, tempUnit: format, opacity: 1.0)
|
||||
return LinearGradient(stops: stops, startPoint: .bottom, endPoint: .top)
|
||||
},
|
||||
chartBody: { series, chartRange, time, temperature in
|
||||
if let temperature {
|
||||
AreaMark(
|
||||
x: .value("Time", time),
|
||||
yStart: .value(series.abbreviatedName, chartRange?.lowerBound.doubleValue ?? 0.0),
|
||||
yEnd: .value(
|
||||
series.abbreviatedName, temperature.localeTemperature())
|
||||
)
|
||||
.interpolationMethod(.catmullRom)
|
||||
.foregroundStyle(by: .value("Series", series.abbreviatedName))
|
||||
.alignsMarkStylesWithPlotArea()
|
||||
.accessibilityHidden(true)
|
||||
.opacity(0.6)
|
||||
LineMark(
|
||||
x: .value("Time", time),
|
||||
y: .value(
|
||||
series.abbreviatedName, temperature.localeTemperature())
|
||||
)
|
||||
.interpolationMethod(.catmullRom)
|
||||
.foregroundStyle(by: .value("Series", series.abbreviatedName))
|
||||
.lineStyle(StrokeStyle(lineWidth: 4))
|
||||
.alignsMarkStylesWithPlotArea()
|
||||
}
|
||||
}),
|
||||
|
||||
// Relative Humidity Series Configuration
|
||||
MetricsChartSeries(
|
||||
id: "relativeHumidity",
|
||||
keyPath: \.relativeHumidity,
|
||||
name: "Relative Humidity",
|
||||
abbreviatedName: "Hum",
|
||||
foregroundStyle: { _ in
|
||||
.linearGradient(
|
||||
colors: [Color(UIColor.purple.darker(componentDelta: 0.2)), .purple],
|
||||
startPoint: .bottom, endPoint: .top
|
||||
)
|
||||
},
|
||||
chartBody: { series, _, time, humidity in
|
||||
if let humidity {
|
||||
LineMark(
|
||||
x: .value("Time", time),
|
||||
y: .value(series.abbreviatedName, humidity)
|
||||
)
|
||||
.interpolationMethod(.catmullRom)
|
||||
.foregroundStyle(by: .value("Series", series.abbreviatedName))
|
||||
.lineStyle(StrokeStyle(lineWidth: 4))
|
||||
.alignsMarkStylesWithPlotArea()
|
||||
}
|
||||
}),
|
||||
|
||||
// Barometric Pressure Series Configuration
|
||||
MetricsChartSeries(
|
||||
id: "barometricPressure",
|
||||
keyPath: \.barometricPressure,
|
||||
name: "Barometric Pressure",
|
||||
abbreviatedName: "Bar",
|
||||
visible: false,
|
||||
foregroundStyle: { _ in
|
||||
.linearGradient(
|
||||
colors: [Color(UIColor.green.darker(componentDelta: 0.3)), .green],
|
||||
startPoint: .bottom, endPoint: .top
|
||||
)
|
||||
},
|
||||
chartBody: { series, _, time, pressure in
|
||||
if let pressure {
|
||||
LineMark(
|
||||
x: .value("Time", time),
|
||||
y: .value(series.abbreviatedName, pressure)
|
||||
)
|
||||
.interpolationMethod(.catmullRom)
|
||||
.foregroundStyle(by: .value("Series", series.abbreviatedName))
|
||||
.lineStyle(StrokeStyle(lineWidth: 4))
|
||||
.alignsMarkStylesWithPlotArea()
|
||||
}
|
||||
}),
|
||||
|
||||
// Indoor Air Quality Series Configuration
|
||||
MetricsChartSeries(
|
||||
id: "iaq",
|
||||
keyPath: \.iaq,
|
||||
name: "Indoor Air Quality",
|
||||
abbreviatedName: "IAQ",
|
||||
visible: false,
|
||||
foregroundStyle: { _ in .gray },
|
||||
chartBody: { series, _, time, iaq in
|
||||
if let iaq {
|
||||
let iaqEnum = Iaq.getIaq(for: Int(iaq))
|
||||
PointMark(
|
||||
x: .value("Time", time),
|
||||
y: .value(series.abbreviatedName, Float(iaq))
|
||||
)
|
||||
.symbol(Circle())
|
||||
.foregroundStyle(iaqEnum.color)
|
||||
LineMark(
|
||||
x: .value("Time", time),
|
||||
y: .value(series.abbreviatedName, Float(iaq))
|
||||
)
|
||||
.interpolationMethod(.catmullRom)
|
||||
.foregroundStyle(by: .value("Series", series.abbreviatedName))
|
||||
.lineStyle(StrokeStyle(lineWidth: 4))
|
||||
.alignsMarkStylesWithPlotArea()
|
||||
}
|
||||
}),
|
||||
|
||||
// Combined Wind Speed and Direction Series Configuration -- For use in Chart only
|
||||
MetricsChartSeries(
|
||||
id: "windSpeedAndDirection",
|
||||
keyPath: \.windSpeedAndDirection,
|
||||
name: "Wind Speed/Direction",
|
||||
abbreviatedName: "Speed/Dir",
|
||||
visible: false,
|
||||
foregroundStyle: { _ in
|
||||
.linearGradient(
|
||||
colors: [Color(UIColor.yellow.darker(componentDelta: 0.3)), Color(UIColor.yellow.darker(componentDelta: 0.1))],
|
||||
startPoint: .bottom, endPoint: .top
|
||||
)
|
||||
},
|
||||
chartBody: { series, _, time, wsad in
|
||||
if let wsad {
|
||||
// debug data: var wsad = WindSpeedAndDirection(windSpeed:Float.random(in:0...25), windDirection: Int32.random(in:0..<3)*90 )
|
||||
LineMark(
|
||||
x: .value("Time", time),
|
||||
y: .value(series.abbreviatedName, wsad.windSpeed)
|
||||
)
|
||||
.interpolationMethod(.catmullRom)
|
||||
.foregroundStyle(by: .value("Series", series.abbreviatedName))
|
||||
.lineStyle(StrokeStyle(lineWidth: 4))
|
||||
.alignsMarkStylesWithPlotArea()
|
||||
PointMark(
|
||||
x: .value("Time", time),
|
||||
y: .value(series.abbreviatedName, wsad.windSpeed)
|
||||
)
|
||||
.symbol {
|
||||
if let wd = wsad.windDirection {
|
||||
Image(systemName: "location.north.circle.fill")
|
||||
.symbolRenderingMode(.palette)
|
||||
.foregroundStyle(Color.white, Color(UIColor.yellow.darker(componentDelta: 0.3)))
|
||||
.rotationEffect(
|
||||
.degrees(Double(wd)))
|
||||
}
|
||||
}.foregroundStyle(.yellow)
|
||||
}
|
||||
})
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
// Extension to combine windspeed and direction into one attribute for rendering
|
||||
// for rendering on the chart.
|
||||
@objc class WindSpeedAndDirection: NSObject, Plottable, Comparable {
|
||||
|
||||
let windSpeed: Float
|
||||
let windDirection: Int32?
|
||||
init(windSpeed: Float, windDirection: Int32?) {
|
||||
self.windSpeed = windSpeed
|
||||
self.windDirection = windDirection
|
||||
}
|
||||
|
||||
// Plottable Conformance
|
||||
required init?(primitivePlottable: Float) { nil }
|
||||
var primitivePlottable: Float { windSpeed }
|
||||
|
||||
static func < (lhs: WindSpeedAndDirection, rhs: WindSpeedAndDirection) -> Bool {
|
||||
lhs.windSpeed < rhs.windSpeed
|
||||
}
|
||||
}
|
||||
|
||||
@objc extension TelemetryEntity {
|
||||
var windSpeedAndDirection: WindSpeedAndDirection? {
|
||||
guard let windSpeed = self.windSpeed else { return nil }
|
||||
|
||||
return WindSpeedAndDirection(windSpeed: windSpeed, windDirection: self.windDirection)
|
||||
}
|
||||
}
|
||||
|
||||
// From: https://github.com/meshtastic/Meshtastic-Apple/pull/1013/commits/bc932567c742c8fa9fd30752237b10cb762c5ef3
|
||||
// Set up gradient stops relative to the scale of the temperature chart
|
||||
func generateStops(minTemp: Double, maxTemp: Double, tempUnit: UnitTemperature, opacity: Double) -> [Gradient.Stop] {
|
||||
var gradientStops = [Gradient.Stop]()
|
||||
|
||||
let stopTargets: [(Double, Color)] = [
|
||||
((tempUnit == .celsius ? 0 : 32), .blue),
|
||||
((tempUnit == .celsius ? 20 : 68), .yellow),
|
||||
((tempUnit == .celsius ? 30 : 86), .orange),
|
||||
((tempUnit == .celsius ? 55 : 125), .red)
|
||||
]
|
||||
for (stopValue, color) in stopTargets {
|
||||
let stopLocation = transform(stopValue, from: minTemp...maxTemp, to: 0...1)
|
||||
gradientStops.append(Gradient.Stop(color: color.opacity(opacity), location: stopLocation))
|
||||
}
|
||||
return gradientStops
|
||||
}
|
||||
|
||||
// Map inputRange to outputRange
|
||||
func transform<T: FloatingPoint>(_ input: T, from inputRange: ClosedRange<T>, to outputRange: ClosedRange<T>) -> T {
|
||||
// need to determine what that value would be in (to.low, to.high)
|
||||
// difference in output range / difference in input range = slope
|
||||
let slope = (outputRange.upperBound - outputRange.lowerBound) / (inputRange.upperBound - inputRange.lowerBound)
|
||||
// slope * normalized input + output lower
|
||||
let output = slope * (input - inputRange.lowerBound) + outputRange.lowerBound
|
||||
return output
|
||||
}
|
||||
|
|
@ -73,6 +73,77 @@ extension MetricsColumnList {
|
|||
}
|
||||
}),
|
||||
|
||||
// Various Lux
|
||||
MetricsTableColumn(
|
||||
id: "lux",
|
||||
keyPath: \.lux,
|
||||
name: "Lux",
|
||||
abbreviatedName: "Lux",
|
||||
minWidth: 30, maxWidth: 50,
|
||||
visible: false,
|
||||
tableBody: { _, lux in
|
||||
lux.map {
|
||||
Text("\($0.formatted(.number.grouping(.never).precision(.fractionLength(1))))")
|
||||
} ?? Text(Constants.nilValueIndicator)
|
||||
}),
|
||||
|
||||
MetricsTableColumn(
|
||||
id: "whiteLux",
|
||||
keyPath: \.whiteLux,
|
||||
name: "White Lux",
|
||||
abbreviatedName: "White",
|
||||
minWidth: 30, maxWidth: 50,
|
||||
visible: false,
|
||||
tableBody: { _, lux in
|
||||
lux.map {
|
||||
Text("\($0.formatted(.number.grouping(.never).precision(.fractionLength(1))))")
|
||||
} ?? Text(Constants.nilValueIndicator)
|
||||
}),
|
||||
|
||||
MetricsTableColumn(
|
||||
id: "uvLux",
|
||||
keyPath: \.uvLux,
|
||||
name: "UV Lux",
|
||||
abbreviatedName: "UV",
|
||||
minWidth: 30, maxWidth: 50,
|
||||
visible: false,
|
||||
tableBody: { _, lux in
|
||||
lux.map {
|
||||
Text("\($0.formatted(.number.grouping(.never).precision(.fractionLength(1))))")
|
||||
} ?? Text(Constants.nilValueIndicator)
|
||||
}),
|
||||
|
||||
MetricsTableColumn(
|
||||
id: "irLux",
|
||||
keyPath: \.irLux,
|
||||
name: "IR Lux",
|
||||
abbreviatedName: "IR",
|
||||
minWidth: 30, maxWidth: 50,
|
||||
visible: false,
|
||||
tableBody: { _, lux in
|
||||
lux.map {
|
||||
Text("\($0.formatted(.number.grouping(.never).precision(.fractionLength(1))))")
|
||||
} ?? Text(Constants.nilValueIndicator)
|
||||
}),
|
||||
|
||||
// Radiation
|
||||
MetricsTableColumn(
|
||||
id: "radiation",
|
||||
keyPath: \.radiation,
|
||||
name: "Radiation",
|
||||
abbreviatedName: "☢️",
|
||||
minWidth: 30, maxWidth: 50,
|
||||
visible: false,
|
||||
tableBody: { _, radiation in
|
||||
radiation.map {
|
||||
if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac {
|
||||
Text(verbatim: "\($0.formatted(.number.grouping(.never).precision(.fractionLength(1)))) µR/h")
|
||||
} else {
|
||||
Text("\($0.formatted(.number.grouping(.never).precision(.fractionLength(1))))")
|
||||
}
|
||||
} ?? Text(Constants.nilValueIndicator)
|
||||
}),
|
||||
|
||||
// Wind Direction Series Configuration
|
||||
MetricsTableColumn(
|
||||
id: "windDirection",
|
||||
|
|
@ -127,6 +198,126 @@ extension MetricsColumnList {
|
|||
} ?? Text(verbatim: Constants.nilValueIndicator)
|
||||
}),
|
||||
|
||||
// Rainfall 1-hour
|
||||
MetricsTableColumn(
|
||||
id: "rainfall1H",
|
||||
keyPath: \.rainfall1H,
|
||||
name: "Rainfall (1H)",
|
||||
abbreviatedName: "Rain 1H",
|
||||
minWidth: 30, maxWidth: 60,
|
||||
visible: false,
|
||||
tableBody: { _, rainfall in
|
||||
rainfall.map {
|
||||
let rain = Measurement(
|
||||
value: Double($0), unit: UnitLength.millimeters)
|
||||
return Text(
|
||||
rain.formatted(
|
||||
.measurement(
|
||||
width: .abbreviated,
|
||||
numberFormatStyle: .number.grouping(.never)
|
||||
.precision(
|
||||
.fractionLength(0))))
|
||||
)
|
||||
} ?? Text(Constants.nilValueIndicator)
|
||||
}),
|
||||
|
||||
// Rainfall 24-hour
|
||||
MetricsTableColumn(
|
||||
id: "rainfall24H",
|
||||
keyPath: \.rainfall24H,
|
||||
name: "Rainfall (24H)",
|
||||
abbreviatedName: "Rain 24H",
|
||||
minWidth: 30, maxWidth: 60,
|
||||
visible: false,
|
||||
tableBody: { _, rainfall in
|
||||
rainfall.map {
|
||||
let rain = Measurement(
|
||||
value: Double($0), unit: UnitLength.millimeters)
|
||||
return Text(
|
||||
rain.formatted(
|
||||
.measurement(
|
||||
width: .abbreviated,
|
||||
numberFormatStyle: .number.grouping(.never)
|
||||
.precision(
|
||||
.fractionLength(0))))
|
||||
)
|
||||
} ?? Text(Constants.nilValueIndicator)
|
||||
}),
|
||||
|
||||
// Weight
|
||||
MetricsTableColumn(
|
||||
id: "weight",
|
||||
keyPath: \.weight,
|
||||
name: "Weight",
|
||||
abbreviatedName: "kg",
|
||||
minWidth: 30, maxWidth: 60,
|
||||
visible: false,
|
||||
tableBody: { _, weight in
|
||||
weight.map {
|
||||
let weight = Measurement(
|
||||
value: Double($0), unit: UnitMass.kilograms)
|
||||
return Text(
|
||||
weight.formatted(
|
||||
.measurement(
|
||||
width: .abbreviated,
|
||||
numberFormatStyle: .number.grouping(.never)
|
||||
.precision(
|
||||
.fractionLength(0))))
|
||||
)
|
||||
} ?? Text(Constants.nilValueIndicator)
|
||||
}),
|
||||
|
||||
// Distance sensor, often used for water level
|
||||
MetricsTableColumn(
|
||||
id: "distance",
|
||||
keyPath: \.distance,
|
||||
name: "Distance",
|
||||
abbreviatedName: "Dist",
|
||||
minWidth: 30, maxWidth: 50,
|
||||
visible: false,
|
||||
tableBody: { _, distance in
|
||||
distance.map {
|
||||
if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac {
|
||||
Text(verbatim: "\($0.formatted(.number.grouping(.never).precision(.fractionLength(1)))) mm")
|
||||
} else {
|
||||
Text("\($0.formatted(.number.grouping(.never).precision(.fractionLength(1))))")
|
||||
}
|
||||
} ?? Text(Constants.nilValueIndicator)
|
||||
}),
|
||||
|
||||
// Soil Temperature
|
||||
MetricsTableColumn(
|
||||
id: "soilTemperature",
|
||||
keyPath: \.soilTemperature,
|
||||
name: "Soil Temperature",
|
||||
abbreviatedName: "Soil Temp",
|
||||
minWidth: 30, maxWidth: 50,
|
||||
visible: false,
|
||||
tableBody: { _, soilTemperature in
|
||||
soilTemperature.map {
|
||||
Text($0.formattedTemperature())
|
||||
} ?? Text(verbatim: Constants.nilValueIndicator)
|
||||
|
||||
}),
|
||||
|
||||
// Soil Moisture
|
||||
MetricsTableColumn(
|
||||
id: "soilMoisture",
|
||||
keyPath: \.soilMoisture,
|
||||
name: "Soil Moisture",
|
||||
abbreviatedName: "Moist",
|
||||
minWidth: 30, maxWidth: 50,
|
||||
visible: false,
|
||||
tableBody: { _, moisture in
|
||||
moisture.map {
|
||||
if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac {
|
||||
Text("\($0.formatted(.number.grouping(.never).precision(.fractionLength(0))))%")
|
||||
} else {
|
||||
Text("\($0.formatted(.number.grouping(.never).precision(.fractionLength(0))))")
|
||||
}
|
||||
} ?? Text(Constants.nilValueIndicator)
|
||||
}),
|
||||
|
||||
// Timestamp Series Configuration -- for use in table only
|
||||
MetricsTableColumn(
|
||||
id: "time",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,522 @@
|
|||
//
|
||||
// EnvironmentDefaultSeries.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Created by Jake Bordens on 12/11/24.
|
||||
//
|
||||
|
||||
import Charts
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
// This is the default configuration used by the EnvironmentMetricsLog view for the chart
|
||||
extension MetricsSeriesList {
|
||||
static var environmentDefaultChartSeries: MetricsSeriesList {
|
||||
MetricsSeriesList([
|
||||
// Temperature Series Configuration
|
||||
MetricsChartSeries(
|
||||
id: "temperature",
|
||||
keyPath: \.temperature,
|
||||
name: "Temperature",
|
||||
abbreviatedName: "Temp",
|
||||
minumumYAxisSpan: 50.0,
|
||||
conversion: { t in t.map { Float($0.localeTemperature()) } },
|
||||
foregroundStyle: { chartRange in
|
||||
let locale = NSLocale.current as NSLocale
|
||||
let localeUnit = locale.object(forKey: NSLocale.Key(rawValue: "kCFLocaleTemperatureUnitKey"))
|
||||
let format: UnitTemperature = localeUnit as? String ?? "Celsius" == "Fahrenheit" ? .fahrenheit : .celsius
|
||||
let lowerBound = chartRange.map { Double($0.lowerBound) } ?? 0.0
|
||||
let upperBound = chartRange.map { Double($0.upperBound) } ?? 100.0
|
||||
let stops: [Gradient.Stop] = generateStops(minTemp: lowerBound, maxTemp: upperBound, tempUnit: format, opacity: 1.0)
|
||||
return LinearGradient(stops: stops, startPoint: .bottom, endPoint: .top)
|
||||
},
|
||||
chartBody: { series, chartRange, time, temperature in
|
||||
if let temperature {
|
||||
AreaMark(
|
||||
x: .value("Time", time),
|
||||
yStart: .value(series.abbreviatedName, chartRange?.lowerBound.doubleValue ?? 0.0),
|
||||
yEnd: .value(
|
||||
series.abbreviatedName, temperature.localeTemperature())
|
||||
)
|
||||
.interpolationMethod(.catmullRom)
|
||||
.foregroundStyle(by: .value("Series", series.abbreviatedName))
|
||||
.alignsMarkStylesWithPlotArea()
|
||||
.accessibilityHidden(true)
|
||||
.opacity(0.6)
|
||||
LineMark(
|
||||
x: .value("Time", time),
|
||||
y: .value(
|
||||
series.abbreviatedName, temperature.localeTemperature())
|
||||
)
|
||||
.interpolationMethod(.catmullRom)
|
||||
.foregroundStyle(by: .value("Series", series.abbreviatedName))
|
||||
.lineStyle(StrokeStyle(lineWidth: 4))
|
||||
.alignsMarkStylesWithPlotArea()
|
||||
}
|
||||
}),
|
||||
|
||||
// Relative Humidity Series Configuration
|
||||
MetricsChartSeries(
|
||||
id: "relativeHumidity",
|
||||
keyPath: \.relativeHumidity,
|
||||
name: "Relative Humidity",
|
||||
abbreviatedName: "Hum",
|
||||
initialYAxisRange: 0.0...100.0,
|
||||
foregroundStyle: { _ in
|
||||
.linearGradient(
|
||||
colors: [Color(UIColor.purple.darker(componentDelta: 0.2)), .purple],
|
||||
startPoint: .bottom, endPoint: .top
|
||||
)
|
||||
},
|
||||
chartBody: { series, _, time, humidity in
|
||||
if let humidity {
|
||||
LineMark(
|
||||
x: .value("Time", time),
|
||||
y: .value(series.abbreviatedName, humidity)
|
||||
)
|
||||
.interpolationMethod(.catmullRom)
|
||||
.foregroundStyle(by: .value("Series", series.abbreviatedName))
|
||||
.lineStyle(StrokeStyle(lineWidth: 4))
|
||||
.alignsMarkStylesWithPlotArea()
|
||||
}
|
||||
}),
|
||||
|
||||
// Barometric Pressure Series Configuration
|
||||
MetricsChartSeries(
|
||||
id: "barometricPressure",
|
||||
keyPath: \.barometricPressure,
|
||||
name: "Barometric Pressure",
|
||||
abbreviatedName: "Bar",
|
||||
visible: false,
|
||||
foregroundStyle: { _ in
|
||||
.linearGradient(
|
||||
colors: [Color(UIColor.green.darker(componentDelta: 0.3)), .green],
|
||||
startPoint: .bottom, endPoint: .top
|
||||
)
|
||||
},
|
||||
chartBody: { series, _, time, pressure in
|
||||
if let pressure {
|
||||
LineMark(
|
||||
x: .value("Time", time),
|
||||
y: .value(series.abbreviatedName, pressure)
|
||||
)
|
||||
.interpolationMethod(.catmullRom)
|
||||
.foregroundStyle(by: .value("Series", series.abbreviatedName))
|
||||
.lineStyle(StrokeStyle(lineWidth: 4))
|
||||
.alignsMarkStylesWithPlotArea()
|
||||
}
|
||||
}),
|
||||
|
||||
// Indoor Air Quality Series Configuration
|
||||
MetricsChartSeries(
|
||||
id: "iaq",
|
||||
keyPath: \.iaq,
|
||||
name: "Indoor Air Quality",
|
||||
abbreviatedName: "IAQ",
|
||||
visible: false,
|
||||
foregroundStyle: { _ in .gray },
|
||||
chartBody: { series, _, time, iaq in
|
||||
if let iaq {
|
||||
let iaqEnum = Iaq.getIaq(for: Int(iaq))
|
||||
PointMark(
|
||||
x: .value("Time", time),
|
||||
y: .value(series.abbreviatedName, Float(iaq))
|
||||
)
|
||||
.symbol(Circle())
|
||||
.foregroundStyle(iaqEnum.color)
|
||||
LineMark(
|
||||
x: .value("Time", time),
|
||||
y: .value(series.abbreviatedName, Float(iaq))
|
||||
)
|
||||
.interpolationMethod(.catmullRom)
|
||||
.foregroundStyle(by: .value("Series", series.abbreviatedName))
|
||||
.lineStyle(StrokeStyle(lineWidth: 4))
|
||||
.alignsMarkStylesWithPlotArea()
|
||||
}
|
||||
}),
|
||||
|
||||
// Lux
|
||||
MetricsChartSeries(
|
||||
id: "lux",
|
||||
keyPath: \.lux,
|
||||
name: "Lux",
|
||||
abbreviatedName: "Lux",
|
||||
visible: false,
|
||||
foregroundStyle: { _ in
|
||||
.linearGradient(
|
||||
colors: [Color(UIColor.cyan.lighter(componentDelta: 0.3)), .cyan],
|
||||
startPoint: .bottom, endPoint: .top
|
||||
)
|
||||
},
|
||||
chartBody: { series, _, time, lux in
|
||||
if let lux {
|
||||
LineMark(
|
||||
x: .value("Time", time),
|
||||
y: .value(series.abbreviatedName, lux)
|
||||
)
|
||||
.interpolationMethod(.catmullRom)
|
||||
.foregroundStyle(by: .value("Series", series.abbreviatedName))
|
||||
.lineStyle(StrokeStyle(lineWidth: 4))
|
||||
.alignsMarkStylesWithPlotArea()
|
||||
}
|
||||
}),
|
||||
|
||||
// White Lux
|
||||
MetricsChartSeries(
|
||||
id: "whiteLux",
|
||||
keyPath: \.whiteLux,
|
||||
name: "White Lux",
|
||||
abbreviatedName: "White",
|
||||
visible: false,
|
||||
foregroundStyle: { _ in
|
||||
.linearGradient(
|
||||
colors: [Color(UIColor.cyan.lighter(componentDelta: 0.5)), Color(UIColor.cyan.lighter(componentDelta: 0.2))],
|
||||
startPoint: .bottom, endPoint: .top
|
||||
)
|
||||
},
|
||||
chartBody: { series, _, time, lux in
|
||||
if let lux {
|
||||
LineMark(
|
||||
x: .value("Time", time),
|
||||
y: .value(series.abbreviatedName, lux)
|
||||
)
|
||||
.interpolationMethod(.catmullRom)
|
||||
.foregroundStyle(by: .value("Series", series.abbreviatedName))
|
||||
.lineStyle(StrokeStyle(lineWidth: 4))
|
||||
.alignsMarkStylesWithPlotArea()
|
||||
}
|
||||
}),
|
||||
|
||||
// UV Lux
|
||||
MetricsChartSeries(
|
||||
id: "uvLux",
|
||||
keyPath: \.uvLux,
|
||||
name: "UV Lux",
|
||||
abbreviatedName: "UV",
|
||||
visible: false,
|
||||
foregroundStyle: { _ in
|
||||
.linearGradient(
|
||||
colors: [Color(UIColor.systemIndigo.lighter(componentDelta: 0.4)), Color(UIColor.systemIndigo.lighter(componentDelta: 0.2))],
|
||||
startPoint: .bottom, endPoint: .top
|
||||
)
|
||||
},
|
||||
chartBody: { series, _, time, lux in
|
||||
if let lux {
|
||||
LineMark(
|
||||
x: .value("Time", time),
|
||||
y: .value(series.abbreviatedName, lux)
|
||||
)
|
||||
.interpolationMethod(.catmullRom)
|
||||
.foregroundStyle(by: .value("Series", series.abbreviatedName))
|
||||
.lineStyle(StrokeStyle(lineWidth: 4))
|
||||
.alignsMarkStylesWithPlotArea()
|
||||
}
|
||||
}),
|
||||
|
||||
// IR Lux
|
||||
MetricsChartSeries(
|
||||
id: "irLux",
|
||||
keyPath: \.irLux,
|
||||
name: "IR Lux",
|
||||
abbreviatedName: "IR",
|
||||
visible: false,
|
||||
foregroundStyle: { _ in
|
||||
.linearGradient(
|
||||
colors: [Color(UIColor.red.darker(componentDelta: 0.5)), .red],
|
||||
startPoint: .bottom, endPoint: .top
|
||||
)
|
||||
},
|
||||
chartBody: { series, _, time, lux in
|
||||
if let lux {
|
||||
LineMark(
|
||||
x: .value("Time", time),
|
||||
y: .value(series.abbreviatedName, lux)
|
||||
)
|
||||
.interpolationMethod(.catmullRom)
|
||||
.foregroundStyle(by: .value("Series", series.abbreviatedName))
|
||||
.lineStyle(StrokeStyle(lineWidth: 4))
|
||||
.alignsMarkStylesWithPlotArea()
|
||||
}
|
||||
}),
|
||||
|
||||
// Radiation
|
||||
MetricsChartSeries(
|
||||
id: "radiation",
|
||||
keyPath: \.radiation,
|
||||
name: "Radiation",
|
||||
abbreviatedName: "☢️",
|
||||
minumumYAxisSpan: 20.0,
|
||||
visible: false,
|
||||
foregroundStyle: { _ in
|
||||
.linearGradient(
|
||||
colors: [Color(UIColor.orange.darker(componentDelta: 0.4)), .orange],
|
||||
startPoint: .bottom, endPoint: .top
|
||||
)
|
||||
},
|
||||
chartBody: { series, _, time, radiation in
|
||||
if let radiation {
|
||||
LineMark(
|
||||
x: .value("Time", time),
|
||||
y: .value(series.abbreviatedName, radiation)
|
||||
)
|
||||
.interpolationMethod(.catmullRom)
|
||||
.foregroundStyle(by: .value("Series", series.abbreviatedName))
|
||||
.lineStyle(StrokeStyle(lineWidth: 4))
|
||||
.alignsMarkStylesWithPlotArea()
|
||||
}
|
||||
}),
|
||||
|
||||
// Combined Wind Speed and Direction Series Configuration -- For use in Chart only
|
||||
MetricsChartSeries(
|
||||
id: "windSpeedAndDirection",
|
||||
keyPath: \.windSpeedAndDirection,
|
||||
name: "Wind Speed/Direction",
|
||||
abbreviatedName: "Speed/Dir",
|
||||
visible: false,
|
||||
foregroundStyle: { _ in
|
||||
.linearGradient(
|
||||
colors: [Color(UIColor.yellow.darker(componentDelta: 0.3)), Color(UIColor.yellow.darker(componentDelta: 0.1))],
|
||||
startPoint: .bottom, endPoint: .top
|
||||
)
|
||||
},
|
||||
chartBody: { series, _, time, wsad in
|
||||
if let wsad {
|
||||
// debug data: var wsad = WindSpeedAndDirection(windSpeed:Float.random(in:0...25), windDirection: Int32.random(in:0..<3)*90 )
|
||||
LineMark(
|
||||
x: .value("Time", time),
|
||||
y: .value(series.abbreviatedName, wsad.windSpeed)
|
||||
)
|
||||
.interpolationMethod(.catmullRom)
|
||||
.foregroundStyle(by: .value("Series", series.abbreviatedName))
|
||||
.lineStyle(StrokeStyle(lineWidth: 4))
|
||||
.alignsMarkStylesWithPlotArea()
|
||||
PointMark(
|
||||
x: .value("Time", time),
|
||||
y: .value(series.abbreviatedName, wsad.windSpeed)
|
||||
)
|
||||
.symbol {
|
||||
if let wd = wsad.windDirection {
|
||||
Image(systemName: "location.north.circle.fill")
|
||||
.symbolRenderingMode(.palette)
|
||||
.foregroundStyle(Color.white, Color(UIColor.yellow.darker(componentDelta: 0.3)))
|
||||
.rotationEffect(
|
||||
.degrees(Double(wd)))
|
||||
}
|
||||
}.foregroundStyle(.yellow)
|
||||
}
|
||||
}),
|
||||
|
||||
// Rainfaill 1-hour
|
||||
MetricsChartSeries(
|
||||
id: "rainfall1H",
|
||||
keyPath: \.rainfall1H,
|
||||
name: "Rainfall 1H",
|
||||
abbreviatedName: "Rain 1H",
|
||||
visible: false,
|
||||
foregroundStyle: { _ in
|
||||
.linearGradient(
|
||||
colors: [Color(UIColor.systemBlue.darker(componentDelta: 0.5)), .blue],
|
||||
startPoint: .bottom, endPoint: .top
|
||||
)
|
||||
},
|
||||
chartBody: { series, _, time, rainfall in
|
||||
if let rainfall {
|
||||
LineMark(
|
||||
x: .value("Time", time),
|
||||
y: .value(series.abbreviatedName, rainfall)
|
||||
)
|
||||
.interpolationMethod(.catmullRom)
|
||||
.foregroundStyle(by: .value("Series", series.abbreviatedName))
|
||||
.lineStyle(StrokeStyle(lineWidth: 4))
|
||||
.alignsMarkStylesWithPlotArea()
|
||||
}
|
||||
}),
|
||||
|
||||
// Rainfaill 24-hour
|
||||
MetricsChartSeries(
|
||||
id: "rainfall24H",
|
||||
keyPath: \.rainfall24H,
|
||||
name: "Rainfall 24H",
|
||||
abbreviatedName: "Rain 24H",
|
||||
visible: false,
|
||||
foregroundStyle: { _ in
|
||||
.linearGradient(
|
||||
colors: [Color(UIColor.systemBlue.darker(componentDelta: 0.5)), .cyan],
|
||||
startPoint: .bottom, endPoint: .top
|
||||
)
|
||||
},
|
||||
chartBody: { series, _, time, rainfall in
|
||||
if let rainfall {
|
||||
LineMark(
|
||||
x: .value("Time", time),
|
||||
y: .value(series.abbreviatedName, rainfall)
|
||||
)
|
||||
.interpolationMethod(.catmullRom)
|
||||
.foregroundStyle(by: .value("Series", series.abbreviatedName))
|
||||
.lineStyle(StrokeStyle(lineWidth: 4))
|
||||
.alignsMarkStylesWithPlotArea()
|
||||
}
|
||||
}),
|
||||
|
||||
// Weight
|
||||
MetricsChartSeries(
|
||||
id: "weight",
|
||||
keyPath: \.weight,
|
||||
name: "Weight",
|
||||
abbreviatedName: "kg",
|
||||
visible: false,
|
||||
foregroundStyle: { _ in
|
||||
.linearGradient(
|
||||
colors: [Color(UIColor.systemPink.darker(componentDelta: 0.5)), .pink],
|
||||
startPoint: .bottom, endPoint: .top
|
||||
)
|
||||
},
|
||||
chartBody: { series, _, time, weight in
|
||||
if let weight {
|
||||
LineMark(
|
||||
x: .value("Time", time),
|
||||
y: .value(series.abbreviatedName, weight)
|
||||
)
|
||||
.interpolationMethod(.catmullRom)
|
||||
.foregroundStyle(by: .value("Series", series.abbreviatedName))
|
||||
.lineStyle(StrokeStyle(lineWidth: 4))
|
||||
.alignsMarkStylesWithPlotArea()
|
||||
}
|
||||
}),
|
||||
|
||||
// Distance
|
||||
MetricsChartSeries(
|
||||
id: "distance",
|
||||
keyPath: \.distance,
|
||||
name: "Distance",
|
||||
abbreviatedName: "Dist",
|
||||
visible: false,
|
||||
foregroundStyle: { _ in
|
||||
.linearGradient(
|
||||
colors: [Color(UIColor.systemTeal.darker(componentDelta: 0.7)), Color(UIColor.systemTeal)],
|
||||
startPoint: .bottom, endPoint: .top
|
||||
)
|
||||
},
|
||||
chartBody: { series, _, time, distance in
|
||||
if let distance {
|
||||
LineMark(
|
||||
x: .value("Time", time),
|
||||
y: .value(series.abbreviatedName, distance)
|
||||
)
|
||||
.interpolationMethod(.catmullRom)
|
||||
.foregroundStyle(by: .value("Series", series.abbreviatedName))
|
||||
.lineStyle(StrokeStyle(lineWidth: 4))
|
||||
.alignsMarkStylesWithPlotArea()
|
||||
}
|
||||
}),
|
||||
|
||||
// Soil Temperature
|
||||
MetricsChartSeries(
|
||||
id: "soilTemperature",
|
||||
keyPath: \.soilTemperature,
|
||||
name: "Soil Temperature",
|
||||
abbreviatedName: "Soil Temp",
|
||||
visible: false,
|
||||
foregroundStyle: { _ in
|
||||
.linearGradient(
|
||||
colors: [Color(UIColor.brown.darker(componentDelta: 0.4)), .brown],
|
||||
startPoint: .bottom, endPoint: .top
|
||||
)
|
||||
},
|
||||
chartBody: { series, _, time, soilTemp in
|
||||
if let soilTemp {
|
||||
LineMark(
|
||||
x: .value("Time", time),
|
||||
y: .value(series.abbreviatedName, soilTemp)
|
||||
)
|
||||
.interpolationMethod(.catmullRom)
|
||||
.foregroundStyle(by: .value("Series", series.abbreviatedName))
|
||||
.lineStyle(StrokeStyle(lineWidth: 4))
|
||||
.alignsMarkStylesWithPlotArea()
|
||||
}
|
||||
}),
|
||||
|
||||
// Soil Temperature
|
||||
MetricsChartSeries(
|
||||
id: "soilMoisture",
|
||||
keyPath: \.soilMoisture,
|
||||
name: "Soil Moisture",
|
||||
abbreviatedName: "Moist",
|
||||
visible: false,
|
||||
foregroundStyle: { _ in
|
||||
.linearGradient(
|
||||
colors: [Color(UIColor.blue.darker(componentDelta: 0.4)), .brown],
|
||||
startPoint: .bottom, endPoint: .top
|
||||
)
|
||||
},
|
||||
chartBody: { series, _, time, soilMoisture in
|
||||
if let soilMoisture {
|
||||
LineMark(
|
||||
x: .value("Time", time),
|
||||
y: .value(series.abbreviatedName, soilMoisture)
|
||||
)
|
||||
.interpolationMethod(.catmullRom)
|
||||
.foregroundStyle(by: .value("Series", series.abbreviatedName))
|
||||
.lineStyle(StrokeStyle(lineWidth: 4))
|
||||
.alignsMarkStylesWithPlotArea()
|
||||
}
|
||||
}),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
// Extension to combine windspeed and direction into one attribute for rendering
|
||||
// for rendering on the chart.
|
||||
@objc class WindSpeedAndDirection: NSObject, Plottable, Comparable {
|
||||
|
||||
let windSpeed: Float
|
||||
let windDirection: Int32?
|
||||
init(windSpeed: Float, windDirection: Int32?) {
|
||||
self.windSpeed = windSpeed
|
||||
self.windDirection = windDirection
|
||||
}
|
||||
|
||||
// Plottable Conformance
|
||||
required init?(primitivePlottable: Float) { nil }
|
||||
var primitivePlottable: Float { windSpeed }
|
||||
|
||||
static func < (lhs: WindSpeedAndDirection, rhs: WindSpeedAndDirection) -> Bool {
|
||||
lhs.windSpeed < rhs.windSpeed
|
||||
}
|
||||
}
|
||||
|
||||
@objc extension TelemetryEntity {
|
||||
var windSpeedAndDirection: WindSpeedAndDirection? {
|
||||
guard let windSpeed = self.windSpeed else { return nil }
|
||||
|
||||
return WindSpeedAndDirection(windSpeed: windSpeed, windDirection: self.windDirection)
|
||||
}
|
||||
}
|
||||
|
||||
// From: https://github.com/meshtastic/Meshtastic-Apple/pull/1013/commits/bc932567c742c8fa9fd30752237b10cb762c5ef3
|
||||
// Set up gradient stops relative to the scale of the temperature chart
|
||||
func generateStops(minTemp: Double, maxTemp: Double, tempUnit: UnitTemperature, opacity: Double) -> [Gradient.Stop] {
|
||||
var gradientStops = [Gradient.Stop]()
|
||||
|
||||
let stopTargets: [(Double, Color)] = [
|
||||
((tempUnit == .celsius ? 0 : 32), .blue),
|
||||
((tempUnit == .celsius ? 20 : 68), .yellow),
|
||||
((tempUnit == .celsius ? 30 : 86), .orange),
|
||||
((tempUnit == .celsius ? 55 : 125), .red)
|
||||
]
|
||||
for (stopValue, color) in stopTargets {
|
||||
let stopLocation = transform(stopValue, from: minTemp...maxTemp, to: 0...1)
|
||||
gradientStops.append(Gradient.Stop(color: color.opacity(opacity), location: stopLocation))
|
||||
}
|
||||
return gradientStops
|
||||
}
|
||||
|
||||
// Map inputRange to outputRange
|
||||
func transform<T: FloatingPoint>(_ input: T, from inputRange: ClosedRange<T>, to outputRange: ClosedRange<T>) -> T {
|
||||
// need to determine what that value would be in (to.low, to.high)
|
||||
// difference in output range / difference in input range = slope
|
||||
let slope = (outputRange.upperBound - outputRange.lowerBound) / (inputRange.upperBound - inputRange.lowerBound)
|
||||
// slope * normalized input + output lower
|
||||
let output = slope * (input - inputRange.lowerBound) + outputRange.lowerBound
|
||||
return output
|
||||
}
|
||||
|
|
@ -163,7 +163,7 @@ struct NodeDetail: View {
|
|||
}
|
||||
}
|
||||
|
||||
if let firstHeard = node.firstHeard, firstHeard.timeIntervalSince1970 > 0 {
|
||||
if let firstHeard = node.firstHeard, firstHeard.timeIntervalSince1970 > 0 && firstHeard < Calendar.current.date(byAdding: .year, value: 1, to: Date())! {
|
||||
HStack {
|
||||
Label {
|
||||
Text("First heard")
|
||||
|
|
@ -184,7 +184,7 @@ struct NodeDetail: View {
|
|||
}
|
||||
}
|
||||
|
||||
if let lastHeard = node.lastHeard, lastHeard.timeIntervalSince1970 > 0 {
|
||||
if let lastHeard = node.lastHeard, lastHeard.timeIntervalSince1970 > 0 && lastHeard < Calendar.current.date(byAdding: .year, value: 1, to: Date())! {
|
||||
HStack {
|
||||
Label {
|
||||
Text("Last heard")
|
||||
|
|
@ -195,8 +195,10 @@ struct NodeDetail: View {
|
|||
Spacer()
|
||||
|
||||
if dateFormatRelative, let text = Self.relativeFormatter.string(for: lastHeard) {
|
||||
Text(text)
|
||||
.textSelection(.enabled)
|
||||
if lastHeard.formatted() != "unknown.age".localized {
|
||||
Text(text)
|
||||
.textSelection(.enabled)
|
||||
}
|
||||
} else {
|
||||
Text(lastHeard.formatted())
|
||||
.textSelection(.enabled)
|
||||
|
|
@ -212,7 +214,7 @@ struct NodeDetail: View {
|
|||
// to use with WeatherKit, or has actual data in the most recent EnvironmentMetrics entity
|
||||
// that will be rendered in this section.
|
||||
if node.hasPositions && UserDefaults.environmentEnableWeatherKit
|
||||
|| node.hasDataForLatestEnvironmentMetrics(attributes: ["iaq", "temperature", "relativeHumidity", "barometricPressure", "windSpeed"]) {
|
||||
|| node.hasDataForLatestEnvironmentMetrics(attributes: ["iaq", "temperature", "relativeHumidity", "barometricPressure", "windSpeed", "radiation", "weight", "distance", "soilTemperature", "soilMoisture"]) {
|
||||
Section("Environment") {
|
||||
if !node.hasEnvironmentMetrics {
|
||||
LocalWeatherConditions(location: node.latestPosition?.nodeLocation)
|
||||
|
|
@ -245,6 +247,44 @@ struct NodeDetail: View {
|
|||
WindCompactWidget(speed: windSpeedMeasurement.formatted(.measurement(width: .abbreviated, numberFormatStyle: .number.precision(.fractionLength(0)))),
|
||||
gust: node.latestEnvironmentMetrics?.windGust ?? 0.0 > 0.0 ? windGust?.formatted(.measurement(width: .abbreviated, numberFormatStyle: .number.precision(.fractionLength(0)))) : "", direction: direction)
|
||||
}
|
||||
if let rainfall1h = node.latestEnvironmentMetrics?.rainfall1H {
|
||||
let locale = NSLocale.current as NSLocale
|
||||
let usesMetricSystem = locale.usesMetricSystem // Returns true for metric (mm), false for imperial (inches)
|
||||
let unit = usesMetricSystem ? UnitLength.millimeters : UnitLength.inches
|
||||
let unitLabel = usesMetricSystem ? "mm" : "in"
|
||||
let measurement = Measurement(value: Double(rainfall1h), unit: UnitLength.millimeters)
|
||||
let decimals = usesMetricSystem ? 0 : 1
|
||||
let formattedRain = measurement.converted(to: unit).value.formatted(.number.precision(.fractionLength(decimals)))
|
||||
RainfallCompactWidget(timespan: .rainfall1H, rainfall: formattedRain, unit: unitLabel)
|
||||
}
|
||||
if let rainfall24h = node.latestEnvironmentMetrics?.rainfall24H {
|
||||
let locale = NSLocale.current as NSLocale
|
||||
let usesMetricSystem = locale.usesMetricSystem // Returns true for metric (mm), false for imperial (inches)
|
||||
let unit = usesMetricSystem ? UnitLength.millimeters : UnitLength.inches
|
||||
let unitLabel = usesMetricSystem ? "mm" : "in"
|
||||
let measurement = Measurement(value: Double(rainfall24h), unit: UnitLength.millimeters)
|
||||
let decimals = usesMetricSystem ? 0 : 1
|
||||
let formattedRain = measurement.converted(to: unit).value.formatted(.number.precision(.fractionLength(decimals)))
|
||||
RainfallCompactWidget(timespan: .rainfall24H, rainfall: formattedRain, unit: unitLabel)
|
||||
}
|
||||
if let radiation = node.latestEnvironmentMetrics?.radiation {
|
||||
RadiationCompactWidget(radiation: radiation.formatted(.number.precision(.fractionLength(1))), unit: "µR/hr")
|
||||
}
|
||||
if let weight = node.latestEnvironmentMetrics?.weight {
|
||||
WeightCompactWidget(weight: weight.formatted(.number.precision(.fractionLength(1))), unit: "kg")
|
||||
}
|
||||
if let distance = node.latestEnvironmentMetrics?.distance {
|
||||
DistanceCompactWidget(distance: distance.formatted(.number.precision(.fractionLength(0))), unit: "mm")
|
||||
}
|
||||
if let soilTemperature = node.latestEnvironmentMetrics?.soilTemperature {
|
||||
let locale = NSLocale.current as NSLocale
|
||||
let localeUnit = locale.object(forKey: NSLocale.Key(rawValue: "kCFLocaleTemperatureUnitKey"))
|
||||
let unit = localeUnit as? String ?? "Celsius" == "Fahrenheit" ? "°F" : "°C"
|
||||
SoilTemperatureCompactWidget(temperature: soilTemperature.localeTemperature().formatted(.number.precision(.fractionLength(0))), unit: unit)
|
||||
}
|
||||
if let soilMoisture = node.latestEnvironmentMetrics?.soilMoisture {
|
||||
SoilMoistureCompactWidget(moisture: soilMoisture.formatted(.number.precision(.fractionLength(0))), unit: "%")
|
||||
}
|
||||
}
|
||||
.padding(node.latestEnvironmentMetrics?.iaq ?? -1 > 0 ? .bottom : .vertical)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,12 +22,12 @@ struct NodeInfoItem: View {
|
|||
if user.hwModel != "UNSET" {
|
||||
VStack(alignment: .center) {
|
||||
Spacer()
|
||||
Image(systemName: currentDevice?.activelySupported ?? false ? "checkmark.seal.fill" : "x.circle")
|
||||
Image(systemName: currentDevice?.activelySupported ?? false ? "checkmark.seal.fill" : "seal.fill")
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fill)
|
||||
.frame(width: 75, height: 75)
|
||||
.foregroundStyle(currentDevice?.activelySupported ?? false ? .green : .red)
|
||||
Text( currentDevice?.activelySupported ?? false ? "Supported" : "Unsupported")
|
||||
Text( currentDevice?.activelySupported ?? false ? "Full Support" : "Community Support")
|
||||
.foregroundStyle(.gray)
|
||||
.font(.callout)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ struct NodeListItem: View {
|
|||
let (image, color) = userKeyStatus
|
||||
IconAndText(systemName: image,
|
||||
imageColor: color,
|
||||
text: node.user?.longName ?? "unknown".localized,
|
||||
text: node.user?.longName?.addingVariationSelectors ?? "unknown".localized,
|
||||
textColor: .primary)
|
||||
if node.favorite {
|
||||
Spacer()
|
||||
|
|
@ -77,10 +77,10 @@ struct NodeListItem: View {
|
|||
imageColor: .green,
|
||||
text: "connected".localized)
|
||||
}
|
||||
if node.lastHeard?.timeIntervalSince1970 ?? 0 > 0 {
|
||||
if node.lastHeard?.timeIntervalSince1970 ?? 0 > 0 && node.lastHeard! < Calendar.current.date(byAdding: .year, value: 1, to: Date())!{
|
||||
IconAndText(systemName: node.isOnline ? "checkmark.circle.fill" : "moon.circle.fill",
|
||||
imageColor: node.isOnline ? .green : .orange,
|
||||
text: node.lastHeard?.formatted() ?? "unknown")
|
||||
text: node.lastHeard?.formatted() ?? "unknown.age".localized)
|
||||
}
|
||||
let role = DeviceRoles(rawValue: Int(node.user?.role ?? 0))
|
||||
IconAndText(systemName: role?.systemName ?? "figure",
|
||||
|
|
|
|||
|
|
@ -115,7 +115,7 @@ struct MeshMap: View {
|
|||
editingWaypoint!.longitudeI = Int32((newWaypointCoord?.longitude ?? 0) * 1e7)
|
||||
editingWaypoint!.expire = Date.now.addingTimeInterval(60 * 480)
|
||||
editingWaypoint!.id = 0
|
||||
Logger.services.debug("Long press occured at Lat: \(coordinate.latitude) Long: \(coordinate.longitude)")
|
||||
Logger.services.debug("Long press occured at Lat: \(coordinate.latitude, privacy: .public) Long: \(coordinate.longitude, privacy: .public)")
|
||||
default: return
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -237,7 +237,7 @@ struct NodeList: View {
|
|||
if deleteNode != nil {
|
||||
let success = bleManager.removeNode(node: deleteNode!, connectedNodeNum: Int64(bleManager.connectedPeripheral?.num ?? -1))
|
||||
if !success {
|
||||
Logger.data.error("Failed to delete node \(deleteNode?.user?.longName ?? "unknown".localized)")
|
||||
Logger.data.error("Failed to delete node \(deleteNode?.user?.longName ?? "unknown".localized, privacy: .public)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -264,7 +264,7 @@ struct NodeList: View {
|
|||
columnVisibility: columnVisibility
|
||||
)
|
||||
.edgesIgnoringSafeArea([.leading, .trailing])
|
||||
.navigationBarTitle(String(node.user?.longName ?? "unknown".localized), displayMode: .inline)
|
||||
.navigationBarTitle(String(node.user?.longName?.addingVariationSelectors ?? "unknown".localized), displayMode: .inline)
|
||||
.navigationBarItems(
|
||||
trailing: ZStack {
|
||||
if UIDevice.current.userInterfaceIdiom != .phone {
|
||||
|
|
|
|||
|
|
@ -176,7 +176,7 @@ struct PaxCounterLog: View {
|
|||
) {
|
||||
Button("paxcounter.delete", role: .destructive) {
|
||||
if clearPax(destNum: node.num, context: context) {
|
||||
Logger.services.info("Cleared Pax Counter for \(node.num)")
|
||||
Logger.services.info("Cleared Pax Counter for \(node.num, privacy: .public)")
|
||||
} else {
|
||||
Logger.services.error("Clear Pax Counter Log Failed")
|
||||
}
|
||||
|
|
@ -216,7 +216,7 @@ struct PaxCounterLog: View {
|
|||
self.isExporting = false
|
||||
Logger.services.info("PAX Counter log download succeeded")
|
||||
case .failure(let error):
|
||||
Logger.services.error("PAX Counter log download failed: \(error.localizedDescription)")
|
||||
Logger.services.error("PAX Counter log download failed: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -163,7 +163,7 @@ struct PositionLog: View {
|
|||
Logger.services.info("Position log download succeeded.")
|
||||
self.isExporting = false
|
||||
case .failure(let error):
|
||||
Logger.services.error("Position log download failed: \(error.localizedDescription)")
|
||||
Logger.services.error("Position log download failed: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -243,7 +243,7 @@ struct PowerMetricsLog: View {
|
|||
) {
|
||||
Button("Delete Power metrics?", role: .destructive) {
|
||||
if clearTelemetry(destNum: node.num, metricsType: 2, context: context) {
|
||||
Logger.data.notice("Cleared Power Metrics for \(node.num)")
|
||||
Logger.data.notice("Cleared Power Metrics for \(node.num, privacy: .public)")
|
||||
} else {
|
||||
Logger.data.error("Clear Power Metrics Log Failed")
|
||||
}
|
||||
|
|
@ -289,7 +289,7 @@ struct PowerMetricsLog: View {
|
|||
self.isExporting = false
|
||||
Logger.services.info("Power metrics log download succeeded.")
|
||||
case .failure(let error):
|
||||
Logger.services.error("Power metrics log download failed: \(error.localizedDescription)")
|
||||
Logger.services.error("Power metrics log download failed: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ struct TraceRouteLog: View {
|
|||
do {
|
||||
try context.save()
|
||||
} catch let error as NSError {
|
||||
Logger.data.error("\(error.localizedDescription)")
|
||||
Logger.data.error("\(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
} label: {
|
||||
Label("delete", systemImage: "trash")
|
||||
|
|
|
|||
|
|
@ -25,34 +25,6 @@ struct AppData: View {
|
|||
GPSStatus()
|
||||
}
|
||||
Divider()
|
||||
Button(action: {
|
||||
let container = NSPersistentContainer(name: "Meshtastic")
|
||||
guard let url = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else {
|
||||
Logger.data.error("nil File path for back")
|
||||
return
|
||||
}
|
||||
do {
|
||||
try container.copyPersistentStores(to: url.appendingPathComponent("backup").appendingPathComponent("\(UserDefaults.preferredPeripheralNum)"), overwriting: true)
|
||||
loadFiles()
|
||||
Logger.data.notice("🗂️ Made a core data backup to backup/\(UserDefaults.preferredPeripheralNum)")
|
||||
} catch {
|
||||
Logger.data.error("🗂️ Core data backup copy error: \(error, privacy: .public)")
|
||||
}
|
||||
}) {
|
||||
Label {
|
||||
Text("Backup Database")
|
||||
.font(idiom == .phone ? .callout : .title)
|
||||
} icon: {
|
||||
Image(systemName: "cylinder.split.1x2")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.font(idiom == .phone ? .callout : .title)
|
||||
.frame(width: 35)
|
||||
}
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
.buttonBorderShape(.capsule)
|
||||
.controlSize(.large)
|
||||
Divider()
|
||||
}
|
||||
|
||||
List(files, id: \.self) { file in
|
||||
|
|
@ -62,20 +34,6 @@ struct AppData: View {
|
|||
Label {
|
||||
Text("Node Core Data Backup \(file.pathComponents[(idiom == .phone || idiom == .pad) ? 9 : 10])/\(file.lastPathComponent) - \(file.creationDate?.formatted() ?? "") - \(file.fileSizeString)")
|
||||
.swipeActions {
|
||||
Button(role: .none) {
|
||||
bleManager.disconnectPeripheral(reconnect: false)
|
||||
let container = NSPersistentContainer(name: "Meshtastic")
|
||||
do {
|
||||
try container.restorePersistentStore(from: file.absoluteURL)
|
||||
UserDefaults.preferredPeripheralId = ""
|
||||
UserDefaults.preferredPeripheralNum = Int(file.pathComponents[(idiom == .phone || idiom == .pad) ? 9 : 10]) ?? 0
|
||||
Logger.data.notice("🗂️ Restored a core data backup to backup/\(UserDefaults.preferredPeripheralNum, privacy: .public)")
|
||||
} catch {
|
||||
Logger.data.error("🗂️ Core data restore copy error: \(error, privacy: .public)")
|
||||
}
|
||||
} label: {
|
||||
Label("restore", systemImage: "arrow.counterclockwise")
|
||||
}
|
||||
Button(role: .destructive) {
|
||||
do {
|
||||
try FileManager.default.removeItem(at: file)
|
||||
|
|
|
|||
|
|
@ -178,7 +178,7 @@ struct AppLog: View {
|
|||
self.isExporting = false
|
||||
Logger.services.info("Application log download succeeded.")
|
||||
case .failure(let error):
|
||||
Logger.services.error("Application log download failed: \(error.localizedDescription)")
|
||||
Logger.services.error("Application log download failed: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -186,11 +186,11 @@ struct Channels: View {
|
|||
if channel.role != Channel.Role.disabled {
|
||||
do {
|
||||
try context.save()
|
||||
Logger.data.info("💾 Saved Channel: \(channel.settings.name)")
|
||||
Logger.data.info("💾 Saved Channel: \(channel.settings.name, privacy: .public)")
|
||||
} catch {
|
||||
context.rollback()
|
||||
let nsError = error as NSError
|
||||
Logger.data.error("Unresolved Core Data error in the channel editor. Error: \(nsError)")
|
||||
Logger.data.error("Unresolved Core Data error in the channel editor. Error: \(nsError, privacy: .public)")
|
||||
}
|
||||
} else {
|
||||
let objects = selectedChannel?.allPrivateMessages ?? []
|
||||
|
|
@ -203,11 +203,11 @@ struct Channels: View {
|
|||
context.delete(selectedChannel!)
|
||||
do {
|
||||
try context.save()
|
||||
Logger.data.info("💾 Deleted Channel: \(channel.settings.name)")
|
||||
Logger.data.info("💾 Deleted Channel: \(channel.settings.name, privacy: .public)")
|
||||
} catch {
|
||||
context.rollback()
|
||||
let nsError = error as NSError
|
||||
Logger.data.error("Unresolved Core Data error in the channel editor. Error: \(nsError)")
|
||||
Logger.data.error("Unresolved Core Data error in the channel editor. Error: \(nsError, privacy: .public)")
|
||||
}
|
||||
}
|
||||
let adminMessageId = bleManager.saveChannel(channel: channel, fromUser: node!.user!, toUser: node!.user!)
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ struct DeviceConfig: View {
|
|||
@State private var isPresentingFactoryResetConfirm = false
|
||||
@State var hasChanges = false
|
||||
@State var deviceRole = 0
|
||||
@State private var pendingDeviceRole = 0
|
||||
@State var buzzerGPIO = 0
|
||||
@State var buttonGPIO = 0
|
||||
@State var rebroadcastMode = 0
|
||||
|
|
@ -29,6 +30,10 @@ struct DeviceConfig: View {
|
|||
@State var ledHeartbeatEnabled = true
|
||||
@State var tripleClickAsAdHocPing = true
|
||||
@State var tzdef = ""
|
||||
|
||||
@State private var showRouterWarning = false
|
||||
@State private var confirmWarning = false
|
||||
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
|
|
@ -39,9 +44,37 @@ struct DeviceConfig: View {
|
|||
VStack(alignment: .leading) {
|
||||
Picker("Device Role", selection: $deviceRole ) {
|
||||
ForEach(DeviceRoles.allCases) { dr in
|
||||
Text(dr.name)
|
||||
Text(dr.name).tag(dr.rawValue as Int)
|
||||
}
|
||||
}
|
||||
.onChange(of: deviceRole) { oldValue, newValue in
|
||||
if !confirmWarning {
|
||||
if [2, 11].contains(newValue) {
|
||||
pendingDeviceRole = newValue
|
||||
deviceRole = oldValue // Reset selection until confirmed
|
||||
showRouterWarning = true
|
||||
}
|
||||
} else {
|
||||
confirmWarning = false
|
||||
}
|
||||
}
|
||||
.confirmationDialog(
|
||||
"Are you sure?",
|
||||
isPresented: $showRouterWarning,
|
||||
titleVisibility: .visible
|
||||
) {
|
||||
|
||||
Button("Confirm") {
|
||||
deviceRole = pendingDeviceRole
|
||||
pendingDeviceRole = 0
|
||||
confirmWarning = true
|
||||
}
|
||||
Button("Cancel", role: .cancel) {
|
||||
pendingDeviceRole = 0
|
||||
}
|
||||
} message: {
|
||||
Text("The Router roles are designed for high vantage locations like mountaintops and towers. This node needs to be able to have a good direct connection to most of the nodes on the network or else this will significantly hurt the network.")
|
||||
}
|
||||
Text(DeviceRoles(rawValue: deviceRole)?.description ?? "")
|
||||
.foregroundColor(.gray)
|
||||
.font(.callout)
|
||||
|
|
|
|||
|
|
@ -249,7 +249,6 @@ struct LoRaConfig: View {
|
|||
let expiration = node.sessionExpiration ?? Date()
|
||||
if expiration < Date() || node.loRaConfig == nil {
|
||||
Logger.mesh.info("⚙️ Empty or expired lora config requesting via PKI admin")
|
||||
|
||||
_ = bleManager.requestLoRaConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0)
|
||||
}
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -230,31 +230,88 @@ struct MQTTConfig: View {
|
|||
}
|
||||
.scrollDismissesKeyboard(.interactively)
|
||||
.disabled(self.bleManager.connectedPeripheral == nil || node?.mqttConfig == nil)
|
||||
}
|
||||
|
||||
SaveConfigButton(node: node, hasChanges: $hasChanges) {
|
||||
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1, context: context)
|
||||
if connectedNode != nil {
|
||||
var mqtt = ModuleConfig.MQTTConfig()
|
||||
mqtt.enabled = self.enabled
|
||||
mqtt.proxyToClientEnabled = self.proxyToClientEnabled
|
||||
mqtt.address = self.address
|
||||
mqtt.username = self.username
|
||||
mqtt.password = self.password
|
||||
mqtt.root = self.root
|
||||
mqtt.encryptionEnabled = self.encryptionEnabled
|
||||
mqtt.jsonEnabled = self.jsonEnabled
|
||||
mqtt.tlsEnabled = self.tlsEnabled
|
||||
mqtt.mapReportingEnabled = self.mapReportingEnabled
|
||||
mqtt.mapReportSettings.positionPrecision = UInt32(self.mapPositionPrecision)
|
||||
mqtt.mapReportSettings.publishIntervalSecs = UInt32(self.mapPublishIntervalSecs)
|
||||
let adminMessageId = bleManager.saveMQTTConfig(config: mqtt, fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0)
|
||||
if adminMessageId > 0 {
|
||||
// Should show a saved successfully alert once I know that to be true
|
||||
// for now just disable the button after a successful save
|
||||
hasChanges = false
|
||||
goBack()
|
||||
SaveConfigButton(node: node, hasChanges: $hasChanges) {
|
||||
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1, context: context)
|
||||
if connectedNode != nil {
|
||||
var mqtt = ModuleConfig.MQTTConfig()
|
||||
mqtt.enabled = self.enabled
|
||||
mqtt.proxyToClientEnabled = self.proxyToClientEnabled
|
||||
mqtt.address = self.address
|
||||
mqtt.username = self.username
|
||||
mqtt.password = self.password
|
||||
mqtt.root = self.root
|
||||
mqtt.encryptionEnabled = self.encryptionEnabled
|
||||
mqtt.jsonEnabled = self.jsonEnabled
|
||||
mqtt.tlsEnabled = self.tlsEnabled
|
||||
mqtt.mapReportingEnabled = self.mapReportingEnabled
|
||||
mqtt.mapReportSettings.positionPrecision = UInt32(self.mapPositionPrecision)
|
||||
mqtt.mapReportSettings.publishIntervalSecs = UInt32(self.mapPublishIntervalSecs)
|
||||
let adminMessageId = bleManager.saveMQTTConfig(config: mqtt, fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0)
|
||||
if adminMessageId > 0 {
|
||||
// Should show a saved successfully alert once I know that to be true
|
||||
// for now just disable the button after a successful save
|
||||
hasChanges = false
|
||||
goBack()
|
||||
}
|
||||
}
|
||||
}.onChange(of: enabled) { _, newEnabled in
|
||||
if newEnabled != node?.mqttConfig?.enabled { hasChanges = true }
|
||||
}
|
||||
.onChange(of: proxyToClientEnabled) { _, newProxyToClientEnabled in
|
||||
if newProxyToClientEnabled {
|
||||
jsonEnabled = false
|
||||
tlsEnabled = false
|
||||
}
|
||||
if newProxyToClientEnabled != node?.mqttConfig?.proxyToClientEnabled { hasChanges = true }
|
||||
}
|
||||
.onChange(of: address) { _, newAddress in
|
||||
if newAddress != node?.mqttConfig?.address ?? "" { hasChanges = true }
|
||||
}
|
||||
.onChange(of: username) { _, newUsername in
|
||||
if newUsername != node?.mqttConfig?.username ?? "" { hasChanges = true }
|
||||
}
|
||||
.onChange(of: password) { _, newPassword in
|
||||
if newPassword != node?.mqttConfig?.password ?? "" { hasChanges = true }
|
||||
}
|
||||
.onChange(of: root) { _, newRoot in
|
||||
if newRoot != node?.mqttConfig?.root ?? "" { hasChanges = true }
|
||||
}
|
||||
.onChange(of: selectedTopic) { _, newSelectedTopic in
|
||||
root = newSelectedTopic
|
||||
}
|
||||
.onChange(of: encryptionEnabled) { _, newEncryptionEnabled in
|
||||
if newEncryptionEnabled != node?.mqttConfig?.encryptionEnabled { hasChanges = true }
|
||||
}
|
||||
.onChange(of: jsonEnabled) { _, newJsonEnabled in
|
||||
if newJsonEnabled {
|
||||
proxyToClientEnabled = false
|
||||
}
|
||||
if newJsonEnabled != node?.mqttConfig?.jsonEnabled { hasChanges = true }
|
||||
}
|
||||
.onChange(of: tlsEnabled) { _, newTlsEnabled in
|
||||
if address.lowercased() == "mqtt.meshtastic.org" {
|
||||
tlsEnabled = false
|
||||
} else {
|
||||
if newTlsEnabled != node?.mqttConfig?.tlsEnabled { hasChanges = true }
|
||||
}
|
||||
}
|
||||
.onChange(of: mqttConnected) { _, newMqttConnected in
|
||||
if newMqttConnected == false {
|
||||
if bleManager.mqttProxyConnected {
|
||||
bleManager.mqttManager.disconnect()
|
||||
}
|
||||
} else {
|
||||
if !bleManager.mqttProxyConnected && node != nil {
|
||||
bleManager.mqttManager.connectFromConfigSettings(node: node!)
|
||||
}
|
||||
}
|
||||
}
|
||||
.onChange(of: mapReportingEnabled) { _, newMapReportingEnabled in
|
||||
if newMapReportingEnabled != node?.mqttConfig?.mapReportingEnabled { hasChanges = true }
|
||||
}
|
||||
.onChange(of: mapPublishIntervalSecs) { _, newMapPublishIntervalSecs in
|
||||
if newMapPublishIntervalSecs != node?.mqttConfig?.mapPublishIntervalSecs ?? -1 { hasChanges = true }
|
||||
}
|
||||
}
|
||||
.navigationTitle("mqtt.config")
|
||||
|
|
@ -267,64 +324,6 @@ struct MQTTConfig: View {
|
|||
)
|
||||
}
|
||||
)
|
||||
.onChange(of: enabled) { _, newEnabled in
|
||||
if newEnabled != node?.mqttConfig?.enabled { hasChanges = true }
|
||||
}
|
||||
.onChange(of: proxyToClientEnabled) { _, newProxyToClientEnabled in
|
||||
if newProxyToClientEnabled {
|
||||
jsonEnabled = false
|
||||
tlsEnabled = false
|
||||
}
|
||||
if newProxyToClientEnabled != node?.mqttConfig?.proxyToClientEnabled { hasChanges = true }
|
||||
}
|
||||
.onChange(of: address) { newAddress in
|
||||
if newAddress != node?.mqttConfig?.address ?? "" { hasChanges = true }
|
||||
}
|
||||
.onChange(of: username) { newUsername in
|
||||
if newUsername != node?.mqttConfig?.username ?? "" { hasChanges = true }
|
||||
}
|
||||
.onChange(of: password) { newPassword in
|
||||
if newPassword != node?.mqttConfig?.password ?? "" { hasChanges = true }
|
||||
}
|
||||
.onChange(of: root) { _, newRoot in
|
||||
if newRoot != node?.mqttConfig?.root ?? "" { hasChanges = true }
|
||||
}
|
||||
.onChange(of: selectedTopic) { _, newSelectedTopic in
|
||||
root = newSelectedTopic
|
||||
}
|
||||
.onChange(of: encryptionEnabled) { _, newEncryptionEnabled in
|
||||
if newEncryptionEnabled != node?.mqttConfig?.encryptionEnabled { hasChanges = true }
|
||||
}
|
||||
.onChange(of: jsonEnabled) { _, newJsonEnabled in
|
||||
if newJsonEnabled {
|
||||
proxyToClientEnabled = false
|
||||
}
|
||||
if newJsonEnabled != node?.mqttConfig?.jsonEnabled { hasChanges = true }
|
||||
}
|
||||
.onChange(of: tlsEnabled) { _, newTlsEnabled in
|
||||
if address.lowercased() == "mqtt.meshtastic.org" {
|
||||
tlsEnabled = false
|
||||
} else {
|
||||
if newTlsEnabled != node?.mqttConfig?.tlsEnabled { hasChanges = true }
|
||||
}
|
||||
}
|
||||
.onChange(of: mqttConnected) { _, newMqttConnected in
|
||||
if newMqttConnected == false {
|
||||
if bleManager.mqttProxyConnected {
|
||||
bleManager.mqttManager.disconnect()
|
||||
}
|
||||
} else {
|
||||
if !bleManager.mqttProxyConnected && node != nil {
|
||||
bleManager.mqttManager.connectFromConfigSettings(node: node!)
|
||||
}
|
||||
}
|
||||
}
|
||||
.onChange(of: mapReportingEnabled) { _, newMapReportingEnabled in
|
||||
if newMapReportingEnabled != node?.mqttConfig?.mapReportingEnabled { hasChanges = true }
|
||||
}
|
||||
.onChange(of: mapPublishIntervalSecs) { _, newMapPublishIntervalSecs in
|
||||
if newMapPublishIntervalSecs != node?.mqttConfig?.mapPublishIntervalSecs ?? -1 { hasChanges = true }
|
||||
}
|
||||
.onFirstAppear {
|
||||
// Need to request a MqttModuleConfig from the remote node before allowing changes
|
||||
if let connectedPeripheral = bleManager.connectedPeripheral, let node {
|
||||
|
|
@ -348,6 +347,7 @@ struct MQTTConfig: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func setMqttValues() {
|
||||
|
||||
nearbyTopics = []
|
||||
|
|
@ -357,7 +357,7 @@ struct MQTTConfig: View {
|
|||
defaultTopic = "msh/" + (region?.topic ?? "UNSET")
|
||||
geocoder.reverseGeocodeLocation(LocationsHandler.shared.locationsArray.first!, completionHandler: {(placemarks, error) in
|
||||
if let error {
|
||||
Logger.services.error("Failed to reverse geocode location: \(error.localizedDescription)")
|
||||
Logger.services.error("Failed to reverse geocode location: \(error.localizedDescription, privacy: .public)")
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ struct StoreForwardConfig: View {
|
|||
do {
|
||||
try context.save()
|
||||
} catch {
|
||||
Logger.mesh.error("Failed to save isServer: \(error.localizedDescription)")
|
||||
Logger.mesh.error("Failed to save isServer: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,66 +24,78 @@ struct NetworkConfig: View {
|
|||
@State var ntpServer = ""
|
||||
@State var ethEnabled = false
|
||||
@State var ethMode = 0
|
||||
@State var udpEnabled = false
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
Form {
|
||||
ConfigHeader(title: "Network", config: \.networkConfig, node: node, onAppear: setNetworkValues)
|
||||
|
||||
if node != nil && node?.metadata?.hasWifi ?? false {
|
||||
Section(header: Text("WiFi Options")) {
|
||||
if let node {
|
||||
if node.metadata?.hasWifi ?? false {
|
||||
Section(header: Text("WiFi Options")) {
|
||||
|
||||
Toggle(isOn: $wifiEnabled) {
|
||||
Label("enabled", systemImage: "wifi")
|
||||
Text("Enabling WiFi will disable the bluetooth connection to the app.")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
Toggle(isOn: $wifiEnabled) {
|
||||
Label("enabled", systemImage: "wifi")
|
||||
Text("Enabling WiFi will disable the bluetooth connection to the app.")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
|
||||
HStack {
|
||||
Label("ssid", systemImage: "network")
|
||||
TextField("ssid", text: $wifiSsid)
|
||||
.foregroundColor(.gray)
|
||||
.autocapitalization(.none)
|
||||
.disableAutocorrection(true)
|
||||
.onChange(of: wifiSsid) {
|
||||
var totalBytes = wifiSsid.utf8.count
|
||||
// Only mess with the value if it is too big
|
||||
while totalBytes > 32 {
|
||||
wifiSsid = String(wifiSsid.dropLast())
|
||||
totalBytes = wifiSsid.utf8.count
|
||||
HStack {
|
||||
Label("ssid", systemImage: "network")
|
||||
TextField("ssid", text: $wifiSsid)
|
||||
.foregroundColor(.gray)
|
||||
.autocapitalization(.none)
|
||||
.disableAutocorrection(true)
|
||||
.onChange(of: wifiSsid) {
|
||||
var totalBytes = wifiSsid.utf8.count
|
||||
// Only mess with the value if it is too big
|
||||
while totalBytes > 32 {
|
||||
wifiSsid = String(wifiSsid.dropLast())
|
||||
totalBytes = wifiSsid.utf8.count
|
||||
}
|
||||
hasChanges = true
|
||||
}
|
||||
hasChanges = true
|
||||
}
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
.keyboardType(.default)
|
||||
HStack {
|
||||
Label("password", systemImage: "wallet.pass")
|
||||
TextField("password", text: $wifiPsk)
|
||||
.foregroundColor(.gray)
|
||||
.autocapitalization(.none)
|
||||
.disableAutocorrection(true)
|
||||
.onChange(of: wifiPsk) {
|
||||
var totalBytes = wifiPsk.utf8.count
|
||||
// Only mess with the value if it is too big
|
||||
while totalBytes > 63 {
|
||||
wifiPsk = String(wifiPsk.dropLast())
|
||||
totalBytes = wifiPsk.utf8.count
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
.keyboardType(.default)
|
||||
HStack {
|
||||
Label("password", systemImage: "wallet.pass")
|
||||
TextField("password", text: $wifiPsk)
|
||||
.foregroundColor(.gray)
|
||||
.autocapitalization(.none)
|
||||
.disableAutocorrection(true)
|
||||
.onChange(of: wifiPsk) {
|
||||
var totalBytes = wifiPsk.utf8.count
|
||||
// Only mess with the value if it is too big
|
||||
while totalBytes > 63 {
|
||||
wifiPsk = String(wifiPsk.dropLast())
|
||||
totalBytes = wifiPsk.utf8.count
|
||||
}
|
||||
hasChanges = true
|
||||
}
|
||||
hasChanges = true
|
||||
}
|
||||
.foregroundColor(.gray)
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
.keyboardType(.default)
|
||||
}
|
||||
.keyboardType(.default)
|
||||
}
|
||||
}
|
||||
if node != nil && node?.metadata?.hasEthernet ?? false {
|
||||
Section(header: Text("Ethernet Options")) {
|
||||
Toggle(isOn: $ethEnabled) {
|
||||
Label("enabled", systemImage: "network")
|
||||
Text("Enabling Ethernet will disable the bluetooth connection to the app.")
|
||||
if node.metadata?.hasEthernet ?? false {
|
||||
Section(header: Text("Ethernet Options")) {
|
||||
Toggle(isOn: $ethEnabled) {
|
||||
Label("enabled", systemImage: "network")
|
||||
Text("Enabling Ethernet will disable the bluetooth connection to the app.")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
}
|
||||
}
|
||||
|
||||
if node.metadata?.hasEthernet ?? false || node.metadata?.hasWifi ?? false {
|
||||
Section(header: Text("UDP Broadcast")) {
|
||||
Toggle(isOn: $udpEnabled) {
|
||||
Label("enabled", systemImage: "point.3.connected.trianglepath.dotted")
|
||||
Text("Enable broadcasting packets via UDP over the local network.")
|
||||
}
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -98,6 +110,7 @@ struct NetworkConfig: View {
|
|||
network.wifiSsid = self.wifiSsid
|
||||
network.wifiPsk = self.wifiPsk
|
||||
network.ethEnabled = self.ethEnabled
|
||||
network.enabledProtocols = self.udpEnabled ? UInt32(Config.NetworkConfig.ProtocolFlags.udpBroadcast.rawValue) : UInt32(Config.NetworkConfig.ProtocolFlags.noBroadcast.rawValue)
|
||||
// network.addressMode = Config.NetworkConfig.AddressMode.dhcp
|
||||
|
||||
let adminMessageId = bleManager.saveNetworkConfig(config: network, fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0)
|
||||
|
|
@ -166,14 +179,30 @@ struct NetworkConfig: View {
|
|||
}
|
||||
.onChange(of: ethEnabled) { _, newEthEnabled in
|
||||
if newEthEnabled != node?.networkConfig?.ethEnabled { hasChanges = true }
|
||||
}.onChange(of: udpEnabled) {_, newUdpEnabled in
|
||||
if let netConfig = node?.networkConfig {
|
||||
let newValue: UInt32
|
||||
if newUdpEnabled {
|
||||
newValue = UInt32(netConfig.enabledProtocols) | UInt32(Config.NetworkConfig.ProtocolFlags.udpBroadcast.rawValue)
|
||||
} else {
|
||||
newValue = UInt32(netConfig.enabledProtocols) & ~UInt32(Config.NetworkConfig.ProtocolFlags.udpBroadcast.rawValue)
|
||||
}
|
||||
if netConfig.enabledProtocols != Int32(newValue) {
|
||||
netConfig.enabledProtocols = Int32(newValue)
|
||||
hasChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func setNetworkValues() {
|
||||
self.wifiEnabled = node?.networkConfig?.wifiEnabled ?? false
|
||||
self.wifiSsid = node?.networkConfig?.wifiSsid ?? ""
|
||||
self.wifiPsk = node?.networkConfig?.wifiPsk ?? ""
|
||||
self.wifiMode = Int(node?.networkConfig?.wifiMode ?? 0)
|
||||
self.ethEnabled = node?.networkConfig?.ethEnabled ?? false
|
||||
let enabledProtocols = UInt32(node?.networkConfig?.enabledProtocols ?? Int32(Config.NetworkConfig.ProtocolFlags.noBroadcast.rawValue))
|
||||
self.udpEnabled = enabledProtocols & UInt32(Config.NetworkConfig.ProtocolFlags.udpBroadcast.rawValue) != 0
|
||||
self.hasChanges = false
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -530,7 +530,7 @@ struct PositionConfig: View {
|
|||
} catch {
|
||||
context.rollback()
|
||||
let nsError = error as NSError
|
||||
Logger.data.error("Error Saving Position Config Entity \(nsError)")
|
||||
Logger.data.error("Error Saving Position Config Entity \(nsError, privacy: .public)")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -550,7 +550,7 @@ struct PositionConfig: View {
|
|||
} catch {
|
||||
context.rollback()
|
||||
let nsError = error as NSError
|
||||
Logger.data.error("Error Saving Position Config Entity \(nsError)")
|
||||
Logger.data.error("Error Saving Position Config Entity \(nsError, privacy: .public)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,9 +25,9 @@ struct SecurityConfig: View {
|
|||
@State var hasValidPublicKey: Bool = false
|
||||
@State var privateKey = ""
|
||||
@State var hasValidPrivateKey: Bool = false
|
||||
@State var adminKey = ""
|
||||
@State var adminKey2 = ""
|
||||
@State var adminKey3 = ""
|
||||
@State var adminKey: String = ""
|
||||
@State var adminKey2: String = ""
|
||||
@State var adminKey3: String = ""
|
||||
@State var hasValidAdminKey: Bool = true
|
||||
@State var hasValidAdminKey2: Bool = true
|
||||
@State var hasValidAdminKey3: Bool = true
|
||||
|
|
@ -259,9 +259,9 @@ struct SecurityConfig: View {
|
|||
func setSecurityValues() {
|
||||
self.publicKey = node?.securityConfig?.publicKey?.base64EncodedString() ?? ""
|
||||
self.privateKey = node?.securityConfig?.privateKey?.base64EncodedString() ?? ""
|
||||
self.adminKey = node?.securityConfig?.adminKey?.base64EncodedString() ?? ""
|
||||
self.adminKey2 = node?.securityConfig?.adminKey2?.base64EncodedString() ?? ""
|
||||
self.adminKey3 = node?.securityConfig?.adminKey3?.base64EncodedString() ?? ""
|
||||
self.adminKey = node?.securityConfig?.adminKey?.base64EncodedString(options: .lineLength64Characters) ?? ""
|
||||
self.adminKey2 = node?.securityConfig?.adminKey2?.base64EncodedString(options: .lineLength64Characters) ?? ""
|
||||
self.adminKey3 = node?.securityConfig?.adminKey3?.base64EncodedString(options: .lineLength64Characters) ?? ""
|
||||
self.isManaged = node?.securityConfig?.isManaged ?? false
|
||||
self.serialEnabled = node?.securityConfig?.serialEnabled ?? false
|
||||
self.debugLogApiEnabled = node?.securityConfig?.debugLogApiEnabled ?? false
|
||||
|
|
|
|||
|
|
@ -138,7 +138,7 @@ struct Firmware: View {
|
|||
.font(.callout)
|
||||
.padding(.bottom)
|
||||
} else {
|
||||
Text("OTA Updates are not supported on the this NRF Device.")
|
||||
Text("OTA Updates are not supported on this NRF Device.")
|
||||
.font(.title3)
|
||||
Link("Drag & Drop Firmware Update", destination: URL(string: "https://meshtastic.org/docs/getting-started/flashing-firmware/nrf52/drag-n-drop")!)
|
||||
.font(.callout)
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ class Api: ObservableObject {
|
|||
completion(deviceHardware)
|
||||
}
|
||||
} catch {
|
||||
Logger.services.error("JSON decode failure: \(error.localizedDescription)")
|
||||
Logger.services.error("JSON decode failure: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
@ -82,7 +82,7 @@ class Api: ObservableObject {
|
|||
completion(firmwareReleases)
|
||||
}
|
||||
} catch {
|
||||
Logger.services.error("JSON decode failure: \(error.localizedDescription)")
|
||||
Logger.services.error("JSON decode failure: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -188,7 +188,7 @@ struct RouteRecorder: View {
|
|||
} catch {
|
||||
context.rollback()
|
||||
let nsError = error as NSError
|
||||
Logger.data.error("Error Saving RouteEntity from the Route Recorder \(nsError)")
|
||||
Logger.data.error("Error Saving RouteEntity from the Route Recorder \(nsError, privacy: .public)")
|
||||
}
|
||||
} label: {
|
||||
Label("start", systemImage: "play")
|
||||
|
|
@ -246,7 +246,7 @@ struct RouteRecorder: View {
|
|||
} catch {
|
||||
context.rollback()
|
||||
let nsError = error as NSError
|
||||
Logger.data.error("Error Saving RouteEntity from the Route Recorder \(nsError)")
|
||||
Logger.data.error("Error Saving RouteEntity from the Route Recorder \(nsError, privacy: .public)")
|
||||
}
|
||||
isShowingDetails = false
|
||||
} label: {
|
||||
|
|
@ -298,11 +298,10 @@ struct RouteRecorder: View {
|
|||
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)")
|
||||
Logger.data.error("Error Saving LocationEntity from the Route Recorder \(nsError, privacy: .public)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ struct Routes: View {
|
|||
var latIndex = -1
|
||||
var longIndex = -1
|
||||
for index in headers!.indices {
|
||||
Logger.services.debug("\(index): \( headers![index])")
|
||||
Logger.services.debug("\(index, privacy: .public): \( headers![index], privacy: .public)")
|
||||
if headers![index].trimmingCharacters(in: .whitespaces) == "Latitude" {
|
||||
latIndex = index
|
||||
} else if headers![index].trimmingCharacters(in: .whitespaces) == "Longitude" {
|
||||
|
|
@ -94,7 +94,7 @@ struct Routes: View {
|
|||
do {
|
||||
try context.save()
|
||||
} catch let error as NSError {
|
||||
Logger.services.error("\(error.localizedDescription)")
|
||||
Logger.services.error("\(error.localizedDescription, privacy: .public)")
|
||||
isShowingBadFileAlert = true
|
||||
}
|
||||
} else {
|
||||
|
|
@ -103,11 +103,11 @@ struct Routes: View {
|
|||
|
||||
} catch {
|
||||
// TODO: deal with errors
|
||||
Logger.services.error("\(error.localizedDescription)")
|
||||
Logger.services.error("\(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
|
||||
} catch {
|
||||
Logger.services.error("CSV Import Error: \(error.localizedDescription)")
|
||||
Logger.services.error("CSV Import Error: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
List(routes, id: \.self, selection: $selectedRoute) { route in
|
||||
|
|
@ -151,7 +151,7 @@ struct Routes: View {
|
|||
do {
|
||||
try context.save()
|
||||
} catch let error as NSError {
|
||||
Logger.data.error("\(error.localizedDescription)")
|
||||
Logger.data.error("\(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
} label: {
|
||||
Label("delete", systemImage: "trash")
|
||||
|
|
@ -227,7 +227,7 @@ struct Routes: View {
|
|||
} catch {
|
||||
context.rollback()
|
||||
let nsError = error as NSError
|
||||
Logger.data.error("Error Saving RouteEntity from the Route Editor \(nsError)")
|
||||
Logger.data.error("Error Saving RouteEntity from the Route Editor \(nsError, privacy: .public)")
|
||||
}
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
|
|
@ -300,7 +300,7 @@ struct Routes: View {
|
|||
self.isExporting = false
|
||||
Logger.services.info("Route log download succeeded.")
|
||||
case .failure(let error):
|
||||
Logger.services.error("Route log download failed: \(error.localizedDescription).")
|
||||
Logger.services.error("Route log download failed: \(error.localizedDescription, privacy: .public).")
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
import SwiftUI
|
||||
import OSLog
|
||||
import TipKit
|
||||
import MeshtasticProtobufs
|
||||
|
||||
struct Settings: View {
|
||||
@Environment(\.managedObjectContext) var context
|
||||
|
|
@ -25,10 +26,21 @@ struct Settings: View {
|
|||
|
||||
@State private var selectedNode: Int = 0
|
||||
@State private var preferredNodeNum: Int = 0
|
||||
@State private var moduleOverride: Bool = false
|
||||
|
||||
@ObservedObject
|
||||
var router: Router
|
||||
|
||||
// MARK: Helper
|
||||
|
||||
private func isModuleSupported(_ module: ExcludedModules) -> Bool {
|
||||
return moduleOverride || Int(nodes.first(where: { $0.num == preferredNodeNum })?.metadata?.excludedModules ?? Int32.zero) & module.rawValue == 0
|
||||
}
|
||||
|
||||
private func isAnySupported(_ modules: [ExcludedModules]) -> Bool {
|
||||
return modules.map(isModuleSupported).contains(true)
|
||||
}
|
||||
|
||||
// MARK: Views
|
||||
|
||||
var radioConfigurationSection: some View {
|
||||
|
|
@ -153,57 +165,68 @@ struct Settings: View {
|
|||
}
|
||||
|
||||
var moduleConfigurationSection: some View {
|
||||
Section("module.configuration") {
|
||||
NavigationLink(value: SettingsNavigationState.ambientLighting) {
|
||||
Label {
|
||||
Text("Ambient Lighting")
|
||||
} icon: {
|
||||
Image(systemName: "light.max")
|
||||
Section {
|
||||
if isModuleSupported(.ambientlightingConfig) {
|
||||
NavigationLink(value: SettingsNavigationState.ambientLighting) {
|
||||
Label {
|
||||
Text("Ambient Lighting")
|
||||
} icon: {
|
||||
Image(systemName: "light.max")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NavigationLink(value: SettingsNavigationState.cannedMessages) {
|
||||
Label {
|
||||
Text("Canned Messages")
|
||||
} icon: {
|
||||
Image(systemName: "list.bullet.rectangle.fill")
|
||||
if isModuleSupported(.cannedmsgConfig) {
|
||||
NavigationLink(value: SettingsNavigationState.cannedMessages) {
|
||||
Label {
|
||||
Text("Canned Messages")
|
||||
} icon: {
|
||||
Image(systemName: "list.bullet.rectangle.fill")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NavigationLink(value: SettingsNavigationState.detectionSensor) {
|
||||
Label {
|
||||
Text("detection.sensor")
|
||||
} icon: {
|
||||
Image(systemName: "sensor")
|
||||
if isModuleSupported(.detectionsensorConfig) {
|
||||
NavigationLink(value: SettingsNavigationState.detectionSensor) {
|
||||
Label {
|
||||
Text("detection.sensor")
|
||||
} icon: {
|
||||
Image(systemName: "sensor")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NavigationLink(value: SettingsNavigationState.externalNotification) {
|
||||
Label {
|
||||
Text("external.notification")
|
||||
} icon: {
|
||||
Image(systemName: "megaphone")
|
||||
if isModuleSupported(.extnotifConfig) {
|
||||
NavigationLink(value: SettingsNavigationState.externalNotification) {
|
||||
Label {
|
||||
Text("external.notification")
|
||||
} icon: {
|
||||
Image(systemName: "megaphone")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NavigationLink(value: SettingsNavigationState.mqtt) {
|
||||
Label {
|
||||
Text("mqtt")
|
||||
} icon: {
|
||||
Image(systemName: "dot.radiowaves.up.forward")
|
||||
if isModuleSupported(.mqttConfig) {
|
||||
NavigationLink(value: SettingsNavigationState.mqtt) {
|
||||
Label {
|
||||
Text("mqtt")
|
||||
} icon: {
|
||||
Image(systemName: "dot.radiowaves.up.forward")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NavigationLink(value: SettingsNavigationState.rangeTest) {
|
||||
Label {
|
||||
Text("range.test")
|
||||
} icon: {
|
||||
Image(systemName: "point.3.connected.trianglepath.dotted")
|
||||
if isModuleSupported(.rangetestConfig) {
|
||||
NavigationLink(value: SettingsNavigationState.rangeTest) {
|
||||
Label {
|
||||
Text("range.test")
|
||||
} icon: {
|
||||
Image(systemName: "point.3.connected.trianglepath.dotted")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let node = nodes.first(where: { $0.num == preferredNodeNum }),
|
||||
node.metadata?.hasWifi ?? false {
|
||||
if isModuleSupported(.paxcounterConfig) {
|
||||
NavigationLink(value: SettingsNavigationState.paxCounter) {
|
||||
Label {
|
||||
Text("config.module.paxcounter.settings")
|
||||
|
|
@ -213,37 +236,61 @@ struct Settings: View {
|
|||
}
|
||||
}
|
||||
|
||||
NavigationLink(value: SettingsNavigationState.ringtone) {
|
||||
Label {
|
||||
Text("ringtone")
|
||||
} icon: {
|
||||
Image(systemName: "music.note.list")
|
||||
if isModuleSupported(.audioConfig) {
|
||||
NavigationLink(value: SettingsNavigationState.ringtone) {
|
||||
Label {
|
||||
Text("ringtone")
|
||||
} icon: {
|
||||
Image(systemName: "music.note.list")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NavigationLink(value: SettingsNavigationState.serial) {
|
||||
Label {
|
||||
Text("serial")
|
||||
} icon: {
|
||||
Image(systemName: "terminal")
|
||||
if isModuleSupported(.serialConfig) {
|
||||
NavigationLink(value: SettingsNavigationState.serial) {
|
||||
Label {
|
||||
Text("serial")
|
||||
} icon: {
|
||||
Image(systemName: "terminal")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NavigationLink(value: SettingsNavigationState.storeAndForward) {
|
||||
Label {
|
||||
Text("Store & Forward")
|
||||
} icon: {
|
||||
Image(systemName: "envelope.arrow.triangle.branch")
|
||||
if isModuleSupported(.storeforwardConfig) {
|
||||
NavigationLink(value: SettingsNavigationState.storeAndForward) {
|
||||
Label {
|
||||
Text("Store & Forward")
|
||||
} icon: {
|
||||
Image(systemName: "envelope.arrow.triangle.branch")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NavigationLink(value: SettingsNavigationState.telemetry) {
|
||||
Label {
|
||||
Text("telemetry")
|
||||
} icon: {
|
||||
Image(systemName: "chart.xyaxis.line")
|
||||
if isModuleSupported(.telemetryConfig) {
|
||||
NavigationLink(value: SettingsNavigationState.telemetry) {
|
||||
Label {
|
||||
Text("telemetry")
|
||||
} icon: {
|
||||
Image(systemName: "chart.xyaxis.line")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update this list with the modules that are shown above. If all are not supported
|
||||
// Then show a message.
|
||||
if !isAnySupported([.ambientlightingConfig, .cannedmsgConfig,
|
||||
.detectionsensorConfig, .extnotifConfig,
|
||||
.mqttConfig, .rangetestConfig, .paxcounterConfig,
|
||||
.audioConfig, .serialConfig, .storeforwardConfig,
|
||||
.telemetryConfig]) {
|
||||
Text("This node does not support any configurable modules.")
|
||||
}
|
||||
} header: {
|
||||
Text("module.configuration")
|
||||
} footer: {
|
||||
if moduleOverride {
|
||||
Text("Currently showing modules that may not be supported by this node.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -341,7 +388,7 @@ struct Settings: View {
|
|||
/// Connected Node
|
||||
if node.num == bleManager.connectedPeripheral?.num ?? 0 {
|
||||
Label {
|
||||
Text("BLE: \(node.user?.longName ?? "unknown".localized)")
|
||||
Text("BLE: \(node.user?.longName?.addingVariationSelectors ?? "unknown".localized)")
|
||||
} icon: {
|
||||
Image(systemName: "antenna.radiowaves.left.and.right")
|
||||
}
|
||||
|
|
@ -363,14 +410,14 @@ struct Settings: View {
|
|||
.tag(Int(node.num))
|
||||
} else if UserDefaults.enableAdministration && node.user?.pkiEncrypted ?? false {
|
||||
Label {
|
||||
Text("Request PKI Admin: \(node.user?.longName ?? "unknown".localized)")
|
||||
Text("Request PKI Admin: \(node.user?.longName?.addingVariationSelectors ?? "unknown".localized)")
|
||||
} icon: {
|
||||
Image(systemName: "rectangle.and.hand.point.up.left")
|
||||
}
|
||||
.tag(Int(node.num))
|
||||
} else if !UserDefaults.enableAdministration {
|
||||
Label {
|
||||
Text("Request Legacy Admin: \(node.user?.longName ?? "unknown".localized)")
|
||||
Text("Request Legacy Admin: \(node.user?.longName?.addingVariationSelectors ?? "unknown".localized)")
|
||||
} icon: {
|
||||
Image(systemName: "rectangle.and.hand.point.up.left")
|
||||
}
|
||||
|
|
@ -395,7 +442,7 @@ struct Settings: View {
|
|||
TipView(AdminChannelTip(), arrowEdge: .top)
|
||||
} else {
|
||||
if bleManager.connectedPeripheral != nil {
|
||||
Text("Connected Node \(node?.user?.longName ?? "unknown".localized)")
|
||||
Text("Connected Node \(node?.user?.longName?.addingVariationSelectors ?? "unknown".localized)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -497,7 +544,10 @@ struct Settings: View {
|
|||
}
|
||||
.navigationTitle("settings")
|
||||
.navigationBarItems(
|
||||
leading: MeshtasticLogo()
|
||||
leading: MeshtasticLogo().onLongPressGesture(minimumDuration: 1.0) {
|
||||
self.moduleOverride.toggle()
|
||||
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,12 +50,14 @@ struct UserConfig: View {
|
|||
|
||||
TextField("Long Name", text: $longName)
|
||||
.onChange(of: longName) {
|
||||
var totalBytes = longName.utf8.count
|
||||
var newValue = longName.withoutVariationSelectors
|
||||
var totalBytes = newValue.utf8.count
|
||||
// Only mess with the value if it is too big
|
||||
while totalBytes > (isLicensed ? 6 : 36) {
|
||||
longName = String(longName.dropLast())
|
||||
totalBytes = longName.utf8.count
|
||||
newValue = String(newValue.dropLast())
|
||||
totalBytes = newValue.utf8.count
|
||||
}
|
||||
longName = newValue
|
||||
}
|
||||
}
|
||||
.keyboardType(.default)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
// DO NOT EDIT.
|
||||
// swift-format-ignore-file
|
||||
// swiftlint:disable all
|
||||
//
|
||||
// Generated by the Swift generator plugin for the protocol buffer compiler.
|
||||
// Source: meshtastic/admin.proto
|
||||
|
|
@ -24,7 +25,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP
|
|||
/// This message is handled by the Admin module and is responsible for all settings/channel read/write operations.
|
||||
/// This message is used to do settings operations to both remote AND local nodes.
|
||||
/// (Prior to 1.2 these operations were done via special ToRadio operations)
|
||||
public struct AdminMessage {
|
||||
public struct AdminMessage: @unchecked Sendable {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
// methods supported on all messages.
|
||||
|
|
@ -261,6 +262,36 @@ public struct AdminMessage {
|
|||
set {payloadVariant = .setScale(newValue)}
|
||||
}
|
||||
|
||||
///
|
||||
/// Backup the node's preferences
|
||||
public var backupPreferences: AdminMessage.BackupLocation {
|
||||
get {
|
||||
if case .backupPreferences(let v)? = payloadVariant {return v}
|
||||
return .flash
|
||||
}
|
||||
set {payloadVariant = .backupPreferences(newValue)}
|
||||
}
|
||||
|
||||
///
|
||||
/// Restore the node's preferences
|
||||
public var restorePreferences: AdminMessage.BackupLocation {
|
||||
get {
|
||||
if case .restorePreferences(let v)? = payloadVariant {return v}
|
||||
return .flash
|
||||
}
|
||||
set {payloadVariant = .restorePreferences(newValue)}
|
||||
}
|
||||
|
||||
///
|
||||
/// Remove backups of the node's preferences
|
||||
public var removeBackupPreferences: AdminMessage.BackupLocation {
|
||||
get {
|
||||
if case .removeBackupPreferences(let v)? = payloadVariant {return v}
|
||||
return .flash
|
||||
}
|
||||
set {payloadVariant = .removeBackupPreferences(newValue)}
|
||||
}
|
||||
|
||||
///
|
||||
/// Set the owner for this node
|
||||
public var setOwner: User {
|
||||
|
|
@ -533,7 +564,7 @@ public struct AdminMessage {
|
|||
|
||||
///
|
||||
/// TODO: REPLACE
|
||||
public enum OneOf_PayloadVariant: Equatable {
|
||||
public enum OneOf_PayloadVariant: Equatable, Sendable {
|
||||
///
|
||||
/// Send the specified channel in the response to this message
|
||||
/// NOTE: This field is sent with the channel index + 1 (to ensure we never try to send 'zero' - which protobufs treats as not present)
|
||||
|
|
@ -603,6 +634,15 @@ public struct AdminMessage {
|
|||
/// Set zero and offset for scale chips
|
||||
case setScale(UInt32)
|
||||
///
|
||||
/// Backup the node's preferences
|
||||
case backupPreferences(AdminMessage.BackupLocation)
|
||||
///
|
||||
/// Restore the node's preferences
|
||||
case restorePreferences(AdminMessage.BackupLocation)
|
||||
///
|
||||
/// Remove backups of the node's preferences
|
||||
case removeBackupPreferences(AdminMessage.BackupLocation)
|
||||
///
|
||||
/// Set the owner for this node
|
||||
case setOwner(User)
|
||||
///
|
||||
|
|
@ -689,213 +729,11 @@ public struct AdminMessage {
|
|||
/// Tell the node to reset the nodedb.
|
||||
case nodedbReset(Int32)
|
||||
|
||||
#if !swift(>=4.1)
|
||||
public static func ==(lhs: AdminMessage.OneOf_PayloadVariant, rhs: AdminMessage.OneOf_PayloadVariant) -> Bool {
|
||||
// The use of inline closures is to circumvent an issue where the compiler
|
||||
// allocates stack space for every case branch when no optimizations are
|
||||
// enabled. https://github.com/apple/swift-protobuf/issues/1034
|
||||
switch (lhs, rhs) {
|
||||
case (.getChannelRequest, .getChannelRequest): return {
|
||||
guard case .getChannelRequest(let l) = lhs, case .getChannelRequest(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.getChannelResponse, .getChannelResponse): return {
|
||||
guard case .getChannelResponse(let l) = lhs, case .getChannelResponse(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.getOwnerRequest, .getOwnerRequest): return {
|
||||
guard case .getOwnerRequest(let l) = lhs, case .getOwnerRequest(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.getOwnerResponse, .getOwnerResponse): return {
|
||||
guard case .getOwnerResponse(let l) = lhs, case .getOwnerResponse(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.getConfigRequest, .getConfigRequest): return {
|
||||
guard case .getConfigRequest(let l) = lhs, case .getConfigRequest(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.getConfigResponse, .getConfigResponse): return {
|
||||
guard case .getConfigResponse(let l) = lhs, case .getConfigResponse(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.getModuleConfigRequest, .getModuleConfigRequest): return {
|
||||
guard case .getModuleConfigRequest(let l) = lhs, case .getModuleConfigRequest(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.getModuleConfigResponse, .getModuleConfigResponse): return {
|
||||
guard case .getModuleConfigResponse(let l) = lhs, case .getModuleConfigResponse(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.getCannedMessageModuleMessagesRequest, .getCannedMessageModuleMessagesRequest): return {
|
||||
guard case .getCannedMessageModuleMessagesRequest(let l) = lhs, case .getCannedMessageModuleMessagesRequest(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.getCannedMessageModuleMessagesResponse, .getCannedMessageModuleMessagesResponse): return {
|
||||
guard case .getCannedMessageModuleMessagesResponse(let l) = lhs, case .getCannedMessageModuleMessagesResponse(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.getDeviceMetadataRequest, .getDeviceMetadataRequest): return {
|
||||
guard case .getDeviceMetadataRequest(let l) = lhs, case .getDeviceMetadataRequest(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.getDeviceMetadataResponse, .getDeviceMetadataResponse): return {
|
||||
guard case .getDeviceMetadataResponse(let l) = lhs, case .getDeviceMetadataResponse(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.getRingtoneRequest, .getRingtoneRequest): return {
|
||||
guard case .getRingtoneRequest(let l) = lhs, case .getRingtoneRequest(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.getRingtoneResponse, .getRingtoneResponse): return {
|
||||
guard case .getRingtoneResponse(let l) = lhs, case .getRingtoneResponse(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.getDeviceConnectionStatusRequest, .getDeviceConnectionStatusRequest): return {
|
||||
guard case .getDeviceConnectionStatusRequest(let l) = lhs, case .getDeviceConnectionStatusRequest(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.getDeviceConnectionStatusResponse, .getDeviceConnectionStatusResponse): return {
|
||||
guard case .getDeviceConnectionStatusResponse(let l) = lhs, case .getDeviceConnectionStatusResponse(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.setHamMode, .setHamMode): return {
|
||||
guard case .setHamMode(let l) = lhs, case .setHamMode(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.getNodeRemoteHardwarePinsRequest, .getNodeRemoteHardwarePinsRequest): return {
|
||||
guard case .getNodeRemoteHardwarePinsRequest(let l) = lhs, case .getNodeRemoteHardwarePinsRequest(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.getNodeRemoteHardwarePinsResponse, .getNodeRemoteHardwarePinsResponse): return {
|
||||
guard case .getNodeRemoteHardwarePinsResponse(let l) = lhs, case .getNodeRemoteHardwarePinsResponse(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.enterDfuModeRequest, .enterDfuModeRequest): return {
|
||||
guard case .enterDfuModeRequest(let l) = lhs, case .enterDfuModeRequest(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.deleteFileRequest, .deleteFileRequest): return {
|
||||
guard case .deleteFileRequest(let l) = lhs, case .deleteFileRequest(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.setScale, .setScale): return {
|
||||
guard case .setScale(let l) = lhs, case .setScale(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.setOwner, .setOwner): return {
|
||||
guard case .setOwner(let l) = lhs, case .setOwner(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.setChannel, .setChannel): return {
|
||||
guard case .setChannel(let l) = lhs, case .setChannel(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.setConfig, .setConfig): return {
|
||||
guard case .setConfig(let l) = lhs, case .setConfig(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.setModuleConfig, .setModuleConfig): return {
|
||||
guard case .setModuleConfig(let l) = lhs, case .setModuleConfig(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.setCannedMessageModuleMessages, .setCannedMessageModuleMessages): return {
|
||||
guard case .setCannedMessageModuleMessages(let l) = lhs, case .setCannedMessageModuleMessages(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.setRingtoneMessage, .setRingtoneMessage): return {
|
||||
guard case .setRingtoneMessage(let l) = lhs, case .setRingtoneMessage(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.removeByNodenum, .removeByNodenum): return {
|
||||
guard case .removeByNodenum(let l) = lhs, case .removeByNodenum(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.setFavoriteNode, .setFavoriteNode): return {
|
||||
guard case .setFavoriteNode(let l) = lhs, case .setFavoriteNode(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.removeFavoriteNode, .removeFavoriteNode): return {
|
||||
guard case .removeFavoriteNode(let l) = lhs, case .removeFavoriteNode(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.setFixedPosition, .setFixedPosition): return {
|
||||
guard case .setFixedPosition(let l) = lhs, case .setFixedPosition(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.removeFixedPosition, .removeFixedPosition): return {
|
||||
guard case .removeFixedPosition(let l) = lhs, case .removeFixedPosition(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.setTimeOnly, .setTimeOnly): return {
|
||||
guard case .setTimeOnly(let l) = lhs, case .setTimeOnly(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.getUiConfigRequest, .getUiConfigRequest): return {
|
||||
guard case .getUiConfigRequest(let l) = lhs, case .getUiConfigRequest(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.getUiConfigResponse, .getUiConfigResponse): return {
|
||||
guard case .getUiConfigResponse(let l) = lhs, case .getUiConfigResponse(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.storeUiConfig, .storeUiConfig): return {
|
||||
guard case .storeUiConfig(let l) = lhs, case .storeUiConfig(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.setIgnoredNode, .setIgnoredNode): return {
|
||||
guard case .setIgnoredNode(let l) = lhs, case .setIgnoredNode(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.removeIgnoredNode, .removeIgnoredNode): return {
|
||||
guard case .removeIgnoredNode(let l) = lhs, case .removeIgnoredNode(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.beginEditSettings, .beginEditSettings): return {
|
||||
guard case .beginEditSettings(let l) = lhs, case .beginEditSettings(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.commitEditSettings, .commitEditSettings): return {
|
||||
guard case .commitEditSettings(let l) = lhs, case .commitEditSettings(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.factoryResetDevice, .factoryResetDevice): return {
|
||||
guard case .factoryResetDevice(let l) = lhs, case .factoryResetDevice(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.rebootOtaSeconds, .rebootOtaSeconds): return {
|
||||
guard case .rebootOtaSeconds(let l) = lhs, case .rebootOtaSeconds(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.exitSimulator, .exitSimulator): return {
|
||||
guard case .exitSimulator(let l) = lhs, case .exitSimulator(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.rebootSeconds, .rebootSeconds): return {
|
||||
guard case .rebootSeconds(let l) = lhs, case .rebootSeconds(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.shutdownSeconds, .shutdownSeconds): return {
|
||||
guard case .shutdownSeconds(let l) = lhs, case .shutdownSeconds(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.factoryResetConfig, .factoryResetConfig): return {
|
||||
guard case .factoryResetConfig(let l) = lhs, case .factoryResetConfig(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.nodedbReset, .nodedbReset): return {
|
||||
guard case .nodedbReset(let l) = lhs, case .nodedbReset(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
///
|
||||
/// TODO: REPLACE
|
||||
public enum ConfigType: SwiftProtobuf.Enum {
|
||||
public enum ConfigType: SwiftProtobuf.Enum, Swift.CaseIterable {
|
||||
public typealias RawValue = Int
|
||||
|
||||
///
|
||||
|
|
@ -929,6 +767,9 @@ public struct AdminMessage {
|
|||
///
|
||||
/// TODO: REPLACE
|
||||
case securityConfig // = 7
|
||||
|
||||
///
|
||||
/// Session key config
|
||||
case sessionkeyConfig // = 8
|
||||
|
||||
///
|
||||
|
|
@ -972,11 +813,25 @@ public struct AdminMessage {
|
|||
}
|
||||
}
|
||||
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
public static let allCases: [AdminMessage.ConfigType] = [
|
||||
.deviceConfig,
|
||||
.positionConfig,
|
||||
.powerConfig,
|
||||
.networkConfig,
|
||||
.displayConfig,
|
||||
.loraConfig,
|
||||
.bluetoothConfig,
|
||||
.securityConfig,
|
||||
.sessionkeyConfig,
|
||||
.deviceuiConfig,
|
||||
]
|
||||
|
||||
}
|
||||
|
||||
///
|
||||
/// TODO: REPLACE
|
||||
public enum ModuleConfigType: SwiftProtobuf.Enum {
|
||||
public enum ModuleConfigType: SwiftProtobuf.Enum, Swift.CaseIterable {
|
||||
public typealias RawValue = Int
|
||||
|
||||
///
|
||||
|
|
@ -1074,53 +929,71 @@ public struct AdminMessage {
|
|||
}
|
||||
}
|
||||
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
public static let allCases: [AdminMessage.ModuleConfigType] = [
|
||||
.mqttConfig,
|
||||
.serialConfig,
|
||||
.extnotifConfig,
|
||||
.storeforwardConfig,
|
||||
.rangetestConfig,
|
||||
.telemetryConfig,
|
||||
.cannedmsgConfig,
|
||||
.audioConfig,
|
||||
.remotehardwareConfig,
|
||||
.neighborinfoConfig,
|
||||
.ambientlightingConfig,
|
||||
.detectionsensorConfig,
|
||||
.paxcounterConfig,
|
||||
]
|
||||
|
||||
}
|
||||
|
||||
public enum BackupLocation: SwiftProtobuf.Enum, Swift.CaseIterable {
|
||||
public typealias RawValue = Int
|
||||
|
||||
///
|
||||
/// Backup to the internal flash
|
||||
case flash // = 0
|
||||
|
||||
///
|
||||
/// Backup to the SD card
|
||||
case sd // = 1
|
||||
case UNRECOGNIZED(Int)
|
||||
|
||||
public init() {
|
||||
self = .flash
|
||||
}
|
||||
|
||||
public init?(rawValue: Int) {
|
||||
switch rawValue {
|
||||
case 0: self = .flash
|
||||
case 1: self = .sd
|
||||
default: self = .UNRECOGNIZED(rawValue)
|
||||
}
|
||||
}
|
||||
|
||||
public var rawValue: Int {
|
||||
switch self {
|
||||
case .flash: return 0
|
||||
case .sd: return 1
|
||||
case .UNRECOGNIZED(let i): return i
|
||||
}
|
||||
}
|
||||
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
public static let allCases: [AdminMessage.BackupLocation] = [
|
||||
.flash,
|
||||
.sd,
|
||||
]
|
||||
|
||||
}
|
||||
|
||||
public init() {}
|
||||
}
|
||||
|
||||
#if swift(>=4.2)
|
||||
|
||||
extension AdminMessage.ConfigType: CaseIterable {
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
public static let allCases: [AdminMessage.ConfigType] = [
|
||||
.deviceConfig,
|
||||
.positionConfig,
|
||||
.powerConfig,
|
||||
.networkConfig,
|
||||
.displayConfig,
|
||||
.loraConfig,
|
||||
.bluetoothConfig,
|
||||
.securityConfig,
|
||||
.sessionkeyConfig,
|
||||
.deviceuiConfig,
|
||||
]
|
||||
}
|
||||
|
||||
extension AdminMessage.ModuleConfigType: CaseIterable {
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
public static let allCases: [AdminMessage.ModuleConfigType] = [
|
||||
.mqttConfig,
|
||||
.serialConfig,
|
||||
.extnotifConfig,
|
||||
.storeforwardConfig,
|
||||
.rangetestConfig,
|
||||
.telemetryConfig,
|
||||
.cannedmsgConfig,
|
||||
.audioConfig,
|
||||
.remotehardwareConfig,
|
||||
.neighborinfoConfig,
|
||||
.ambientlightingConfig,
|
||||
.detectionsensorConfig,
|
||||
.paxcounterConfig,
|
||||
]
|
||||
}
|
||||
|
||||
#endif // swift(>=4.2)
|
||||
|
||||
///
|
||||
/// Parameters for setting up Meshtastic for ameteur radio usage
|
||||
public struct HamParameters {
|
||||
public struct HamParameters: Sendable {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
// methods supported on all messages.
|
||||
|
|
@ -1150,7 +1023,7 @@ public struct HamParameters {
|
|||
|
||||
///
|
||||
/// Response envelope for node_remote_hardware_pins
|
||||
public struct NodeRemoteHardwarePinsResponse {
|
||||
public struct NodeRemoteHardwarePinsResponse: Sendable {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
// methods supported on all messages.
|
||||
|
|
@ -1164,15 +1037,6 @@ public struct NodeRemoteHardwarePinsResponse {
|
|||
public init() {}
|
||||
}
|
||||
|
||||
#if swift(>=5.5) && canImport(_Concurrency)
|
||||
extension AdminMessage: @unchecked Sendable {}
|
||||
extension AdminMessage.OneOf_PayloadVariant: @unchecked Sendable {}
|
||||
extension AdminMessage.ConfigType: @unchecked Sendable {}
|
||||
extension AdminMessage.ModuleConfigType: @unchecked Sendable {}
|
||||
extension HamParameters: @unchecked Sendable {}
|
||||
extension NodeRemoteHardwarePinsResponse: @unchecked Sendable {}
|
||||
#endif // swift(>=5.5) && canImport(_Concurrency)
|
||||
|
||||
// MARK: - Code below here is support for the SwiftProtobuf runtime.
|
||||
|
||||
fileprivate let _protobuf_package = "meshtastic"
|
||||
|
|
@ -1203,6 +1067,9 @@ extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat
|
|||
21: .standard(proto: "enter_dfu_mode_request"),
|
||||
22: .standard(proto: "delete_file_request"),
|
||||
23: .standard(proto: "set_scale"),
|
||||
24: .standard(proto: "backup_preferences"),
|
||||
25: .standard(proto: "restore_preferences"),
|
||||
26: .standard(proto: "remove_backup_preferences"),
|
||||
32: .standard(proto: "set_owner"),
|
||||
33: .standard(proto: "set_channel"),
|
||||
34: .standard(proto: "set_config"),
|
||||
|
|
@ -1453,6 +1320,30 @@ extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat
|
|||
self.payloadVariant = .setScale(v)
|
||||
}
|
||||
}()
|
||||
case 24: try {
|
||||
var v: AdminMessage.BackupLocation?
|
||||
try decoder.decodeSingularEnumField(value: &v)
|
||||
if let v = v {
|
||||
if self.payloadVariant != nil {try decoder.handleConflictingOneOf()}
|
||||
self.payloadVariant = .backupPreferences(v)
|
||||
}
|
||||
}()
|
||||
case 25: try {
|
||||
var v: AdminMessage.BackupLocation?
|
||||
try decoder.decodeSingularEnumField(value: &v)
|
||||
if let v = v {
|
||||
if self.payloadVariant != nil {try decoder.handleConflictingOneOf()}
|
||||
self.payloadVariant = .restorePreferences(v)
|
||||
}
|
||||
}()
|
||||
case 26: try {
|
||||
var v: AdminMessage.BackupLocation?
|
||||
try decoder.decodeSingularEnumField(value: &v)
|
||||
if let v = v {
|
||||
if self.payloadVariant != nil {try decoder.handleConflictingOneOf()}
|
||||
self.payloadVariant = .removeBackupPreferences(v)
|
||||
}
|
||||
}()
|
||||
case 32: try {
|
||||
var v: User?
|
||||
var hadOneofValue = false
|
||||
|
|
@ -1796,6 +1687,18 @@ extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat
|
|||
guard case .setScale(let v)? = self.payloadVariant else { preconditionFailure() }
|
||||
try visitor.visitSingularUInt32Field(value: v, fieldNumber: 23)
|
||||
}()
|
||||
case .backupPreferences?: try {
|
||||
guard case .backupPreferences(let v)? = self.payloadVariant else { preconditionFailure() }
|
||||
try visitor.visitSingularEnumField(value: v, fieldNumber: 24)
|
||||
}()
|
||||
case .restorePreferences?: try {
|
||||
guard case .restorePreferences(let v)? = self.payloadVariant else { preconditionFailure() }
|
||||
try visitor.visitSingularEnumField(value: v, fieldNumber: 25)
|
||||
}()
|
||||
case .removeBackupPreferences?: try {
|
||||
guard case .removeBackupPreferences(let v)? = self.payloadVariant else { preconditionFailure() }
|
||||
try visitor.visitSingularEnumField(value: v, fieldNumber: 26)
|
||||
}()
|
||||
case .setOwner?: try {
|
||||
guard case .setOwner(let v)? = self.payloadVariant else { preconditionFailure() }
|
||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 32)
|
||||
|
|
@ -1949,6 +1852,13 @@ extension AdminMessage.ModuleConfigType: SwiftProtobuf._ProtoNameProviding {
|
|||
]
|
||||
}
|
||||
|
||||
extension AdminMessage.BackupLocation: SwiftProtobuf._ProtoNameProviding {
|
||||
public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
||||
0: .same(proto: "FLASH"),
|
||||
1: .same(proto: "SD"),
|
||||
]
|
||||
}
|
||||
|
||||
extension HamParameters: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
||||
public static let protoMessageName: String = _protobuf_package + ".HamParameters"
|
||||
public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
||||
|
|
@ -1980,7 +1890,7 @@ extension HamParameters: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementa
|
|||
if self.txPower != 0 {
|
||||
try visitor.visitSingularInt32Field(value: self.txPower, fieldNumber: 2)
|
||||
}
|
||||
if self.frequency != 0 {
|
||||
if self.frequency.bitPattern != 0 {
|
||||
try visitor.visitSingularFloatField(value: self.frequency, fieldNumber: 3)
|
||||
}
|
||||
if !self.shortName.isEmpty {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
// DO NOT EDIT.
|
||||
// swift-format-ignore-file
|
||||
// swiftlint:disable all
|
||||
//
|
||||
// Generated by the Swift generator plugin for the protocol buffer compiler.
|
||||
// Source: meshtastic/apponly.proto
|
||||
|
|
@ -7,7 +8,6 @@
|
|||
// For information on using the generated types, please see the documentation:
|
||||
// https://github.com/apple/swift-protobuf/
|
||||
|
||||
import Foundation
|
||||
import SwiftProtobuf
|
||||
|
||||
// If the compiler emits an error on this type, it is because this file
|
||||
|
|
@ -26,7 +26,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP
|
|||
/// any SECONDARY channels.
|
||||
/// No DISABLED channels are included.
|
||||
/// This abstraction is used only on the the 'app side' of the world (ie python, javascript and android etc) to show a group of Channels as a (long) URL
|
||||
public struct ChannelSet {
|
||||
public struct ChannelSet: Sendable {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
// methods supported on all messages.
|
||||
|
|
@ -53,10 +53,6 @@ public struct ChannelSet {
|
|||
fileprivate var _loraConfig: Config.LoRaConfig? = nil
|
||||
}
|
||||
|
||||
#if swift(>=5.5) && canImport(_Concurrency)
|
||||
extension ChannelSet: @unchecked Sendable {}
|
||||
#endif // swift(>=5.5) && canImport(_Concurrency)
|
||||
|
||||
// MARK: - Code below here is support for the SwiftProtobuf runtime.
|
||||
|
||||
fileprivate let _protobuf_package = "meshtastic"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
// DO NOT EDIT.
|
||||
// swift-format-ignore-file
|
||||
// swiftlint:disable all
|
||||
//
|
||||
// Generated by the Swift generator plugin for the protocol buffer compiler.
|
||||
// Source: meshtastic/atak.proto
|
||||
|
|
@ -20,7 +21,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP
|
|||
typealias Version = _2
|
||||
}
|
||||
|
||||
public enum Team: SwiftProtobuf.Enum {
|
||||
public enum Team: SwiftProtobuf.Enum, Swift.CaseIterable {
|
||||
public typealias RawValue = Int
|
||||
|
||||
///
|
||||
|
|
@ -130,11 +131,6 @@ public enum Team: SwiftProtobuf.Enum {
|
|||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#if swift(>=4.2)
|
||||
|
||||
extension Team: CaseIterable {
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
public static let allCases: [Team] = [
|
||||
.unspecifedColor,
|
||||
|
|
@ -153,13 +149,12 @@ extension Team: CaseIterable {
|
|||
.darkGreen,
|
||||
.brown,
|
||||
]
|
||||
}
|
||||
|
||||
#endif // swift(>=4.2)
|
||||
}
|
||||
|
||||
///
|
||||
/// Role of the group member
|
||||
public enum MemberRole: SwiftProtobuf.Enum {
|
||||
public enum MemberRole: SwiftProtobuf.Enum, Swift.CaseIterable {
|
||||
public typealias RawValue = Int
|
||||
|
||||
///
|
||||
|
|
@ -233,11 +228,6 @@ public enum MemberRole: SwiftProtobuf.Enum {
|
|||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#if swift(>=4.2)
|
||||
|
||||
extension MemberRole: CaseIterable {
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
public static let allCases: [MemberRole] = [
|
||||
.unspecifed,
|
||||
|
|
@ -250,13 +240,12 @@ extension MemberRole: CaseIterable {
|
|||
.rto,
|
||||
.k9,
|
||||
]
|
||||
}
|
||||
|
||||
#endif // swift(>=4.2)
|
||||
}
|
||||
|
||||
///
|
||||
/// Packets for the official ATAK Plugin
|
||||
public struct TAKPacket {
|
||||
public struct TAKPacket: @unchecked Sendable {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
// methods supported on all messages.
|
||||
|
|
@ -337,7 +326,7 @@ public struct TAKPacket {
|
|||
|
||||
///
|
||||
/// The payload of the packet
|
||||
public enum OneOf_PayloadVariant: Equatable {
|
||||
public enum OneOf_PayloadVariant: Equatable, @unchecked Sendable {
|
||||
///
|
||||
/// TAK position report
|
||||
case pli(PLI)
|
||||
|
|
@ -349,28 +338,6 @@ public struct TAKPacket {
|
|||
/// May be compressed / truncated by the sender (EUD)
|
||||
case detail(Data)
|
||||
|
||||
#if !swift(>=4.1)
|
||||
public static func ==(lhs: TAKPacket.OneOf_PayloadVariant, rhs: TAKPacket.OneOf_PayloadVariant) -> Bool {
|
||||
// The use of inline closures is to circumvent an issue where the compiler
|
||||
// allocates stack space for every case branch when no optimizations are
|
||||
// enabled. https://github.com/apple/swift-protobuf/issues/1034
|
||||
switch (lhs, rhs) {
|
||||
case (.pli, .pli): return {
|
||||
guard case .pli(let l) = lhs, case .pli(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.chat, .chat): return {
|
||||
guard case .chat(let l) = lhs, case .chat(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.detail, .detail): return {
|
||||
guard case .detail(let l) = lhs, case .detail(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public init() {}
|
||||
|
|
@ -382,7 +349,7 @@ public struct TAKPacket {
|
|||
|
||||
///
|
||||
/// ATAK GeoChat message
|
||||
public struct GeoChat {
|
||||
public struct GeoChat: Sendable {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
// methods supported on all messages.
|
||||
|
|
@ -424,7 +391,7 @@ public struct GeoChat {
|
|||
///
|
||||
/// ATAK Group
|
||||
/// <__group role='Team Member' name='Cyan'/>
|
||||
public struct Group {
|
||||
public struct Group: Sendable {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
// methods supported on all messages.
|
||||
|
|
@ -446,7 +413,7 @@ public struct Group {
|
|||
///
|
||||
/// ATAK EUD Status
|
||||
/// <status battery='100' />
|
||||
public struct Status {
|
||||
public struct Status: Sendable {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
// methods supported on all messages.
|
||||
|
|
@ -463,7 +430,7 @@ public struct Status {
|
|||
///
|
||||
/// ATAK Contact
|
||||
/// <contact endpoint='0.0.0.0:4242:tcp' phone='+12345678' callsign='FALKE'/>
|
||||
public struct Contact {
|
||||
public struct Contact: Sendable {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
// methods supported on all messages.
|
||||
|
|
@ -483,7 +450,7 @@ public struct Contact {
|
|||
|
||||
///
|
||||
/// Position Location Information from ATAK
|
||||
public struct PLI {
|
||||
public struct PLI: Sendable {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
// methods supported on all messages.
|
||||
|
|
@ -515,18 +482,6 @@ public struct PLI {
|
|||
public init() {}
|
||||
}
|
||||
|
||||
#if swift(>=5.5) && canImport(_Concurrency)
|
||||
extension Team: @unchecked Sendable {}
|
||||
extension MemberRole: @unchecked Sendable {}
|
||||
extension TAKPacket: @unchecked Sendable {}
|
||||
extension TAKPacket.OneOf_PayloadVariant: @unchecked Sendable {}
|
||||
extension GeoChat: @unchecked Sendable {}
|
||||
extension Group: @unchecked Sendable {}
|
||||
extension Status: @unchecked Sendable {}
|
||||
extension Contact: @unchecked Sendable {}
|
||||
extension PLI: @unchecked Sendable {}
|
||||
#endif // swift(>=5.5) && canImport(_Concurrency)
|
||||
|
||||
// MARK: - Code below here is support for the SwiftProtobuf runtime.
|
||||
|
||||
fileprivate let _protobuf_package = "meshtastic"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
// DO NOT EDIT.
|
||||
// swift-format-ignore-file
|
||||
// swiftlint:disable all
|
||||
//
|
||||
// Generated by the Swift generator plugin for the protocol buffer compiler.
|
||||
// Source: meshtastic/cannedmessages.proto
|
||||
|
|
@ -7,7 +8,6 @@
|
|||
// For information on using the generated types, please see the documentation:
|
||||
// https://github.com/apple/swift-protobuf/
|
||||
|
||||
import Foundation
|
||||
import SwiftProtobuf
|
||||
|
||||
// If the compiler emits an error on this type, it is because this file
|
||||
|
|
@ -22,7 +22,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP
|
|||
|
||||
///
|
||||
/// Canned message module configuration.
|
||||
public struct CannedMessageModuleConfig {
|
||||
public struct CannedMessageModuleConfig: Sendable {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
// methods supported on all messages.
|
||||
|
|
@ -36,10 +36,6 @@ public struct CannedMessageModuleConfig {
|
|||
public init() {}
|
||||
}
|
||||
|
||||
#if swift(>=5.5) && canImport(_Concurrency)
|
||||
extension CannedMessageModuleConfig: @unchecked Sendable {}
|
||||
#endif // swift(>=5.5) && canImport(_Concurrency)
|
||||
|
||||
// MARK: - Code below here is support for the SwiftProtobuf runtime.
|
||||
|
||||
fileprivate let _protobuf_package = "meshtastic"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
// DO NOT EDIT.
|
||||
// swift-format-ignore-file
|
||||
// swiftlint:disable all
|
||||
//
|
||||
// Generated by the Swift generator plugin for the protocol buffer compiler.
|
||||
// Source: meshtastic/channel.proto
|
||||
|
|
@ -36,13 +37,15 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP
|
|||
/// FIXME: Add description of multi-channel support and how primary vs secondary channels are used.
|
||||
/// FIXME: explain how apps use channels for security.
|
||||
/// explain how remote settings and remote gpio are managed as an example
|
||||
public struct ChannelSettings {
|
||||
public struct ChannelSettings: @unchecked Sendable {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
// methods supported on all messages.
|
||||
|
||||
///
|
||||
/// Deprecated in favor of LoraConfig.channel_num
|
||||
///
|
||||
/// NOTE: This field was marked as deprecated in the .proto file.
|
||||
public var channelNum: UInt32 = 0
|
||||
|
||||
///
|
||||
|
|
@ -111,7 +114,7 @@ public struct ChannelSettings {
|
|||
|
||||
///
|
||||
/// This message is specifically for modules to store per-channel configuration data.
|
||||
public struct ModuleSettings {
|
||||
public struct ModuleSettings: Sendable {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
// methods supported on all messages.
|
||||
|
|
@ -132,7 +135,7 @@ public struct ModuleSettings {
|
|||
|
||||
///
|
||||
/// A pair of a channel number, mode and the (sharable) settings for that channel
|
||||
public struct Channel {
|
||||
public struct Channel: Sendable {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
// methods supported on all messages.
|
||||
|
|
@ -170,7 +173,7 @@ public struct Channel {
|
|||
/// cross band routing as needed.
|
||||
/// If a device has only a single radio (the common case) only one channel can be PRIMARY at a time
|
||||
/// (but any number of SECONDARY channels can't be sent received on that common frequency)
|
||||
public enum Role: SwiftProtobuf.Enum {
|
||||
public enum Role: SwiftProtobuf.Enum, Swift.CaseIterable {
|
||||
public typealias RawValue = Int
|
||||
|
||||
///
|
||||
|
|
@ -209,6 +212,13 @@ public struct Channel {
|
|||
}
|
||||
}
|
||||
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
public static let allCases: [Channel.Role] = [
|
||||
.disabled,
|
||||
.primary,
|
||||
.secondary,
|
||||
]
|
||||
|
||||
}
|
||||
|
||||
public init() {}
|
||||
|
|
@ -216,26 +226,6 @@ public struct Channel {
|
|||
fileprivate var _settings: ChannelSettings? = nil
|
||||
}
|
||||
|
||||
#if swift(>=4.2)
|
||||
|
||||
extension Channel.Role: CaseIterable {
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
public static let allCases: [Channel.Role] = [
|
||||
.disabled,
|
||||
.primary,
|
||||
.secondary,
|
||||
]
|
||||
}
|
||||
|
||||
#endif // swift(>=4.2)
|
||||
|
||||
#if swift(>=5.5) && canImport(_Concurrency)
|
||||
extension ChannelSettings: @unchecked Sendable {}
|
||||
extension ModuleSettings: @unchecked Sendable {}
|
||||
extension Channel: @unchecked Sendable {}
|
||||
extension Channel.Role: @unchecked Sendable {}
|
||||
#endif // swift(>=5.5) && canImport(_Concurrency)
|
||||
|
||||
// MARK: - Code below here is support for the SwiftProtobuf runtime.
|
||||
|
||||
fileprivate let _protobuf_package = "meshtastic"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
// DO NOT EDIT.
|
||||
// swift-format-ignore-file
|
||||
// swiftlint:disable all
|
||||
//
|
||||
// Generated by the Swift generator plugin for the protocol buffer compiler.
|
||||
// Source: meshtastic/clientonly.proto
|
||||
|
|
@ -7,7 +8,6 @@
|
|||
// For information on using the generated types, please see the documentation:
|
||||
// https://github.com/apple/swift-protobuf/
|
||||
|
||||
import Foundation
|
||||
import SwiftProtobuf
|
||||
|
||||
// If the compiler emits an error on this type, it is because this file
|
||||
|
|
@ -23,7 +23,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP
|
|||
///
|
||||
/// This abstraction is used to contain any configuration for provisioning a node on any client.
|
||||
/// It is useful for importing and exporting configurations.
|
||||
public struct DeviceProfile {
|
||||
public struct DeviceProfile: Sendable {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
// methods supported on all messages.
|
||||
|
|
@ -130,10 +130,6 @@ public struct DeviceProfile {
|
|||
fileprivate var _cannedMessages: String? = nil
|
||||
}
|
||||
|
||||
#if swift(>=5.5) && canImport(_Concurrency)
|
||||
extension DeviceProfile: @unchecked Sendable {}
|
||||
#endif // swift(>=5.5) && canImport(_Concurrency)
|
||||
|
||||
// MARK: - Code below here is support for the SwiftProtobuf runtime.
|
||||
|
||||
fileprivate let _protobuf_package = "meshtastic"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
// DO NOT EDIT.
|
||||
// swift-format-ignore-file
|
||||
// swiftlint:disable all
|
||||
//
|
||||
// Generated by the Swift generator plugin for the protocol buffer compiler.
|
||||
// Source: meshtastic/config.proto
|
||||
|
|
@ -20,7 +21,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP
|
|||
typealias Version = _2
|
||||
}
|
||||
|
||||
public struct Config {
|
||||
public struct Config: Sendable {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
// methods supported on all messages.
|
||||
|
|
@ -113,7 +114,7 @@ public struct Config {
|
|||
|
||||
///
|
||||
/// Payload Variant
|
||||
public enum OneOf_PayloadVariant: Equatable {
|
||||
public enum OneOf_PayloadVariant: Equatable, Sendable {
|
||||
case device(Config.DeviceConfig)
|
||||
case position(Config.PositionConfig)
|
||||
case power(Config.PowerConfig)
|
||||
|
|
@ -125,61 +126,11 @@ public struct Config {
|
|||
case sessionkey(Config.SessionkeyConfig)
|
||||
case deviceUi(DeviceUIConfig)
|
||||
|
||||
#if !swift(>=4.1)
|
||||
public static func ==(lhs: Config.OneOf_PayloadVariant, rhs: Config.OneOf_PayloadVariant) -> Bool {
|
||||
// The use of inline closures is to circumvent an issue where the compiler
|
||||
// allocates stack space for every case branch when no optimizations are
|
||||
// enabled. https://github.com/apple/swift-protobuf/issues/1034
|
||||
switch (lhs, rhs) {
|
||||
case (.device, .device): return {
|
||||
guard case .device(let l) = lhs, case .device(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.position, .position): return {
|
||||
guard case .position(let l) = lhs, case .position(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.power, .power): return {
|
||||
guard case .power(let l) = lhs, case .power(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.network, .network): return {
|
||||
guard case .network(let l) = lhs, case .network(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.display, .display): return {
|
||||
guard case .display(let l) = lhs, case .display(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.lora, .lora): return {
|
||||
guard case .lora(let l) = lhs, case .lora(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.bluetooth, .bluetooth): return {
|
||||
guard case .bluetooth(let l) = lhs, case .bluetooth(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.security, .security): return {
|
||||
guard case .security(let l) = lhs, case .security(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.sessionkey, .sessionkey): return {
|
||||
guard case .sessionkey(let l) = lhs, case .sessionkey(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.deviceUi, .deviceUi): return {
|
||||
guard case .deviceUi(let l) = lhs, case .deviceUi(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
///
|
||||
/// Configuration
|
||||
public struct DeviceConfig {
|
||||
public struct DeviceConfig: Sendable {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
// methods supported on all messages.
|
||||
|
|
@ -191,6 +142,8 @@ public struct Config {
|
|||
///
|
||||
/// Disabling this will disable the SerialConsole by not initilizing the StreamAPI
|
||||
/// Moved to SecurityConfig
|
||||
///
|
||||
/// NOTE: This field was marked as deprecated in the .proto file.
|
||||
public var serialEnabled: Bool = false
|
||||
|
||||
///
|
||||
|
|
@ -220,6 +173,8 @@ public struct Config {
|
|||
/// If true, device is considered to be "managed" by a mesh administrator
|
||||
/// Clients should then limit available configuration and administrative options inside the user interface
|
||||
/// Moved to SecurityConfig
|
||||
///
|
||||
/// NOTE: This field was marked as deprecated in the .proto file.
|
||||
public var isManaged: Bool = false
|
||||
|
||||
///
|
||||
|
|
@ -231,14 +186,14 @@ public struct Config {
|
|||
public var tzdef: String = String()
|
||||
|
||||
///
|
||||
/// If true, disable the default blinking LED (LED_PIN) behavior on the device
|
||||
/// If true, disable the default blinking LED (LED_PIN) behavior on the device
|
||||
public var ledHeartbeatDisabled: Bool = false
|
||||
|
||||
public var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
///
|
||||
/// Defines the device's role on the Mesh network
|
||||
public enum Role: SwiftProtobuf.Enum {
|
||||
public enum Role: SwiftProtobuf.Enum, Swift.CaseIterable {
|
||||
public typealias RawValue = Int
|
||||
|
||||
///
|
||||
|
|
@ -256,6 +211,8 @@ public struct Config {
|
|||
/// The wifi radio and the oled screen will be put to sleep.
|
||||
/// This mode may still potentially have higher power usage due to it's preference in message rebroadcasting on the mesh.
|
||||
case router // = 2
|
||||
|
||||
/// NOTE: This enum value was marked as deprecated in the .proto file
|
||||
case routerClient // = 3
|
||||
|
||||
///
|
||||
|
|
@ -356,11 +313,27 @@ public struct Config {
|
|||
}
|
||||
}
|
||||
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
public static let allCases: [Config.DeviceConfig.Role] = [
|
||||
.client,
|
||||
.clientMute,
|
||||
.router,
|
||||
.routerClient,
|
||||
.repeater,
|
||||
.tracker,
|
||||
.sensor,
|
||||
.tak,
|
||||
.clientHidden,
|
||||
.lostAndFound,
|
||||
.takTracker,
|
||||
.routerLate,
|
||||
]
|
||||
|
||||
}
|
||||
|
||||
///
|
||||
/// Defines the device's behavior for how messages are rebroadcast
|
||||
public enum RebroadcastMode: SwiftProtobuf.Enum {
|
||||
public enum RebroadcastMode: SwiftProtobuf.Enum, Swift.CaseIterable {
|
||||
public typealias RawValue = Int
|
||||
|
||||
///
|
||||
|
|
@ -421,6 +394,16 @@ public struct Config {
|
|||
}
|
||||
}
|
||||
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
public static let allCases: [Config.DeviceConfig.RebroadcastMode] = [
|
||||
.all,
|
||||
.allSkipDecoding,
|
||||
.localOnly,
|
||||
.knownOnly,
|
||||
.none,
|
||||
.corePortnumsOnly,
|
||||
]
|
||||
|
||||
}
|
||||
|
||||
public init() {}
|
||||
|
|
@ -428,7 +411,7 @@ public struct Config {
|
|||
|
||||
///
|
||||
/// Position Config
|
||||
public struct PositionConfig {
|
||||
public struct PositionConfig: Sendable {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
// methods supported on all messages.
|
||||
|
|
@ -450,6 +433,8 @@ public struct Config {
|
|||
|
||||
///
|
||||
/// Is GPS enabled for this node?
|
||||
///
|
||||
/// NOTE: This field was marked as deprecated in the .proto file.
|
||||
public var gpsEnabled: Bool = false
|
||||
|
||||
///
|
||||
|
|
@ -460,6 +445,8 @@ public struct Config {
|
|||
|
||||
///
|
||||
/// Deprecated in favor of using smart / regular broadcast intervals as implicit attempt time
|
||||
///
|
||||
/// NOTE: This field was marked as deprecated in the .proto file.
|
||||
public var gpsAttemptTime: UInt32 = 0
|
||||
|
||||
///
|
||||
|
|
@ -500,7 +487,7 @@ public struct Config {
|
|||
/// are always included (also time if GPS-synced)
|
||||
/// NOTE: the more fields are included, the larger the message will be -
|
||||
/// leading to longer airtime and a higher risk of packet loss
|
||||
public enum PositionFlags: SwiftProtobuf.Enum {
|
||||
public enum PositionFlags: SwiftProtobuf.Enum, Swift.CaseIterable {
|
||||
public typealias RawValue = Int
|
||||
|
||||
///
|
||||
|
|
@ -590,9 +577,24 @@ public struct Config {
|
|||
}
|
||||
}
|
||||
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
public static let allCases: [Config.PositionConfig.PositionFlags] = [
|
||||
.unset,
|
||||
.altitude,
|
||||
.altitudeMsl,
|
||||
.geoidalSeparation,
|
||||
.dop,
|
||||
.hvdop,
|
||||
.satinview,
|
||||
.seqNo,
|
||||
.timestamp,
|
||||
.heading,
|
||||
.speed,
|
||||
]
|
||||
|
||||
}
|
||||
|
||||
public enum GpsMode: SwiftProtobuf.Enum {
|
||||
public enum GpsMode: SwiftProtobuf.Enum, Swift.CaseIterable {
|
||||
public typealias RawValue = Int
|
||||
|
||||
///
|
||||
|
|
@ -630,6 +632,13 @@ public struct Config {
|
|||
}
|
||||
}
|
||||
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
public static let allCases: [Config.PositionConfig.GpsMode] = [
|
||||
.disabled,
|
||||
.enabled,
|
||||
.notPresent,
|
||||
]
|
||||
|
||||
}
|
||||
|
||||
public init() {}
|
||||
|
|
@ -638,13 +647,13 @@ public struct Config {
|
|||
///
|
||||
/// Power Config\
|
||||
/// See [Power Config](/docs/settings/config/power) for additional power config details.
|
||||
public struct PowerConfig {
|
||||
public struct PowerConfig: Sendable {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
// methods supported on all messages.
|
||||
|
||||
///
|
||||
/// Description: Will sleep everything as much as possible, for the tracker and sensor role this will also include the lora radio.
|
||||
/// 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.
|
||||
/// Technical Details: Works for ESP32 devices and NRF52 devices in the Sensor or Tracker roles
|
||||
public var isPowerSaving: Bool = false
|
||||
|
|
@ -698,7 +707,7 @@ public struct Config {
|
|||
|
||||
///
|
||||
/// Network Config
|
||||
public struct NetworkConfig {
|
||||
public struct NetworkConfig: Sendable {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
// methods supported on all messages.
|
||||
|
|
@ -717,7 +726,7 @@ public struct Config {
|
|||
public var wifiPsk: String = String()
|
||||
|
||||
///
|
||||
/// NTP server to use if WiFi is conneced, defaults to `0.pool.ntp.org`
|
||||
/// NTP server to use if WiFi is conneced, defaults to `meshtastic.pool.ntp.org`
|
||||
public var ntpServer: String = String()
|
||||
|
||||
///
|
||||
|
|
@ -749,7 +758,7 @@ public struct Config {
|
|||
|
||||
public var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
public enum AddressMode: SwiftProtobuf.Enum {
|
||||
public enum AddressMode: SwiftProtobuf.Enum, Swift.CaseIterable {
|
||||
public typealias RawValue = Int
|
||||
|
||||
///
|
||||
|
|
@ -781,11 +790,17 @@ public struct Config {
|
|||
}
|
||||
}
|
||||
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
public static let allCases: [Config.NetworkConfig.AddressMode] = [
|
||||
.dhcp,
|
||||
.static,
|
||||
]
|
||||
|
||||
}
|
||||
|
||||
///
|
||||
/// Available flags auxiliary network protocols
|
||||
public enum ProtocolFlags: SwiftProtobuf.Enum {
|
||||
public enum ProtocolFlags: SwiftProtobuf.Enum, Swift.CaseIterable {
|
||||
public typealias RawValue = Int
|
||||
|
||||
///
|
||||
|
|
@ -817,9 +832,15 @@ public struct Config {
|
|||
}
|
||||
}
|
||||
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
public static let allCases: [Config.NetworkConfig.ProtocolFlags] = [
|
||||
.noBroadcast,
|
||||
.udpBroadcast,
|
||||
]
|
||||
|
||||
}
|
||||
|
||||
public struct IpV4Config {
|
||||
public struct IpV4Config: Sendable {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
// methods supported on all messages.
|
||||
|
|
@ -852,7 +873,7 @@ public struct Config {
|
|||
|
||||
///
|
||||
/// Display Config
|
||||
public struct DisplayConfig {
|
||||
public struct DisplayConfig: Sendable {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
// methods supported on all messages.
|
||||
|
|
@ -913,7 +934,7 @@ public struct Config {
|
|||
|
||||
///
|
||||
/// How the GPS coordinates are displayed on the OLED screen.
|
||||
public enum GpsCoordinateFormat: SwiftProtobuf.Enum {
|
||||
public enum GpsCoordinateFormat: SwiftProtobuf.Enum, Swift.CaseIterable {
|
||||
public typealias RawValue = Int
|
||||
|
||||
///
|
||||
|
|
@ -976,11 +997,21 @@ public struct Config {
|
|||
}
|
||||
}
|
||||
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
public static let allCases: [Config.DisplayConfig.GpsCoordinateFormat] = [
|
||||
.dec,
|
||||
.dms,
|
||||
.utm,
|
||||
.mgrs,
|
||||
.olc,
|
||||
.osgr,
|
||||
]
|
||||
|
||||
}
|
||||
|
||||
///
|
||||
/// Unit display preference
|
||||
public enum DisplayUnits: SwiftProtobuf.Enum {
|
||||
public enum DisplayUnits: SwiftProtobuf.Enum, Swift.CaseIterable {
|
||||
public typealias RawValue = Int
|
||||
|
||||
///
|
||||
|
|
@ -1012,11 +1043,17 @@ public struct Config {
|
|||
}
|
||||
}
|
||||
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
public static let allCases: [Config.DisplayConfig.DisplayUnits] = [
|
||||
.metric,
|
||||
.imperial,
|
||||
]
|
||||
|
||||
}
|
||||
|
||||
///
|
||||
/// Override OLED outo detect with this if it fails.
|
||||
public enum OledType: SwiftProtobuf.Enum {
|
||||
public enum OledType: SwiftProtobuf.Enum, Swift.CaseIterable {
|
||||
public typealias RawValue = Int
|
||||
|
||||
///
|
||||
|
|
@ -1060,9 +1097,17 @@ public struct Config {
|
|||
}
|
||||
}
|
||||
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
public static let allCases: [Config.DisplayConfig.OledType] = [
|
||||
.oledAuto,
|
||||
.oledSsd1306,
|
||||
.oledSh1106,
|
||||
.oledSh1107,
|
||||
]
|
||||
|
||||
}
|
||||
|
||||
public enum DisplayMode: SwiftProtobuf.Enum {
|
||||
public enum DisplayMode: SwiftProtobuf.Enum, Swift.CaseIterable {
|
||||
public typealias RawValue = Int
|
||||
|
||||
///
|
||||
|
|
@ -1106,9 +1151,17 @@ public struct Config {
|
|||
}
|
||||
}
|
||||
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
public static let allCases: [Config.DisplayConfig.DisplayMode] = [
|
||||
.default,
|
||||
.twocolor,
|
||||
.inverted,
|
||||
.color,
|
||||
]
|
||||
|
||||
}
|
||||
|
||||
public enum CompassOrientation: SwiftProtobuf.Enum {
|
||||
public enum CompassOrientation: SwiftProtobuf.Enum, Swift.CaseIterable {
|
||||
public typealias RawValue = Int
|
||||
|
||||
///
|
||||
|
|
@ -1176,6 +1229,18 @@ public struct Config {
|
|||
}
|
||||
}
|
||||
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
public static let allCases: [Config.DisplayConfig.CompassOrientation] = [
|
||||
.degrees0,
|
||||
.degrees90,
|
||||
.degrees180,
|
||||
.degrees270,
|
||||
.degrees0Inverted,
|
||||
.degrees90Inverted,
|
||||
.degrees180Inverted,
|
||||
.degrees270Inverted,
|
||||
]
|
||||
|
||||
}
|
||||
|
||||
public init() {}
|
||||
|
|
@ -1183,7 +1248,7 @@ public struct Config {
|
|||
|
||||
///
|
||||
/// Lora Config
|
||||
public struct LoRaConfig {
|
||||
public struct LoRaConfig: @unchecked Sendable {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
// methods supported on all messages.
|
||||
|
|
@ -1347,7 +1412,7 @@ public struct Config {
|
|||
|
||||
public var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
public enum RegionCode: SwiftProtobuf.Enum {
|
||||
public enum RegionCode: SwiftProtobuf.Enum, Swift.CaseIterable {
|
||||
public typealias RawValue = Int
|
||||
|
||||
///
|
||||
|
|
@ -1499,12 +1564,38 @@ public struct Config {
|
|||
}
|
||||
}
|
||||
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
public static let allCases: [Config.LoRaConfig.RegionCode] = [
|
||||
.unset,
|
||||
.us,
|
||||
.eu433,
|
||||
.eu868,
|
||||
.cn,
|
||||
.jp,
|
||||
.anz,
|
||||
.kr,
|
||||
.tw,
|
||||
.ru,
|
||||
.in,
|
||||
.nz865,
|
||||
.th,
|
||||
.lora24,
|
||||
.ua433,
|
||||
.ua868,
|
||||
.my433,
|
||||
.my919,
|
||||
.sg923,
|
||||
.ph433,
|
||||
.ph868,
|
||||
.ph915,
|
||||
]
|
||||
|
||||
}
|
||||
|
||||
///
|
||||
/// Standard predefined channel settings
|
||||
/// Note: these mappings must match ModemPreset Choice in the device code.
|
||||
public enum ModemPreset: SwiftProtobuf.Enum {
|
||||
public enum ModemPreset: SwiftProtobuf.Enum, Swift.CaseIterable {
|
||||
public typealias RawValue = Int
|
||||
|
||||
///
|
||||
|
|
@ -1518,6 +1609,8 @@ public struct Config {
|
|||
///
|
||||
/// Very Long Range - Slow
|
||||
/// Deprecated in 2.5: Works only with txco and is unusably slow
|
||||
///
|
||||
/// NOTE: This enum value was marked as deprecated in the .proto file
|
||||
case veryLongSlow // = 2
|
||||
|
||||
///
|
||||
|
|
@ -1581,6 +1674,19 @@ public struct Config {
|
|||
}
|
||||
}
|
||||
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
public static let allCases: [Config.LoRaConfig.ModemPreset] = [
|
||||
.longFast,
|
||||
.longSlow,
|
||||
.veryLongSlow,
|
||||
.mediumSlow,
|
||||
.mediumFast,
|
||||
.shortSlow,
|
||||
.shortFast,
|
||||
.longModerate,
|
||||
.shortTurbo,
|
||||
]
|
||||
|
||||
}
|
||||
|
||||
public init() {}
|
||||
|
|
@ -1588,7 +1694,7 @@ public struct Config {
|
|||
fileprivate var _storage = _StorageClass.defaultInstance
|
||||
}
|
||||
|
||||
public struct BluetoothConfig {
|
||||
public struct BluetoothConfig: Sendable {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
// methods supported on all messages.
|
||||
|
|
@ -1607,7 +1713,7 @@ public struct Config {
|
|||
|
||||
public var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
public enum PairingMode: SwiftProtobuf.Enum {
|
||||
public enum PairingMode: SwiftProtobuf.Enum, Swift.CaseIterable {
|
||||
public typealias RawValue = Int
|
||||
|
||||
///
|
||||
|
|
@ -1645,12 +1751,19 @@ public struct Config {
|
|||
}
|
||||
}
|
||||
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
public static let allCases: [Config.BluetoothConfig.PairingMode] = [
|
||||
.randomPin,
|
||||
.fixedPin,
|
||||
.noPin,
|
||||
]
|
||||
|
||||
}
|
||||
|
||||
public init() {}
|
||||
}
|
||||
|
||||
public struct SecurityConfig {
|
||||
public struct SecurityConfig: @unchecked Sendable {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
// methods supported on all messages.
|
||||
|
|
@ -1694,7 +1807,7 @@ public struct Config {
|
|||
|
||||
///
|
||||
/// Blank config request, strictly for getting the session key
|
||||
public struct SessionkeyConfig {
|
||||
public struct SessionkeyConfig: Sendable {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
// methods supported on all messages.
|
||||
|
|
@ -1707,217 +1820,6 @@ public struct Config {
|
|||
public init() {}
|
||||
}
|
||||
|
||||
#if swift(>=4.2)
|
||||
|
||||
extension Config.DeviceConfig.Role: CaseIterable {
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
public static let allCases: [Config.DeviceConfig.Role] = [
|
||||
.client,
|
||||
.clientMute,
|
||||
.router,
|
||||
.routerClient,
|
||||
.repeater,
|
||||
.tracker,
|
||||
.sensor,
|
||||
.tak,
|
||||
.clientHidden,
|
||||
.lostAndFound,
|
||||
.takTracker,
|
||||
.routerLate,
|
||||
]
|
||||
}
|
||||
|
||||
extension Config.DeviceConfig.RebroadcastMode: CaseIterable {
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
public static let allCases: [Config.DeviceConfig.RebroadcastMode] = [
|
||||
.all,
|
||||
.allSkipDecoding,
|
||||
.localOnly,
|
||||
.knownOnly,
|
||||
.none,
|
||||
.corePortnumsOnly,
|
||||
]
|
||||
}
|
||||
|
||||
extension Config.PositionConfig.PositionFlags: CaseIterable {
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
public static let allCases: [Config.PositionConfig.PositionFlags] = [
|
||||
.unset,
|
||||
.altitude,
|
||||
.altitudeMsl,
|
||||
.geoidalSeparation,
|
||||
.dop,
|
||||
.hvdop,
|
||||
.satinview,
|
||||
.seqNo,
|
||||
.timestamp,
|
||||
.heading,
|
||||
.speed,
|
||||
]
|
||||
}
|
||||
|
||||
extension Config.PositionConfig.GpsMode: CaseIterable {
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
public static let allCases: [Config.PositionConfig.GpsMode] = [
|
||||
.disabled,
|
||||
.enabled,
|
||||
.notPresent,
|
||||
]
|
||||
}
|
||||
|
||||
extension Config.NetworkConfig.AddressMode: CaseIterable {
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
public static let allCases: [Config.NetworkConfig.AddressMode] = [
|
||||
.dhcp,
|
||||
.static,
|
||||
]
|
||||
}
|
||||
|
||||
extension Config.NetworkConfig.ProtocolFlags: CaseIterable {
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
public static let allCases: [Config.NetworkConfig.ProtocolFlags] = [
|
||||
.noBroadcast,
|
||||
.udpBroadcast,
|
||||
]
|
||||
}
|
||||
|
||||
extension Config.DisplayConfig.GpsCoordinateFormat: CaseIterable {
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
public static let allCases: [Config.DisplayConfig.GpsCoordinateFormat] = [
|
||||
.dec,
|
||||
.dms,
|
||||
.utm,
|
||||
.mgrs,
|
||||
.olc,
|
||||
.osgr,
|
||||
]
|
||||
}
|
||||
|
||||
extension Config.DisplayConfig.DisplayUnits: CaseIterable {
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
public static let allCases: [Config.DisplayConfig.DisplayUnits] = [
|
||||
.metric,
|
||||
.imperial,
|
||||
]
|
||||
}
|
||||
|
||||
extension Config.DisplayConfig.OledType: CaseIterable {
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
public static let allCases: [Config.DisplayConfig.OledType] = [
|
||||
.oledAuto,
|
||||
.oledSsd1306,
|
||||
.oledSh1106,
|
||||
.oledSh1107,
|
||||
]
|
||||
}
|
||||
|
||||
extension Config.DisplayConfig.DisplayMode: CaseIterable {
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
public static let allCases: [Config.DisplayConfig.DisplayMode] = [
|
||||
.default,
|
||||
.twocolor,
|
||||
.inverted,
|
||||
.color,
|
||||
]
|
||||
}
|
||||
|
||||
extension Config.DisplayConfig.CompassOrientation: CaseIterable {
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
public static let allCases: [Config.DisplayConfig.CompassOrientation] = [
|
||||
.degrees0,
|
||||
.degrees90,
|
||||
.degrees180,
|
||||
.degrees270,
|
||||
.degrees0Inverted,
|
||||
.degrees90Inverted,
|
||||
.degrees180Inverted,
|
||||
.degrees270Inverted,
|
||||
]
|
||||
}
|
||||
|
||||
extension Config.LoRaConfig.RegionCode: CaseIterable {
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
public static let allCases: [Config.LoRaConfig.RegionCode] = [
|
||||
.unset,
|
||||
.us,
|
||||
.eu433,
|
||||
.eu868,
|
||||
.cn,
|
||||
.jp,
|
||||
.anz,
|
||||
.kr,
|
||||
.tw,
|
||||
.ru,
|
||||
.in,
|
||||
.nz865,
|
||||
.th,
|
||||
.lora24,
|
||||
.ua433,
|
||||
.ua868,
|
||||
.my433,
|
||||
.my919,
|
||||
.sg923,
|
||||
.ph433,
|
||||
.ph868,
|
||||
.ph915,
|
||||
]
|
||||
}
|
||||
|
||||
extension Config.LoRaConfig.ModemPreset: CaseIterable {
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
public static let allCases: [Config.LoRaConfig.ModemPreset] = [
|
||||
.longFast,
|
||||
.longSlow,
|
||||
.veryLongSlow,
|
||||
.mediumSlow,
|
||||
.mediumFast,
|
||||
.shortSlow,
|
||||
.shortFast,
|
||||
.longModerate,
|
||||
.shortTurbo,
|
||||
]
|
||||
}
|
||||
|
||||
extension Config.BluetoothConfig.PairingMode: CaseIterable {
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
public static let allCases: [Config.BluetoothConfig.PairingMode] = [
|
||||
.randomPin,
|
||||
.fixedPin,
|
||||
.noPin,
|
||||
]
|
||||
}
|
||||
|
||||
#endif // swift(>=4.2)
|
||||
|
||||
#if swift(>=5.5) && canImport(_Concurrency)
|
||||
extension Config: @unchecked Sendable {}
|
||||
extension Config.OneOf_PayloadVariant: @unchecked Sendable {}
|
||||
extension Config.DeviceConfig: @unchecked Sendable {}
|
||||
extension Config.DeviceConfig.Role: @unchecked Sendable {}
|
||||
extension Config.DeviceConfig.RebroadcastMode: @unchecked Sendable {}
|
||||
extension Config.PositionConfig: @unchecked Sendable {}
|
||||
extension Config.PositionConfig.PositionFlags: @unchecked Sendable {}
|
||||
extension Config.PositionConfig.GpsMode: @unchecked Sendable {}
|
||||
extension Config.PowerConfig: @unchecked Sendable {}
|
||||
extension Config.NetworkConfig: @unchecked Sendable {}
|
||||
extension Config.NetworkConfig.AddressMode: @unchecked Sendable {}
|
||||
extension Config.NetworkConfig.ProtocolFlags: @unchecked Sendable {}
|
||||
extension Config.NetworkConfig.IpV4Config: @unchecked Sendable {}
|
||||
extension Config.DisplayConfig: @unchecked Sendable {}
|
||||
extension Config.DisplayConfig.GpsCoordinateFormat: @unchecked Sendable {}
|
||||
extension Config.DisplayConfig.DisplayUnits: @unchecked Sendable {}
|
||||
extension Config.DisplayConfig.OledType: @unchecked Sendable {}
|
||||
extension Config.DisplayConfig.DisplayMode: @unchecked Sendable {}
|
||||
extension Config.DisplayConfig.CompassOrientation: @unchecked Sendable {}
|
||||
extension Config.LoRaConfig: @unchecked Sendable {}
|
||||
extension Config.LoRaConfig.RegionCode: @unchecked Sendable {}
|
||||
extension Config.LoRaConfig.ModemPreset: @unchecked Sendable {}
|
||||
extension Config.BluetoothConfig: @unchecked Sendable {}
|
||||
extension Config.BluetoothConfig.PairingMode: @unchecked Sendable {}
|
||||
extension Config.SecurityConfig: @unchecked Sendable {}
|
||||
extension Config.SessionkeyConfig: @unchecked Sendable {}
|
||||
#endif // swift(>=5.5) && canImport(_Concurrency)
|
||||
|
||||
// MARK: - Code below here is support for the SwiftProtobuf runtime.
|
||||
|
||||
fileprivate let _protobuf_package = "meshtastic"
|
||||
|
|
@ -2425,7 +2327,7 @@ extension Config.PowerConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImple
|
|||
if self.onBatteryShutdownAfterSecs != 0 {
|
||||
try visitor.visitSingularUInt32Field(value: self.onBatteryShutdownAfterSecs, fieldNumber: 2)
|
||||
}
|
||||
if self.adcMultiplierOverride != 0 {
|
||||
if self.adcMultiplierOverride.bitPattern != 0 {
|
||||
try visitor.visitSingularFloatField(value: self.adcMultiplierOverride, fieldNumber: 3)
|
||||
}
|
||||
if self.waitBluetoothSecs != 0 {
|
||||
|
|
@ -2892,7 +2794,7 @@ extension Config.LoRaConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem
|
|||
if _storage._codingRate != 0 {
|
||||
try visitor.visitSingularUInt32Field(value: _storage._codingRate, fieldNumber: 5)
|
||||
}
|
||||
if _storage._frequencyOffset != 0 {
|
||||
if _storage._frequencyOffset.bitPattern != 0 {
|
||||
try visitor.visitSingularFloatField(value: _storage._frequencyOffset, fieldNumber: 6)
|
||||
}
|
||||
if _storage._region != .unset {
|
||||
|
|
@ -2916,7 +2818,7 @@ extension Config.LoRaConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem
|
|||
if _storage._sx126XRxBoostedGain != false {
|
||||
try visitor.visitSingularBoolField(value: _storage._sx126XRxBoostedGain, fieldNumber: 13)
|
||||
}
|
||||
if _storage._overrideFrequency != 0 {
|
||||
if _storage._overrideFrequency.bitPattern != 0 {
|
||||
try visitor.visitSingularFloatField(value: _storage._overrideFrequency, fieldNumber: 14)
|
||||
}
|
||||
if _storage._paFanDisabled != false {
|
||||
|
|
@ -3133,8 +3035,8 @@ extension Config.SessionkeyConfig: SwiftProtobuf.Message, SwiftProtobuf._Message
|
|||
public static let _protobuf_nameMap = SwiftProtobuf._NameMap()
|
||||
|
||||
public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||
while let _ = try decoder.nextFieldNumber() {
|
||||
}
|
||||
// Load everything into unknown fields
|
||||
while try decoder.nextFieldNumber() != nil {}
|
||||
}
|
||||
|
||||
public func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
// DO NOT EDIT.
|
||||
// swift-format-ignore-file
|
||||
// swiftlint:disable all
|
||||
//
|
||||
// Generated by the Swift generator plugin for the protocol buffer compiler.
|
||||
// Source: meshtastic/connection_status.proto
|
||||
|
|
@ -7,7 +8,6 @@
|
|||
// For information on using the generated types, please see the documentation:
|
||||
// https://github.com/apple/swift-protobuf/
|
||||
|
||||
import Foundation
|
||||
import SwiftProtobuf
|
||||
|
||||
// If the compiler emits an error on this type, it is because this file
|
||||
|
|
@ -20,7 +20,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP
|
|||
typealias Version = _2
|
||||
}
|
||||
|
||||
public struct DeviceConnectionStatus {
|
||||
public struct DeviceConnectionStatus: Sendable {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
// methods supported on all messages.
|
||||
|
|
@ -81,7 +81,7 @@ public struct DeviceConnectionStatus {
|
|||
|
||||
///
|
||||
/// WiFi connection status
|
||||
public struct WifiConnectionStatus {
|
||||
public struct WifiConnectionStatus: Sendable {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
// methods supported on all messages.
|
||||
|
|
@ -114,7 +114,7 @@ public struct WifiConnectionStatus {
|
|||
|
||||
///
|
||||
/// Ethernet connection status
|
||||
public struct EthernetConnectionStatus {
|
||||
public struct EthernetConnectionStatus: Sendable {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
// methods supported on all messages.
|
||||
|
|
@ -139,7 +139,7 @@ public struct EthernetConnectionStatus {
|
|||
|
||||
///
|
||||
/// Ethernet or WiFi connection status
|
||||
public struct NetworkConnectionStatus {
|
||||
public struct NetworkConnectionStatus: Sendable {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
// methods supported on all messages.
|
||||
|
|
@ -167,7 +167,7 @@ public struct NetworkConnectionStatus {
|
|||
|
||||
///
|
||||
/// Bluetooth connection status
|
||||
public struct BluetoothConnectionStatus {
|
||||
public struct BluetoothConnectionStatus: Sendable {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
// methods supported on all messages.
|
||||
|
|
@ -191,7 +191,7 @@ public struct BluetoothConnectionStatus {
|
|||
|
||||
///
|
||||
/// Serial connection status
|
||||
public struct SerialConnectionStatus {
|
||||
public struct SerialConnectionStatus: Sendable {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
// methods supported on all messages.
|
||||
|
|
@ -209,15 +209,6 @@ public struct SerialConnectionStatus {
|
|||
public init() {}
|
||||
}
|
||||
|
||||
#if swift(>=5.5) && canImport(_Concurrency)
|
||||
extension DeviceConnectionStatus: @unchecked Sendable {}
|
||||
extension WifiConnectionStatus: @unchecked Sendable {}
|
||||
extension EthernetConnectionStatus: @unchecked Sendable {}
|
||||
extension NetworkConnectionStatus: @unchecked Sendable {}
|
||||
extension BluetoothConnectionStatus: @unchecked Sendable {}
|
||||
extension SerialConnectionStatus: @unchecked Sendable {}
|
||||
#endif // swift(>=5.5) && canImport(_Concurrency)
|
||||
|
||||
// MARK: - Code below here is support for the SwiftProtobuf runtime.
|
||||
|
||||
fileprivate let _protobuf_package = "meshtastic"
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue