diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 788e7a2e..b8bfb7a9 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -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. diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 94744d0c..3557fc1f 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -18,5 +18,6 @@ jobs: - name: Stale PR+Issues uses: actions/stale@v9.0.0 with: + days-before-stale: 30 exempt-issue-labels: 'has sponsor,needs sponsor,help wanted,backlog,security issue' exempt-pr-labels: 'has sponsor,needs sponsor,help wanted,backlog,security issue' diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 94ade3d3..60f3057e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -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//Meshtastic-Apple.git + git clone https://github.com//Meshtastic-Apple.git ``` 3. Navigate to the project directory: ```sh diff --git a/Localizable.xcstrings b/Localizable.xcstrings index dd215188..d69614a3 100644 --- a/Localizable.xcstrings +++ b/Localizable.xcstrings @@ -2210,22 +2210,6 @@ } } }, - "Backup Database" : { - "localizations" : { - "sr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Резервна база података" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "备份数据库" - } - } - } - }, "Bandwidth" : { "localizations" : { "de" : { @@ -4281,6 +4265,9 @@ } } } + }, + "Community Support" : { + }, "Config" : { "localizations" : { @@ -5642,6 +5629,9 @@ } } } + }, + "Confirm" : { + }, "Connect to a Node" : { "localizations" : { @@ -5658,6 +5648,9 @@ } } } + }, + "Connect to new radio?" : { + }, "connected" : { "localizations" : { @@ -5738,6 +5731,9 @@ } } } + }, + "Connected Radio" : { + }, "connected.radio" : { "localizations" : { @@ -5866,6 +5862,9 @@ } } } + }, + "Connecting to a new radio will clear all app data on the phone." : { + }, "Connection Attempt %lld of 10" : { "localizations" : { @@ -6377,6 +6376,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" : { @@ -9311,6 +9313,9 @@ } } } + }, + "Enable broadcasting packets via UDP over the local network." : { + }, "Enable Notifications" : { "localizations" : { @@ -9327,6 +9332,9 @@ } } } + }, + "Enable this device as a Store and Forward server. Requires an ESP32 device with PSRAM." : { + }, "enabled" : { "localizations" : { @@ -9424,21 +9432,8 @@ } } }, - "Enables the store and forward module. Store and forward must be enabled on both client and router devices." : { - "localizations" : { - "sr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Омогућава модул за чување и пренос. Чување и пренос мора бити омогућено на оба уређаја, клијенту и рутеру." - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "启用存储和转发模块。客户端和路由器设备都必须启用存储和转发功能。" - } - } - } + "Enables the store and forward module." : { + }, "Enabling Ethernet will disable the bluetooth connection to the app." : { "localizations" : { @@ -10694,6 +10689,9 @@ } } } + }, + "Full Support" : { + }, "gas" : { "extractionState" : "manual", @@ -22146,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" : { @@ -23546,6 +23531,9 @@ } } } + }, + "Radiation" : { + }, "Radio Disconnected" : { "extractionState" : "manual", @@ -24633,22 +24621,6 @@ } } }, - "restore" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Wiederherstellen" - } - }, - "sr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Обнова" - } - } - } - }, "resume" : { "localizations" : { "de" : { @@ -24994,26 +24966,6 @@ } } }, - "Router" : { - "localizations" : { - "sr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Рутер" - } - } - } - }, - "Router Options" : { - "localizations" : { - "sr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Опције рутера" - } - } - } - }, "Routes" : { "localizations" : { "sr" : { @@ -27078,6 +27030,9 @@ } } } + }, + "Send a heartbeat to advertise the server's presence." : { + }, "Send a message to a certain meshtastic channel" : { "localizations" : { @@ -27827,6 +27782,9 @@ } } } + }, + "Server Option" : { + }, "Set" : { "localizations" : { @@ -27985,6 +27943,9 @@ } } } + }, + "Settings" : { + }, "Share QR Code & Link" : { "localizations" : { @@ -28337,6 +28298,9 @@ } } } + }, + "Shows information for the Lora radio connected via bluetooth. You can swipe left to disconnect the radio and long press start the live activity." : { + }, "Shut Down" : { "localizations" : { @@ -28452,6 +28416,12 @@ } } } + }, + "Soil Moisture" : { + + }, + "Soil Temp" : { + }, "Specifies how long the monitored GPIO should output." : { "localizations" : { @@ -28821,25 +28791,8 @@ } } }, - "Store and forward clients can request history from routers on the network." : { - "localizations" : { - "sr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Клијенти за складиштење и прослеђивање могу затражити историју од рутера на мрежи." - } - } - } - }, - "Store and forward router devices require a ESP32 device with PSRAM." : { - "localizations" : { - "sr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Рутер за складиштење и прослеђивање захтева ESP32 уређај са PSRAM." - } - } - } + "Store and forward servers require an ESP32 device with PSRAM or Linux Native." : { + }, "storeforward.heartbeat" : { "localizations" : { @@ -30119,6 +30072,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" : { @@ -30292,6 +30248,9 @@ } } } + }, + "This node does not support any configurable modules." : { + }, "This will disable fixed position and remove the currently set position." : { "localizations" : { @@ -30531,134 +30490,6 @@ } } }, - "tip.bluetooth.connect.message" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Shows information for the Lora radio currently connected via bluetooth. You can swipe left to disconnect the radio and long press to view stats or start the live activity." - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Shows information for the Lora radio connected via bluetooth. You can swipe left to disconnect the radio and long press to view stats or start the live activity." - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Affiche les informations de la radio Lora connectée via le bluetooth. Vous pouvez faire un glissé vers la gauche pour déconnecter la radio et un appui long pour voir les statistiques ou démarrer l'activité en direct." - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "מראה מידע אודות מכשיר המשטסטיק המחובר כעת לבלוטוס. ניתן לגרור שמאלה להתנתקות או לחיצה ארוכה לראות סטטיסטיקה או להתחיל פעילות." - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Shows information for the Lora radio currently connected via bluetooth. You can swipe left to disconnect the radio and long press to view stats or start the live activity." - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Mostra informações para o rádio LoRa conectado via bluetooth. Você pode deslizar para a esquerda para desconectar o rádio e pressionar por um longo período para ver estatísticas ou iniciar a atividade ao vivo." - } - }, - "se" : { - "stringUnit" : { - "state" : "translated", - "value" : "Visar information för LoRa-radion ansluten via bluetooth. Du kan svepa åt vänster för att koppla från radion och långtryck för att visa statistik eller starta liveaktivitet." - } - }, - "sr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Приказује информације за LoRA радио повезан преко Блутута. Можете превући лево да бисте одспојили радио и дуго притиснути да бисте погледали статистику или започели активност у реалном времену." - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "显示当前通过蓝牙连接的 Lora 电台的信息。您可以向左滑动断开电台,长按查看统计信息或开始实时活动。" - } - }, - "zh-Hant-TW" : { - "stringUnit" : { - "state" : "translated", - "value" : "顯示目前通過藍芽連接的 Lora 電台的信息。您可以向左滑動斷開電台,長按查看統計訊息或開始即時活動。" - } - } - } - }, - "tip.bluetooth.connect.title" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Connected LoRa Radio" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Connected Radio" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Radio connectée" - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "מכשיר מחובר" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Connected LoRa Radio" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Rádio Conectado" - } - }, - "se" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ansluten Radio" - } - }, - "sr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Радио повезан" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "电台已连接" - } - }, - "zh-Hant-TW" : { - "stringUnit" : { - "state" : "translated", - "value" : "連接到 LoRa 電台" - } - } - } - }, "tip.channel.admin.message" : { "localizations" : { "de" : { @@ -31442,6 +31273,9 @@ } } } + }, + "UDP Broadcast" : { + }, "Ukraine 433mhz" : { "extractionState" : "manual", @@ -32622,6 +32456,9 @@ } } } + }, + "Weight" : { + }, "What does the lock mean?" : { "localizations" : { diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 60c6e50b..58e2baa5 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -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 = ""; }; 231B3F242D087C3C0069A07D /* EnvironmentDefaultColumns.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnvironmentDefaultColumns.swift; sourceTree = ""; }; 231B3F262D0885240069A07D /* MetricsColumnDetail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetricsColumnDetail.swift; sourceTree = ""; }; + 233E99B32D84969500CC3A77 /* MeshtasticDataModelV 50.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 50.xcdatamodel"; sourceTree = ""; }; + 233E99B52D849C3D00CC3A77 /* WeatherConditionsCompactWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeatherConditionsCompactWidget.swift; sourceTree = ""; }; + 233E99B72D849C6500CC3A77 /* HumidityCompactWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HumidityCompactWidget.swift; sourceTree = ""; }; + 233E99B92D849C7000CC3A77 /* PressureCompactWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PressureCompactWidget.swift; sourceTree = ""; }; + 233E99BB2D849C8C00CC3A77 /* WindCompactWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindCompactWidget.swift; sourceTree = ""; }; + 233E99BD2D849D3200CC3A77 /* RadiationCompactWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadiationCompactWidget.swift; sourceTree = ""; }; + 233E99C02D849D6000CC3A77 /* DistanceCompactWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DistanceCompactWidget.swift; sourceTree = ""; }; + 233E99C22D849D7A00CC3A77 /* WeightCompactWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeightCompactWidget.swift; sourceTree = ""; }; + 233E99C42D84A0B600CC3A77 /* CompactWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompactWidget.swift; sourceTree = ""; }; + 233E99C62D84A70900CC3A77 /* SoilCompactWidgets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoilCompactWidgets.swift; sourceTree = ""; }; + 233E99CA2D85AAA900CC3A77 /* RainfallCompactWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RainfallCompactWidget.swift; sourceTree = ""; }; 2344A2AA2D66973D00170A77 /* ManagedAttributePropertyWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManagedAttributePropertyWrapper.swift; sourceTree = ""; }; 2344A2AD2D6697A700170A77 /* TelemetryEntity+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TelemetryEntity+CoreDataClass.swift"; sourceTree = ""; }; 2344A2AE2D6697A700170A77 /* TelemetryEntity+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TelemetryEntity+CoreDataProperties.swift"; sourceTree = ""; }; 2373AE122D0A216C0086C749 /* MetricsChartSeries.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetricsChartSeries.swift; sourceTree = ""; }; 2373AE142D0A24930086C749 /* MetricsSeriesList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetricsSeriesList.swift; sourceTree = ""; }; - 2373AE162D0A26620086C749 /* EnviornmentDefaultSeries.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnviornmentDefaultSeries.swift; sourceTree = ""; }; + 2373AE162D0A26620086C749 /* EnvironmentDefaultSeries.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnvironmentDefaultSeries.swift; sourceTree = ""; }; 251926842C3BA97800249DF5 /* FavoriteNodeButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoriteNodeButton.swift; sourceTree = ""; }; 251926862C3BAE2200249DF5 /* NodeAlertsButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeAlertsButton.swift; sourceTree = ""; }; 251926892C3BB1B200249DF5 /* ExchangePositionsButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExchangePositionsButton.swift; sourceTree = ""; }; @@ -324,7 +338,6 @@ D93068DC2B81CA820066FBC8 /* ConfigHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigHeader.swift; sourceTree = ""; }; D93069062B81D8900066FBC8 /* MeshtasticDataModelV 27.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 27.xcdatamodel"; sourceTree = ""; }; D93069072B81DF040066FBC8 /* SaveConfigButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SaveConfigButton.swift; sourceTree = ""; }; - D9BC22DA2B7DE8E2006A37D5 /* TileDownloadStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TileDownloadStatus.swift; sourceTree = ""; }; D9C9839C2B79CFD700BDBE6A /* TextMessageSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextMessageSize.swift; sourceTree = ""; }; D9C9839F2B79D0E800BDBE6A /* AlertButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertButton.swift; sourceTree = ""; }; D9C983A12B79D1A600BDBE6A /* RequestPositionButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestPositionButton.swift; sourceTree = ""; }; @@ -359,7 +372,6 @@ DD268D8D2BCC90E2008073AE /* RouteEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RouteEnums.swift; sourceTree = ""; }; DD295CE92B323ED9002CC4AC /* MeshtasticDataModelV22.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV22.xcdatamodel; sourceTree = ""; }; DD2984A82C5AEF7500B1268D /* MeshtasticDataModelV 41.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 41.xcdatamodel"; sourceTree = ""; }; - DD2AD8A7296D2DF9001FF0E7 /* MapViewSwiftUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapViewSwiftUI.swift; sourceTree = ""; }; DD2CC2E52ABFE04E00EDFDA7 /* MeshtasticDataModelV19.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV19.xcdatamodel; sourceTree = ""; }; DD31B04D2BDC6FD30024FA63 /* MeshtasticDataModelV 36.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 36.xcdatamodel"; sourceTree = ""; }; DD33DB602B3D1ECC003E1EA0 /* MeshtasticDataModelV 23.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 23.xcdatamodel"; sourceTree = ""; }; @@ -431,10 +443,8 @@ DD93800D2BA74D0C008BEC06 /* ChannelForm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelForm.swift; sourceTree = ""; }; DD94B73F2ACCE3BE00DCD1D1 /* MapSettingsForm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapSettingsForm.swift; sourceTree = ""; }; DD964FBC296E6B01007C176F /* EmojiOnlyTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiOnlyTextField.swift; sourceTree = ""; }; - DD964FBE296E76EF007C176F /* WaypointFormMapKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaypointFormMapKit.swift; sourceTree = ""; }; DD964FC029724F6D007C176F /* MeshtasticDataModelV6.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV6.xcdatamodel; sourceTree = ""; }; DD964FC1297272AE007C176F /* WaypointEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaypointEntityExtension.swift; sourceTree = ""; }; - DD964FC32974767D007C176F /* MapViewFitExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapViewFitExtension.swift; sourceTree = ""; }; DD964FC52975DBFD007C176F /* QueryCoreData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueryCoreData.swift; sourceTree = ""; }; DD9681A22BBB22BE00FD2C47 /* MeshtasticDataModelV32.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV32.xcdatamodel; sourceTree = ""; }; DD97E96528EFD9820056DDA4 /* MeshtasticLogo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshtasticLogo.swift; sourceTree = ""; }; @@ -508,9 +518,7 @@ DDDB26412AABF655003AFCB7 /* NodeListItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeListItem.swift; sourceTree = ""; }; DDDB26432AAC0206003AFCB7 /* NodeDetail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeDetail.swift; sourceTree = ""; }; DDDB26452AACC0B7003AFCB7 /* NodeInfoItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeInfoItem.swift; sourceTree = ""; }; - DDDB26472AACD6D1003AFCB7 /* NodeMapMapkit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeMapMapkit.swift; sourceTree = ""; }; DDDB26492AAD743E003AFCB7 /* MeshtasticDataModelV18.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV18.xcdatamodel; sourceTree = ""; }; - DDDB443529F6287000EE2349 /* MapButtons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapButtons.swift; sourceTree = ""; }; DDDB443F29F79AB000EE2349 /* UserDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaults.swift; sourceTree = ""; }; DDDB444129F8A88700EE2349 /* Double.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Double.swift; sourceTree = ""; }; DDDB444329F8A8DD00EE2349 /* Float.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Float.swift; sourceTree = ""; }; @@ -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 = ""; }; + 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 = ""; + }; 2344A2AC2D66978000170A77 /* CoreData */ = { isa = PBXGroup; children = ( @@ -665,27 +690,6 @@ path = AppIntents; sourceTree = ""; }; - C9483F6B2773016700998F6B /* MapKitMap */ = { - isa = PBXGroup; - children = ( - C9A7BC0E27759A6800760B50 /* Custom */, - DDDB26472AACD6D1003AFCB7 /* NodeMapMapkit.swift */, - DD964FBE296E76EF007C176F /* WaypointFormMapKit.swift */, - ); - path = MapKitMap; - sourceTree = ""; - }; - C9A7BC0E27759A6800760B50 /* Custom */ = { - isa = PBXGroup; - children = ( - DD964FC32974767D007C176F /* MapViewFitExtension.swift */, - DD2AD8A7296D2DF9001FF0E7 /* MapViewSwiftUI.swift */, - DDDB443529F6287000EE2349 /* MapButtons.swift */, - D9BC22DA2B7DE8E2006A37D5 /* TileDownloadStatus.swift */, - ); - path = Custom; - sourceTree = ""; - }; 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 = ""; diff --git a/Meshtastic.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Meshtastic.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved deleted file mode 100644 index d528f985..00000000 --- a/Meshtastic.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ /dev/null @@ -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 -} diff --git a/Meshtastic.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/Meshtastic.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 00000000..0c67376e --- /dev/null +++ b/Meshtastic.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,5 @@ + + + + + diff --git a/Meshtastic.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Meshtastic.xcworkspace/xcshareddata/swiftpm/Package.resolved index 3465793d..8cb1b6ba 100644 --- a/Meshtastic.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Meshtastic.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -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" } } ], diff --git a/Meshtastic/AppIntents/AppIntentErrors.swift b/Meshtastic/AppIntents/AppIntentErrors.swift index 427c30ae..9732de12 100644 --- a/Meshtastic/AppIntents/AppIntentErrors.swift +++ b/Meshtastic/AppIntents/AppIntentErrors.swift @@ -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") diff --git a/Meshtastic/Assets.xcassets/soil.moisture.symbolset/Contents.json b/Meshtastic/Assets.xcassets/soil.moisture.symbolset/Contents.json new file mode 100644 index 00000000..c5fa326a --- /dev/null +++ b/Meshtastic/Assets.xcassets/soil.moisture.symbolset/Contents.json @@ -0,0 +1,12 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "symbols" : [ + { + "filename" : "soilMoisture.variable.svg", + "idiom" : "universal" + } + ] +} diff --git a/Meshtastic/Assets.xcassets/soil.moisture.symbolset/soilMoisture.variable.svg b/Meshtastic/Assets.xcassets/soil.moisture.symbolset/soilMoisture.variable.svg new file mode 100644 index 00000000..346bc90e --- /dev/null +++ b/Meshtastic/Assets.xcassets/soil.moisture.symbolset/soilMoisture.variable.svg @@ -0,0 +1,366 @@ + + + + + + + + + + Weight/Scale Variations + Ultralight + Thin + Light + Regular + Medium + Semibold + Bold + Heavy + Black + + + + + + + + + + + Design Variations + Symbols are supported in up to nine weights and three scales. + For optimal layout with text and other symbols, vertically align + symbols with the adjacent text. + + + + + + Margins + Leading and trailing margins on the left and right side of each symbol + can be adjusted by modifying the x-location of the margin guidelines. + Modifications are automatically applied proportionally to all + scales and weights. + + + + Exporting + Symbols should be outlined when exporting to ensure the + design is preserved when submitting to Xcode. + Template v.6.0 + Requires Xcode 16 or greater + Generated from thermometer.variable + Typeset at 100.0 points + Small + Medium + Large + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Meshtastic/Assets.xcassets/soil.temperature.symbolset/Contents.json b/Meshtastic/Assets.xcassets/soil.temperature.symbolset/Contents.json new file mode 100644 index 00000000..d136a31b --- /dev/null +++ b/Meshtastic/Assets.xcassets/soil.temperature.symbolset/Contents.json @@ -0,0 +1,12 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "symbols" : [ + { + "filename" : "soilTemp.variable.svg", + "idiom" : "universal" + } + ] +} diff --git a/Meshtastic/Assets.xcassets/soil.temperature.symbolset/soilTemp.variable.svg b/Meshtastic/Assets.xcassets/soil.temperature.symbolset/soilTemp.variable.svg new file mode 100644 index 00000000..b5501a8a --- /dev/null +++ b/Meshtastic/Assets.xcassets/soil.temperature.symbolset/soilTemp.variable.svg @@ -0,0 +1,363 @@ + + + + + + + + + + Weight/Scale Variations + Ultralight + Thin + Light + Regular + Medium + Semibold + Bold + Heavy + Black + + + + + + + + + + + Design Variations + Symbols are supported in up to nine weights and three scales. + For optimal layout with text and other symbols, vertically align + symbols with the adjacent text. + + + + + + Margins + Leading and trailing margins on the left and right side of each symbol + can be adjusted by modifying the x-location of the margin guidelines. + Modifications are automatically applied proportionally to all + scales and weights. + + + + Exporting + Symbols should be outlined when exporting to ensure the + design is preserved when submitting to Xcode. + Template v.6.0 + Requires Xcode 16 or greater + Generated from thermometer.variable + Typeset at 100.0 points + Small + Medium + Large + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Meshtastic/Extensions/CoreData/ManagedAttributePropertyWrapper.swift b/Meshtastic/Extensions/CoreData/ManagedAttributePropertyWrapper.swift index 3b123207..5c5fc71f 100644 --- a/Meshtastic/Extensions/CoreData/ManagedAttributePropertyWrapper.swift +++ b/Meshtastic/Extensions/CoreData/ManagedAttributePropertyWrapper.swift @@ -29,6 +29,8 @@ public struct ManagedAttribute { 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)") } diff --git a/Meshtastic/Extensions/Date.swift b/Meshtastic/Extensions/Date.swift index 0736fc63..d8cdd30f 100644 --- a/Meshtastic/Extensions/Date.swift +++ b/Meshtastic/Extensions/Date.swift @@ -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 diff --git a/Meshtastic/Extensions/String.swift b/Meshtastic/Extensions/String.swift index c7c6385b..d2ae1e5a 100644 --- a/Meshtastic/Extensions/String.swift +++ b/Meshtastic/Extensions/String.swift @@ -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 + } } diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 47db7ebd..9f633f30 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -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 @@ -537,7 +537,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate if let coordsMatch = try CommonRegex.COORDS_REGEX.firstMatch(in: logString) { log = "\(log.replacingOccurrences(of: "DEBUG |", with: "").trimmingCharacters(in: .whitespaces))" log = log.replacingOccurrences(of: "[,]", with: "", options: .regularExpression) - Logger.radio.debug("🛰️ \(log.prefix(upTo: coordsMatch.range.lowerBound), privacy: .public) \(coordsMatch.0.replacingOccurrences(of: "[,]", with: "", options: .regularExpression), privacy: .private) \(log.suffix(from: coordsMatch.range.upperBound), privacy: .public)") + Logger.radio.debug("🛰️ \(log.prefix(upTo: coordsMatch.range.lowerBound), privacy: .public) \(coordsMatch.0.replacingOccurrences(of: "[,]", with: "", options: .regularExpression), privacy: .private(mask: .none)) \(log.suffix(from: coordsMatch.range.upperBound), privacy: .public)") } else { log = log.replacingOccurrences(of: "[,]", with: "", options: .regularExpression) Logger.radio.debug("🕵🏻‍♂️ \(log.replacingOccurrences(of: "DEBUG |", with: "").trimmingCharacters(in: .whitespaces), privacy: .public)") @@ -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: @@ -811,11 +794,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate case .serialApp: Logger.mesh.info("🕸️ MESH PACKET received for Serial App UNHANDLED UNHANDLED") case .storeForwardApp: - if wantStoreAndForwardPackets { - storeAndForwardPacket(packet: decodedInfo.packet, connectedNodeNum: (self.connectedPeripheral != nil ? connectedPeripheral.num : 0), context: context) - } else { - Logger.mesh.info("🕸️ MESH PACKET received for Store and Forward App - Store and Forward is disabled.") - } + storeAndForwardPacket(packet: decodedInfo.packet, connectedNodeNum: (self.connectedPeripheral != nil ? connectedPeripheral.num : 0), context: context) case .rangeTestApp: if wantRangeTestPackets { textMessageAppPacket( @@ -988,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 }) @@ -1046,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)") } } @@ -1090,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 { @@ -1177,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)") @@ -1224,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 @@ -1388,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.") @@ -1704,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)") } } @@ -1743,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 @@ -1772,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 { @@ -1849,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 @@ -2692,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 } @@ -3272,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 @@ -3469,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 diff --git a/Meshtastic/Helpers/LocalNotificationManager.swift b/Meshtastic/Helpers/LocalNotificationManager.swift index d5f25749..c51a2af3 100644 --- a/Meshtastic/Helpers/LocalNotificationManager.swift +++ b/Meshtastic/Helpers/LocalNotificationManager.swift @@ -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)") } } } diff --git a/Meshtastic/Helpers/LocationsHandler.swift b/Meshtastic/Helpers/LocationsHandler.swift index 6754891c..75830805 100644 --- a/Meshtastic/Helpers/LocationsHandler.swift +++ b/Meshtastic/Helpers/LocationsHandler.swift @@ -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 } @@ -84,15 +84,15 @@ import OSLog if smartPostion { let age = -location.timestamp.timeIntervalSinceNow if age > 10 { - Logger.services.info("📍 [App] Smart Position - Bad Location: Too Old \(age, privacy: .public) seconds ago \(location, privacy: .private)") + Logger.services.info("📍 [App] Smart Position - Bad Location: Too Old \(age, privacy: .public) seconds ago \(location, privacy: .private(mask: .none))") return false } if location.horizontalAccuracy < 0 { - Logger.services.info("📍 [App] Smart Position - Bad Location: Horizontal Accuracy: \(location.horizontalAccuracy) \(location, privacy: .private)") + Logger.services.info("📍 [App] Smart Position - Bad Location: Horizontal Accuracy: \(location.horizontalAccuracy) \(location, privacy: .private(mask: .none))") return false } if location.horizontalAccuracy > 5 { - Logger.services.info("📍 [App] Smart Position - Bad Location: Horizontal Accuracy: \(location.horizontalAccuracy) \(location, privacy: .private)") + Logger.services.info("📍 [App] Smart Position - Bad Location: Horizontal Accuracy: \(location.horizontalAccuracy) \(location, privacy: .private(mask: .none))") return false } } diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index 093066ef..83f0b8ef 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -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 { @@ -898,11 +909,14 @@ func textMessageAppPacket( if packet.decoded.replyID > 0 { newMessage.replyID = Int64(packet.decoded.replyID) } + // Updated logic for handling toUser if fetchedUsers.first(where: { $0.num == packet.to }) != nil && packet.to != Constants.maximumNodeNum { if !storeForwardBroadcast { newMessage.toUser = fetchedUsers.first(where: { $0.num == packet.to }) + } else if storeForwardBroadcast { + // For S&F broadcast messages, treat as a channel message (not a DM) + newMessage.toUser = nil } else { - /// Make a new to user if they are unknown newMessage.toUser = createUser(num: Int64(truncatingIfNeeded: packet.to), context: context) } } @@ -948,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 { @@ -961,7 +975,7 @@ func textMessageAppPacket( appState.unreadDirectMessages = newMessage.toUser?.unreadMessages ?? 0 } if !(newMessage.fromUser?.mute ?? false) { - // Create an iOS Notification for the received DM message and schedule it immediately + // Create an iOS Notification for the received DM message let manager = LocalNotificationManager() manager.notifications = [ Notification( @@ -978,23 +992,21 @@ 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() fetchMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %lld", Int64(connectedNode)) - do { let fetchedMyInfo = try context.fetch(fetchMyInfoRequest) if !fetchedMyInfo.isEmpty { appState.unreadChannelMessages = fetchedMyInfo[0].unreadMessages - for channel in (fetchedMyInfo[0].channels?.array ?? []) as? [ChannelEntity] ?? [] { if channel.index == newMessage.channel { context.refresh(channel, mergeChanges: true) } if channel.index == newMessage.channel && !channel.mute && UserDefaults.channelMessageNotifications { - // Create an iOS Notification for the received private channel message and schedule it immediately + // Create an iOS Notification for the received channel message let manager = LocalNotificationManager() manager.notifications = [ Notification( @@ -1007,10 +1019,11 @@ func textMessageAppPacket( messageId: newMessage.messageId, channel: newMessage.channel, userNum: Int64(newMessage.fromUser?.userId ?? "0"), - critical: critical) + critical: critical + ) ] 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)") } } } @@ -1022,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") @@ -1033,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)) @@ -1060,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 @@ -1075,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) @@ -1098,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)") } } } diff --git a/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion b/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion index 0b4b8e13..cd530ab2 100644 --- a/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion +++ b/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion @@ -3,6 +3,6 @@ _XCCurrentVersionName - MeshtasticDataModelV 49.xcdatamodel + MeshtasticDataModelV 50.xcdatamodel diff --git a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 49.xcdatamodel/contents b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 49.xcdatamodel/contents index 9e23de8a..03e88490 100644 --- a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 49.xcdatamodel/contents +++ b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 49.xcdatamodel/contents @@ -1,5 +1,5 @@ - + @@ -373,7 +373,8 @@ - + + diff --git a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 50.xcdatamodel/contents b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 50.xcdatamodel/contents new file mode 100644 index 00000000..9578507a --- /dev/null +++ b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 50.xcdatamodel/contents @@ -0,0 +1,504 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Meshtastic/MeshtasticApp.swift b/Meshtastic/MeshtasticApp.swift index b7b28f35..5c82c257 100644 --- a/Meshtastic/MeshtasticApp.swift +++ b/Meshtastic/MeshtasticApp.swift @@ -38,6 +38,8 @@ struct MeshtasticAppleApp: App { // Wire up router self.appDelegate.router = appState.router + // Show Tips + try? Tips.resetDatastore() } var body: some Scene { @@ -55,7 +57,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 +74,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,21 +101,15 @@ 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) } }) .task { - #if DEBUG - /// Optionally, call `Tips.resetDatastore()` before `Tips.configure()` to reset the state of all tips. This will allow tips to re-appear even after they have been dismissed by the user. - /// This is for testing only, and should not be enabled in release builds. - try? Tips.resetDatastore() - #endif - try? Tips.configure( [ // Reset which tips have been shown and what parameters have been tracked, useful during testing and for this sample project diff --git a/Meshtastic/MeshtasticAppDelegate.swift b/Meshtastic/MeshtasticAppDelegate.swift index 801fa955..365faef4 100644 --- a/Meshtastic/MeshtasticAppDelegate.swift +++ b/Meshtastic/MeshtasticAppDelegate.swift @@ -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() } diff --git a/Meshtastic/Model/CoreData/TelemetryEntity+CoreDataClass.swift b/Meshtastic/Model/CoreData/TelemetryEntity+CoreDataClass.swift index bcbaf4b9..48f8263b 100644 --- a/Meshtastic/Model/CoreData/TelemetryEntity+CoreDataClass.swift +++ b/Meshtastic/Model/CoreData/TelemetryEntity+CoreDataClass.swift @@ -42,5 +42,14 @@ public class TelemetryEntity: NSManagedObject, Identifiable { @ManagedAttribute(attributeName: "windGust") public var windGust: Float? @ManagedAttribute(attributeName: "windLull") public var windLull: Float? @ManagedAttribute(attributeName: "windSpeed") public var windSpeed: Float? + @ManagedAttribute(attributeName: "irLux") public var irLux: Float? + @ManagedAttribute(attributeName: "lux") public var lux: Float? + @ManagedAttribute(attributeName: "uvLux") public var uvLux: Float? + @ManagedAttribute(attributeName: "whiteLux") public var whiteLux: Float? + @ManagedAttribute(attributeName: "radiation") public var radiation: Float? + @ManagedAttribute(attributeName: "rainfall1H") public var rainfall1H: Float? + @ManagedAttribute(attributeName: "rainfall24H") public var rainfall24H: Float? + @ManagedAttribute(attributeName: "soilTemperature") public var soilTemperature: Float? + @ManagedAttribute(attributeName: "soilMoisture") public var soilMoisture: UInt32? } diff --git a/Meshtastic/Model/Metrics Visualization/MetricsChartSeries.swift b/Meshtastic/Model/Metrics Visualization/MetricsChartSeries.swift index b535099e..ab68c4cc 100644 --- a/Meshtastic/Model/Metrics Visualization/MetricsChartSeries.swift +++ b/Meshtastic/Model/Metrics Visualization/MetricsChartSeries.swift @@ -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? + let minumumYAxisSpan: Float? + // Main initializer init( id: String, keyPath: KeyPath, name: String, abbreviatedName: String, + initialYAxisRange: ClosedRange? = nil, + minumumYAxisSpan: Float? = nil, conversion: ((Value) -> Value)? = nil, visible: Bool = true, foregroundStyle: @escaping ((ClosedRange?) -> 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 diff --git a/Meshtastic/Model/Metrics Visualization/MetricsSeriesList.swift b/Meshtastic/Model/Metrics Visualization/MetricsSeriesList.swift index 049d1fb4..fb68af92 100644 --- a/Meshtastic/Model/Metrics Visualization/MetricsSeriesList.swift +++ b/Meshtastic/Model/Metrics Visualization/MetricsSeriesList.swift @@ -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 { - 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] = [:] + + // 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 diff --git a/Meshtastic/Persistence/Persistence.swift b/Meshtastic/Persistence/Persistence.swift index 70e9f898..f18a8566 100644 --- a/Meshtastic/Persistence/Persistence.swift +++ b/Meshtastic/Persistence/Persistence.swift @@ -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)") - } - } - } } diff --git a/Meshtastic/Persistence/UpdateCoreData.swift b/Meshtastic/Persistence/UpdateCoreData.swift index 0c367968..e5e7bd87 100644 --- a/Meshtastic/Persistence/UpdateCoreData.swift +++ b/Meshtastic/Persistence/UpdateCoreData.swift @@ -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)) @@ -1363,6 +1361,7 @@ func upsertStoreForwardModuleConfigPacket(config: ModuleConfig.StoreForwardConfi newConfig.records = Int32(config.records) newConfig.historyReturnMax = Int32(config.historyReturnMax) newConfig.historyReturnWindow = Int32(config.historyReturnWindow) + newConfig.isRouter = config.isServer fetchedNode[0].storeForwardConfig = newConfig } else { fetchedNode[0].storeForwardConfig?.enabled = config.enabled @@ -1395,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)) diff --git a/Meshtastic/Resources/DeviceHardware.json b/Meshtastic/Resources/DeviceHardware.json index a4a6949c..00b80aea 100644 --- a/Meshtastic/Resources/DeviceHardware.json +++ b/Meshtastic/Resources/DeviceHardware.json @@ -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 } ] diff --git a/Meshtastic/Router/Router.swift b/Meshtastic/Router/Router.swift index 718c71b1..8ba39609 100644 --- a/Meshtastic/Router/Router.swift +++ b/Meshtastic/Router/Router.swift @@ -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)") } } diff --git a/Meshtastic/Tips/BluetoothTips.swift b/Meshtastic/Tips/BluetoothTips.swift index 02d2af53..838d29fc 100644 --- a/Meshtastic/Tips/BluetoothTips.swift +++ b/Meshtastic/Tips/BluetoothTips.swift @@ -13,10 +13,10 @@ struct BluetoothConnectionTip: Tip { return "tip.bluetooth.connect" } var title: Text { - Text("tip.bluetooth.connect.title") + Text("Connected Radio") } var message: Text? { - Text("tip.bluetooth.connect.message") + Text("Shows information for the Lora radio connected via bluetooth. You can swipe left to disconnect the radio and long press start the live activity.") } var image: Image? { Image(systemName: "flipphone") diff --git a/Meshtastic/Views/Bluetooth/Connect.swift b/Meshtastic/Views/Bluetooth/Connect.swift index 2d86e15a..880faf8d 100644 --- a/Meshtastic/Views/Bluetooth/Connect.swift +++ b/Meshtastic/Views/Bluetooth/Connect.swift @@ -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)") } } diff --git a/Meshtastic/Views/ContentView.swift b/Meshtastic/Views/ContentView.swift index f593651e..d6b2fd6b 100644 --- a/Meshtastic/Views/ContentView.swift +++ b/Meshtastic/Views/ContentView.swift @@ -53,6 +53,5 @@ struct ContentView: View { } .tag(NavigationState.Tab.settings) } - .toolbarBackground(.visible, for: .tabBar) } } diff --git a/Meshtastic/Views/Helpers/CircleText.swift b/Meshtastic/Views/Helpers/CircleText.swift index b7e4238d..b8f74842 100644 --- a/Meshtastic/Views/Helpers/CircleText.swift +++ b/Meshtastic/Views/Helpers/CircleText.swift @@ -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) diff --git a/Meshtastic/Views/Helpers/Compact Widgets/CompactWidget.swift b/Meshtastic/Views/Helpers/Compact Widgets/CompactWidget.swift new file mode 100644 index 00000000..42411cc1 --- /dev/null +++ b/Meshtastic/Views/Helpers/Compact Widgets/CompactWidget.swift @@ -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) + } + } +} diff --git a/Meshtastic/Views/Helpers/Weather/CurrentConditionsCompact.swift b/Meshtastic/Views/Helpers/Compact Widgets/CurrentConditionsCompact.swift similarity index 99% rename from Meshtastic/Views/Helpers/Weather/CurrentConditionsCompact.swift rename to Meshtastic/Views/Helpers/Compact Widgets/CurrentConditionsCompact.swift index fe166e48..5f0edf46 100644 --- a/Meshtastic/Views/Helpers/Weather/CurrentConditionsCompact.swift +++ b/Meshtastic/Views/Helpers/Compact Widgets/CurrentConditionsCompact.swift @@ -17,6 +17,7 @@ struct CurrentConditionsCompact: View { .symbolRenderingMode(.multicolor) } } + struct CurrentConditionsCompact_Previews: PreviewProvider { static var previews: some View { diff --git a/Meshtastic/Views/Helpers/Compact Widgets/DistanceCompactWidget.swift b/Meshtastic/Views/Helpers/Compact Widgets/DistanceCompactWidget.swift new file mode 100644 index 00000000..9f04406e --- /dev/null +++ b/Meshtastic/Views/Helpers/Compact Widgets/DistanceCompactWidget.swift @@ -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") +} diff --git a/Meshtastic/Views/Helpers/Compact Widgets/HumidityCompactWidget.swift b/Meshtastic/Views/Helpers/Compact Widgets/HumidityCompactWidget.swift new file mode 100644 index 00000000..b20981dd --- /dev/null +++ b/Meshtastic/Views/Helpers/Compact Widgets/HumidityCompactWidget.swift @@ -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) + } + } +} diff --git a/Meshtastic/Views/Helpers/Compact Widgets/PressureCompactWidget.swift b/Meshtastic/Views/Helpers/Compact Widgets/PressureCompactWidget.swift new file mode 100644 index 00000000..6f2f6583 --- /dev/null +++ b/Meshtastic/Views/Helpers/Compact Widgets/PressureCompactWidget.swift @@ -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) + } + } +} diff --git a/Meshtastic/Views/Helpers/Compact Widgets/RadiationCompactWidget.swift b/Meshtastic/Views/Helpers/Compact Widgets/RadiationCompactWidget.swift new file mode 100644 index 00000000..b5cd7232 --- /dev/null +++ b/Meshtastic/Views/Helpers/Compact Widgets/RadiationCompactWidget.swift @@ -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") +} diff --git a/Meshtastic/Views/Helpers/Compact Widgets/RainfallCompactWidget.swift b/Meshtastic/Views/Helpers/Compact Widgets/RainfallCompactWidget.swift new file mode 100644 index 00000000..9e71d213 --- /dev/null +++ b/Meshtastic/Views/Helpers/Compact Widgets/RainfallCompactWidget.swift @@ -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) + } + } +} diff --git a/Meshtastic/Views/Helpers/Compact Widgets/SoilCompactWidgets.swift b/Meshtastic/Views/Helpers/Compact Widgets/SoilCompactWidgets.swift new file mode 100644 index 00000000..7f51d21e --- /dev/null +++ b/Meshtastic/Views/Helpers/Compact Widgets/SoilCompactWidgets.swift @@ -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: "%") + } + } +} diff --git a/Meshtastic/Views/Helpers/Compact Widgets/WeatherConditionsCompactWidget.swift b/Meshtastic/Views/Helpers/Compact Widgets/WeatherConditionsCompactWidget.swift new file mode 100644 index 00000000..4918c7b6 --- /dev/null +++ b/Meshtastic/Views/Helpers/Compact Widgets/WeatherConditionsCompactWidget.swift @@ -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") + } + } +} + diff --git a/Meshtastic/Views/Helpers/Compact Widgets/WeightCompactWidget.swift b/Meshtastic/Views/Helpers/Compact Widgets/WeightCompactWidget.swift new file mode 100644 index 00000000..f6b6df81 --- /dev/null +++ b/Meshtastic/Views/Helpers/Compact Widgets/WeightCompactWidget.swift @@ -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") +} diff --git a/Meshtastic/Views/Helpers/Compact Widgets/WindCompactWidget.swift b/Meshtastic/Views/Helpers/Compact Widgets/WindCompactWidget.swift new file mode 100644 index 00000000..3b3c5273 --- /dev/null +++ b/Meshtastic/Views/Helpers/Compact Widgets/WindCompactWidget.swift @@ -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) + } + } +} diff --git a/Meshtastic/Views/Helpers/ConnectedDevice.swift b/Meshtastic/Views/Helpers/ConnectedDevice.swift index 42b0ac70..c795b1b0 100644 --- a/Meshtastic/Views/Helpers/ConnectedDevice.swift +++ b/Meshtastic/Views/Helpers/ConnectedDevice.swift @@ -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) diff --git a/Meshtastic/Views/Helpers/SecureInput.swift b/Meshtastic/Views/Helpers/SecureInput.swift index 6bcab1d0..aaed8bd1 100644 --- a/Meshtastic/Views/Helpers/SecureInput.swift +++ b/Meshtastic/Views/Helpers/SecureInput.swift @@ -55,7 +55,7 @@ struct SecureInput: View { }) { Image(systemName: self.isSecure ? "eye.slash" : "eye") .accentColor(.secondary) - } + }.buttonStyle(BorderlessButtonStyle()) } } } diff --git a/Meshtastic/Views/Helpers/Weather/LocalWeatherConditions.swift b/Meshtastic/Views/Helpers/Weather/LocalWeatherConditions.swift index b4e5336c..6682f18c 100644 --- a/Meshtastic/Views/Helpers/Weather/LocalWeatherConditions.swift +++ b/Meshtastic/Views/Helpers/Weather/LocalWeatherConditions.swift @@ -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 diff --git a/Meshtastic/Views/Helpers/Weather/NodeWeatherForecast.swift b/Meshtastic/Views/Helpers/Weather/NodeWeatherForecast.swift index 5fec377e..53f19a0f 100644 --- a/Meshtastic/Views/Helpers/Weather/NodeWeatherForecast.swift +++ b/Meshtastic/Views/Helpers/Weather/NodeWeatherForecast.swift @@ -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)") } } } diff --git a/Meshtastic/Views/MapKitMap/Custom/MapButtons.swift b/Meshtastic/Views/MapKitMap/Custom/MapButtons.swift deleted file mode 100644 index 89959f74..00000000 --- a/Meshtastic/Views/MapKitMap/Custom/MapButtons.swift +++ /dev/null @@ -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)) -//// } -//// } diff --git a/Meshtastic/Views/MapKitMap/Custom/MapViewFitExtension.swift b/Meshtastic/Views/MapKitMap/Custom/MapViewFitExtension.swift deleted file mode 100644 index a5c8ef63..00000000 --- a/Meshtastic/Views/MapKitMap/Custom/MapViewFitExtension.swift +++ /dev/null @@ -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) - } -} diff --git a/Meshtastic/Views/MapKitMap/Custom/MapViewSwiftUI.swift b/Meshtastic/Views/MapKitMap/Custom/MapViewSwiftUI.swift deleted file mode 100644 index 8cdf6d84..00000000 --- a/Meshtastic/Views/MapKitMap/Custom/MapViewSwiftUI.swift +++ /dev/null @@ -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 -// } -// } -//} diff --git a/Meshtastic/Views/MapKitMap/Custom/TileDownloadStatus.swift b/Meshtastic/Views/MapKitMap/Custom/TileDownloadStatus.swift deleted file mode 100644 index 07cddcb8..00000000 --- a/Meshtastic/Views/MapKitMap/Custom/TileDownloadStatus.swift +++ /dev/null @@ -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() - } - } -} diff --git a/Meshtastic/Views/MapKitMap/NodeMapMapkit.swift b/Meshtastic/Views/MapKitMap/NodeMapMapkit.swift deleted file mode 100644 index f9593585..00000000 --- a/Meshtastic/Views/MapKitMap/NodeMapMapkit.swift +++ /dev/null @@ -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? -// @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 -// @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) -// } -// } -//} diff --git a/Meshtastic/Views/MapKitMap/WaypointFormMapKit.swift b/Meshtastic/Views/MapKitMap/WaypointFormMapKit.swift deleted file mode 100644 index 456472e0..00000000 --- a/Meshtastic/Views/MapKitMap/WaypointFormMapKit.swift +++ /dev/null @@ -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).. 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 -// } -// } -// } -//} diff --git a/Meshtastic/Views/Messages/ChannelMessageList.swift b/Meshtastic/Views/Messages/ChannelMessageList.swift index 573a7dc8..bf5be325 100644 --- a/Meshtastic/Views/Messages/ChannelMessageList.swift +++ b/Meshtastic/Views/Messages/ChannelMessageList.swift @@ -115,12 +115,15 @@ struct ChannelMessageList: View { if !message.read { message.read = true do { + for unreadMessage in channel.allPrivateMessages.filter({ !$0.read }) { + unreadMessage.read = true + } 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)") } } } diff --git a/Meshtastic/Views/Messages/MessageText.swift b/Meshtastic/Views/Messages/MessageText.swift index df5b1f3d..002d2c4c 100644 --- a/Meshtastic/Views/Messages/MessageText.swift +++ b/Meshtastic/Views/Messages/MessageText.swift @@ -37,14 +37,28 @@ struct MessageText: View { HStack { Spacer() Image(systemName: "lock.circle.fill") - .symbolRenderingMode(.palette) - .foregroundStyle(.white, .green) - .font(.system(size: 20)) - .offset(x: 8, y: 8) + .symbolRenderingMode(.palette) + .foregroundStyle(.white, .green) + .font(.system(size: 20)) + .offset(x: 8, y: 8) } } } + let isStoreAndForward = message.portNum == Int32(PortNum.storeForwardApp.rawValue) let isDetectionSensorMessage = message.portNum == Int32(PortNum.detectionSensorApp.rawValue) + if isStoreAndForward { + VStack(alignment: .trailing) { + Spacer() + HStack { + Spacer() + Image(systemName: "envelope.circle.fill") + .symbolRenderingMode(.palette) + .foregroundStyle(.white, .gray) + .font(.system(size: 20)) + .offset(x: 8, y: 8) + } + } + } if tapBackDestination.overlaySensorMessage { VStack { isDetectionSensorMessage ? Image(systemName: "sensor.fill") @@ -59,6 +73,7 @@ struct MessageText: View { } else { EmptyView() } + } .contextMenu { MessageContextMenuItems( @@ -79,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) {} diff --git a/Meshtastic/Views/Messages/RetryButton.swift b/Meshtastic/Views/Messages/RetryButton.swift index afda173a..6964f1b5 100644 --- a/Meshtastic/Views/Messages/RetryButton.swift +++ b/Meshtastic/Views/Messages/RetryButton.swift @@ -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: diff --git a/Meshtastic/Views/Messages/TapbackResponses.swift b/Meshtastic/Views/Messages/TapbackResponses.swift index 77310184..b46e65f3 100644 --- a/Meshtastic/Views/Messages/TapbackResponses.swift +++ b/Meshtastic/Views/Messages/TapbackResponses.swift @@ -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)") } } } diff --git a/Meshtastic/Views/Messages/UserMessageList.swift b/Meshtastic/Views/Messages/UserMessageList.swift index 68db6280..dea4586f 100644 --- a/Meshtastic/Views/Messages/UserMessageList.swift +++ b/Meshtastic/Views/Messages/UserMessageList.swift @@ -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)") } } } diff --git a/Meshtastic/Views/Nodes/DetectionSensorLog.swift b/Meshtastic/Views/Nodes/DetectionSensorLog.swift index 1eeb499e..89c62be3 100644 --- a/Meshtastic/Views/Nodes/DetectionSensorLog.swift +++ b/Meshtastic/Views/Nodes/DetectionSensorLog.swift @@ -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).") } } ) diff --git a/Meshtastic/Views/Nodes/DeviceMetricsLog.swift b/Meshtastic/Views/Nodes/DeviceMetricsLog.swift index 9132d068..9ebce292 100644 --- a/Meshtastic/Views/Nodes/DeviceMetricsLog.swift +++ b/Meshtastic/Views/Nodes/DeviceMetricsLog.swift @@ -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)") } } ) diff --git a/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift b/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift index bdd544c8..7ae9bc50 100644 --- a/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift +++ b/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift @@ -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)") } } ) diff --git a/Meshtastic/Views/Nodes/Helpers/Actions/DeleteNodeButton.swift b/Meshtastic/Views/Nodes/Helpers/Actions/DeleteNodeButton.swift index d6adc165..127a31d7 100644 --- a/Meshtastic/Views/Nodes/Helpers/Actions/DeleteNodeButton.swift +++ b/Meshtastic/Views/Nodes/Helpers/Actions/DeleteNodeButton.swift @@ -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() } diff --git a/Meshtastic/Views/Nodes/Helpers/Actions/NavigateToButton.swift b/Meshtastic/Views/Nodes/Helpers/Actions/NavigateToButton.swift index e7a3567e..78917082 100644 --- a/Meshtastic/Views/Nodes/Helpers/Actions/NavigateToButton.swift +++ b/Meshtastic/Views/Nodes/Helpers/Actions/NavigateToButton.swift @@ -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 = 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 { diff --git a/Meshtastic/Views/Nodes/Helpers/Metrics Columns/EnviornmentDefaultSeries.swift b/Meshtastic/Views/Nodes/Helpers/Metrics Columns/EnviornmentDefaultSeries.swift deleted file mode 100644 index 8150f971..00000000 --- a/Meshtastic/Views/Nodes/Helpers/Metrics Columns/EnviornmentDefaultSeries.swift +++ /dev/null @@ -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(_ input: T, from inputRange: ClosedRange, to outputRange: ClosedRange) -> 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 -} diff --git a/Meshtastic/Views/Nodes/Helpers/Metrics Columns/EnvironmentDefaultColumns.swift b/Meshtastic/Views/Nodes/Helpers/Metrics Columns/EnvironmentDefaultColumns.swift index 245c2f9c..ffd86987 100644 --- a/Meshtastic/Views/Nodes/Helpers/Metrics Columns/EnvironmentDefaultColumns.swift +++ b/Meshtastic/Views/Nodes/Helpers/Metrics Columns/EnvironmentDefaultColumns.swift @@ -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", diff --git a/Meshtastic/Views/Nodes/Helpers/Metrics Columns/EnvironmentDefaultSeries.swift b/Meshtastic/Views/Nodes/Helpers/Metrics Columns/EnvironmentDefaultSeries.swift new file mode 100644 index 00000000..d3762845 --- /dev/null +++ b/Meshtastic/Views/Nodes/Helpers/Metrics Columns/EnvironmentDefaultSeries.swift @@ -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(_ input: T, from inputRange: ClosedRange, to outputRange: ClosedRange) -> 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 +} diff --git a/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift b/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift index 485bbdd6..e48a9acc 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift @@ -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) } diff --git a/Meshtastic/Views/Nodes/Helpers/NodeInfoItem.swift b/Meshtastic/Views/Nodes/Helpers/NodeInfoItem.swift index 9f3b8105..6a8f468a 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeInfoItem.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeInfoItem.swift @@ -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) } diff --git a/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift b/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift index 9819ef38..cabfabc6 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift @@ -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", diff --git a/Meshtastic/Views/Nodes/MeshMap.swift b/Meshtastic/Views/Nodes/MeshMap.swift index 65928ba3..a0650c41 100644 --- a/Meshtastic/Views/Nodes/MeshMap.swift +++ b/Meshtastic/Views/Nodes/MeshMap.swift @@ -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 } }) diff --git a/Meshtastic/Views/Nodes/NodeList.swift b/Meshtastic/Views/Nodes/NodeList.swift index 12c76cd2..34dbd475 100644 --- a/Meshtastic/Views/Nodes/NodeList.swift +++ b/Meshtastic/Views/Nodes/NodeList.swift @@ -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 { diff --git a/Meshtastic/Views/Nodes/PaxCounterLog.swift b/Meshtastic/Views/Nodes/PaxCounterLog.swift index 6be95332..37ca7c30 100644 --- a/Meshtastic/Views/Nodes/PaxCounterLog.swift +++ b/Meshtastic/Views/Nodes/PaxCounterLog.swift @@ -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)") } } ) diff --git a/Meshtastic/Views/Nodes/PositionLog.swift b/Meshtastic/Views/Nodes/PositionLog.swift index d5972a27..6c78d1c3 100644 --- a/Meshtastic/Views/Nodes/PositionLog.swift +++ b/Meshtastic/Views/Nodes/PositionLog.swift @@ -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)") } } ) diff --git a/Meshtastic/Views/Nodes/PowerMetricsLog.swift b/Meshtastic/Views/Nodes/PowerMetricsLog.swift index 3fd5582a..489184da 100644 --- a/Meshtastic/Views/Nodes/PowerMetricsLog.swift +++ b/Meshtastic/Views/Nodes/PowerMetricsLog.swift @@ -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") } @@ -272,7 +272,7 @@ struct PowerMetricsLog: View { ContentUnavailableView("No Power Metrics", systemImage: "slash.circle") } } - .navigationTitle("Power Metrics Log}") + .navigationTitle("Power Metrics Log") .navigationBarTitleDisplayMode(.inline) .navigationBarItems(trailing: ZStack { @@ -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)") } } ) diff --git a/Meshtastic/Views/Nodes/TraceRouteLog.swift b/Meshtastic/Views/Nodes/TraceRouteLog.swift index f10dad58..e4ad4add 100644 --- a/Meshtastic/Views/Nodes/TraceRouteLog.swift +++ b/Meshtastic/Views/Nodes/TraceRouteLog.swift @@ -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") diff --git a/Meshtastic/Views/Settings/AppData.swift b/Meshtastic/Views/Settings/AppData.swift index 3f1ebf0e..f1777989 100644 --- a/Meshtastic/Views/Settings/AppData.swift +++ b/Meshtastic/Views/Settings/AppData.swift @@ -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) diff --git a/Meshtastic/Views/Settings/AppLog.swift b/Meshtastic/Views/Settings/AppLog.swift index b96ed806..53a32e07 100644 --- a/Meshtastic/Views/Settings/AppLog.swift +++ b/Meshtastic/Views/Settings/AppLog.swift @@ -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)") } } ) diff --git a/Meshtastic/Views/Settings/Channels.swift b/Meshtastic/Views/Settings/Channels.swift index 6d439194..551b9ab4 100644 --- a/Meshtastic/Views/Settings/Channels.swift +++ b/Meshtastic/Views/Settings/Channels.swift @@ -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!) diff --git a/Meshtastic/Views/Settings/Config/DeviceConfig.swift b/Meshtastic/Views/Settings/Config/DeviceConfig.swift index 467b5901..abce6e9a 100644 --- a/Meshtastic/Views/Settings/Config/DeviceConfig.swift +++ b/Meshtastic/Views/Settings/Config/DeviceConfig.swift @@ -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) diff --git a/Meshtastic/Views/Settings/Config/LoRaConfig.swift b/Meshtastic/Views/Settings/Config/LoRaConfig.swift index 0948f36f..20371f31 100644 --- a/Meshtastic/Views/Settings/Config/LoRaConfig.swift +++ b/Meshtastic/Views/Settings/Config/LoRaConfig.swift @@ -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 { diff --git a/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift b/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift index 5114a7e4..29b06464 100644 --- a/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift @@ -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 } diff --git a/Meshtastic/Views/Settings/Config/Module/StoreForwardConfig.swift b/Meshtastic/Views/Settings/Config/Module/StoreForwardConfig.swift index f0718ac2..fe6abcd1 100644 --- a/Meshtastic/Views/Settings/Config/Module/StoreForwardConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/StoreForwardConfig.swift @@ -18,8 +18,8 @@ struct StoreForwardConfig: View { @State var hasChanges: Bool = false /// Enable the Store and Forward Module @State var enabled = false - /// Is a S&F Router - @State var isRouter = false + /// Is a S&F Server + @State var isServer = false /// Send a Heartbeat @State var heartbeat: Bool = false /// Number of Records @@ -35,43 +35,19 @@ struct StoreForwardConfig: View { ConfigHeader(title: "Store & Forward", config: \.storeForwardConfig, node: node, onAppear: setStoreAndForwardValues) Section(header: Text("options")) { - Toggle(isOn: $enabled) { Label("enabled", systemImage: "envelope.arrow.triangle.branch") - Text("Enables the store and forward module. Store and forward must be enabled on both client and router devices.") + Text("Enables the store and forward module.") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) .listRowSeparator(.visible) - if enabled { - HStack { - Picker(selection: $isRouter, label: Text("Role")) { - Text("Client") - .tag(false) - Text("Router") - .tag(true) - } - .pickerStyle(SegmentedPickerStyle()) - .padding(.top, 5) - .padding(.bottom, 5) - } - VStack { - if isRouter { - Text("Store and forward router devices require a ESP32 device with PSRAM.") - .foregroundColor(.gray) - .font(.callout) - } else { - Text("Store and forward clients can request history from routers on the network.") - .foregroundColor(.gray) - .font(.callout) - } - } - } } - if isRouter { - Section(header: Text("Router Options")) { + if enabled { + Section(header: Text("Settings")) { Toggle(isOn: $heartbeat) { Label("storeforward.heartbeat", systemImage: "waveform.path.ecg") + Text("Send a heartbeat to advertise the server's presence.") } Picker("Number of records", selection: $records) { Text("unset").tag(0) @@ -81,7 +57,7 @@ struct StoreForwardConfig: View { Text("100").tag(100) } .pickerStyle(DefaultPickerStyle()) - Picker("History Return Max", selection: $historyReturnMax ) { + Picker("History Return Max", selection: $historyReturnMax) { Text("unset").tag(0) Text("25").tag(25) Text("50").tag(50) @@ -89,7 +65,7 @@ struct StoreForwardConfig: View { Text("100").tag(100) } .pickerStyle(DefaultPickerStyle()) - Picker("History Return Window", selection: $historyReturnWindow ) { + Picker("History Return Window", selection: $historyReturnWindow) { Text("unset").tag(0) Text("One Minute").tag(60) Text("Five Minutes").tag(300) @@ -101,6 +77,20 @@ struct StoreForwardConfig: View { } .pickerStyle(DefaultPickerStyle()) } + + Section(header: Text("Server Option")) { + Toggle(isOn: $isServer) { + Label("Server", systemImage: "server.rack") + Text("Enable this device as a Store and Forward server. Requires an ESP32 device with PSRAM.") + } + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + .listRowSeparator(.visible) + if isServer { + Text("Store and forward servers require an ESP32 device with PSRAM or Linux Native.") + .foregroundColor(.gray) + .font(.callout) + } + } } } .scrollDismissesKeyboard(.interactively) @@ -110,18 +100,19 @@ struct StoreForwardConfig: View { SaveConfigButton(node: node, hasChanges: $hasChanges) { let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1, context: context) if connectedNode != nil { - /// Let the user set isRouter for the connected node, for nodes on the mesh set isRouter based + /// Let the user set isServer for the connected node, for nodes on the mesh set isServer based /// on receipt of a primary heartbeat if connectedNode?.num ?? 0 == node?.num ?? -1 { - connectedNode?.storeForwardConfig?.isRouter = isRouter + connectedNode?.storeForwardConfig?.isRouter = isServer do { try context.save() } catch { - Logger.mesh.error("Failed to save isRouter: \(error.localizedDescription)") + Logger.mesh.error("Failed to save isServer: \(error.localizedDescription, privacy: .public)") } } var sfc = ModuleConfig.StoreForwardConfig() + sfc.isServer = isServer sfc.enabled = self.enabled sfc.heartbeat = self.heartbeat sfc.records = UInt32(self.records) @@ -171,8 +162,8 @@ struct StoreForwardConfig: View { .onChange(of: enabled) { oldEnabled, newEnabled in if oldEnabled != newEnabled && newEnabled != node!.storeForwardConfig!.enabled { hasChanges = true } } - .onChange(of: isRouter) { oldIsRouter, newIsRouter in - if oldIsRouter != newIsRouter && newIsRouter != node!.storeForwardConfig!.isRouter { hasChanges = true } + .onChange(of: isServer) { oldIsServer, newIsServer in + if oldIsServer != newIsServer && newIsServer != node!.storeForwardConfig!.isRouter { hasChanges = true } } .onChange(of: heartbeat) { oldHeartbeat, newHeartbeat in if oldHeartbeat != newHeartbeat && newHeartbeat != node?.storeForwardConfig?.heartbeat ?? true { hasChanges = true } @@ -187,9 +178,10 @@ struct StoreForwardConfig: View { if oldHistoryReturnWindow != newHistoryReturnWindow && newHistoryReturnWindow != node!.storeForwardConfig?.historyReturnWindow ?? -1 { hasChanges = true } } } + func setStoreAndForwardValues() { self.enabled = (node?.storeForwardConfig?.enabled ?? false) - self.isRouter = (node?.storeForwardConfig?.isRouter ?? false) + self.isServer = (node?.storeForwardConfig?.isRouter ?? false) self.heartbeat = (node?.storeForwardConfig?.heartbeat ?? true) self.records = Int(node?.storeForwardConfig?.records ?? 50) self.historyReturnMax = Int(node?.storeForwardConfig?.historyReturnMax ?? 100) diff --git a/Meshtastic/Views/Settings/Config/NetworkConfig.swift b/Meshtastic/Views/Settings/Config/NetworkConfig.swift index 84f48c41..c63e95be 100644 --- a/Meshtastic/Views/Settings/Config/NetworkConfig.swift +++ b/Meshtastic/Views/Settings/Config/NetworkConfig.swift @@ -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 } } diff --git a/Meshtastic/Views/Settings/Config/PositionConfig.swift b/Meshtastic/Views/Settings/Config/PositionConfig.swift index f963b02b..db0574c7 100644 --- a/Meshtastic/Views/Settings/Config/PositionConfig.swift +++ b/Meshtastic/Views/Settings/Config/PositionConfig.swift @@ -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)") } } } diff --git a/Meshtastic/Views/Settings/Config/SecurityConfig.swift b/Meshtastic/Views/Settings/Config/SecurityConfig.swift index 6f16c8ff..938a9202 100644 --- a/Meshtastic/Views/Settings/Config/SecurityConfig.swift +++ b/Meshtastic/Views/Settings/Config/SecurityConfig.swift @@ -22,12 +22,11 @@ struct SecurityConfig: View { @State var hasChanges = false @State var publicKey = "" - @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 @@ -45,11 +44,14 @@ struct SecurityConfig: View { Section(header: Text("Admin & Direct Message Keys")) { VStack(alignment: .leading) { Label("Public Key", systemImage: "key") - SecureInput("Public Key", text: $publicKey, isValid: $hasValidPublicKey) - .background( - RoundedRectangle(cornerRadius: 10.0) - .stroke(hasValidPublicKey ? Color.clear : Color.red, lineWidth: 2.0) - ) + Text(publicKey) + .font(idiom == .phone ? .caption : .callout) + .allowsTightening(true) + .monospaced() + .keyboardType(.alphabet) + .foregroundStyle(.tertiary) + .disableAutocorrection(true) + .textSelection(.enabled) Text("Sent out to other nodes on the mesh to allow them to compute a shared secret key.") .foregroundStyle(.secondary) .font(idiom == .phone ? .caption : .callout) @@ -144,15 +146,6 @@ struct SecurityConfig: View { .onChange(of: adminChannelEnabled) { _, newAdminChannelEnabled in if newAdminChannelEnabled != node?.securityConfig?.adminChannelEnabled { hasChanges = true } } - .onChange(of: publicKey) { - let tempKey = Data(base64Encoded: publicKey) ?? Data() - if tempKey.count == 32 { - hasValidPublicKey = true - } else { - hasValidPublicKey = false - } - hasChanges = true - } .onChange(of: privateKey) { let tempKey = Data(base64Encoded: privateKey) ?? Data() if tempKey.count == 32 { @@ -222,7 +215,7 @@ struct SecurityConfig: View { SaveConfigButton(node: node, hasChanges: $hasChanges) { - if !hasValidPublicKey || !hasValidPrivateKey || !hasValidAdminKey { + if !hasValidPrivateKey || !hasValidAdminKey || !hasValidAdminKey2 || !hasValidAdminKey3 { return } @@ -259,9 +252,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 diff --git a/Meshtastic/Views/Settings/Firmware.swift b/Meshtastic/Views/Settings/Firmware.swift index 76922d54..b0c9c08f 100644 --- a/Meshtastic/Views/Settings/Firmware.swift +++ b/Meshtastic/Views/Settings/Firmware.swift @@ -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) diff --git a/Meshtastic/Views/Settings/FirmwareApi.swift b/Meshtastic/Views/Settings/FirmwareApi.swift index 3b64c1e9..9daae230 100644 --- a/Meshtastic/Views/Settings/FirmwareApi.swift +++ b/Meshtastic/Views/Settings/FirmwareApi.swift @@ -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 } diff --git a/Meshtastic/Views/Settings/RouteRecorder.swift b/Meshtastic/Views/Settings/RouteRecorder.swift index 9a5b90c5..8cee2eeb 100644 --- a/Meshtastic/Views/Settings/RouteRecorder.swift +++ b/Meshtastic/Views/Settings/RouteRecorder.swift @@ -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)") } } } diff --git a/Meshtastic/Views/Settings/Routes.swift b/Meshtastic/Views/Settings/Routes.swift index 91638ab6..52b00fa0 100644 --- a/Meshtastic/Views/Settings/Routes.swift +++ b/Meshtastic/Views/Settings/Routes.swift @@ -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).") } } ) diff --git a/Meshtastic/Views/Settings/Settings.swift b/Meshtastic/Views/Settings/Settings.swift index abf8a0ec..8ebcfd83 100644 --- a/Meshtastic/Views/Settings/Settings.swift +++ b/Meshtastic/Views/Settings/Settings.swift @@ -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() + } ) } } diff --git a/Meshtastic/Views/Settings/UserConfig.swift b/Meshtastic/Views/Settings/UserConfig.swift index ea64e36e..644c0077 100644 --- a/Meshtastic/Views/Settings/UserConfig.swift +++ b/Meshtastic/Views/Settings/UserConfig.swift @@ -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) diff --git a/MeshtasticProtobufs/Sources/meshtastic/admin.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/admin.pb.swift index 1f51447d..5a9a75a5 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/admin.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/admin.pb.swift @@ -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 { diff --git a/MeshtasticProtobufs/Sources/meshtastic/apponly.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/apponly.pb.swift index 0457077c..52dac5ca 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/apponly.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/apponly.pb.swift @@ -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" diff --git a/MeshtasticProtobufs/Sources/meshtastic/atak.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/atak.pb.swift index 867648a9..06d6af88 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/atak.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/atak.pb.swift @@ -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 /// -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 /// -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" diff --git a/MeshtasticProtobufs/Sources/meshtastic/cannedmessages.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/cannedmessages.pb.swift index 1b8c84de..ce1f0503 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/cannedmessages.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/cannedmessages.pb.swift @@ -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" diff --git a/MeshtasticProtobufs/Sources/meshtastic/channel.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/channel.pb.swift index 5b9c7e49..180cd698 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/channel.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/channel.pb.swift @@ -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" diff --git a/MeshtasticProtobufs/Sources/meshtastic/clientonly.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/clientonly.pb.swift index f89a8e3c..d72c0ae1 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/clientonly.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/clientonly.pb.swift @@ -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" diff --git a/MeshtasticProtobufs/Sources/meshtastic/config.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/config.pb.swift index a5f8cbc5..b3988aaa 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/config.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/config.pb.swift @@ -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(decoder: inout D) throws { - while let _ = try decoder.nextFieldNumber() { - } + // Load everything into unknown fields + while try decoder.nextFieldNumber() != nil {} } public func traverse(visitor: inout V) throws { diff --git a/MeshtasticProtobufs/Sources/meshtastic/connection_status.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/connection_status.pb.swift index a2ec180e..6847c0e3 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/connection_status.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/connection_status.pb.swift @@ -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" diff --git a/MeshtasticProtobufs/Sources/meshtastic/device_ui.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/device_ui.pb.swift index 5e13b166..0281d6c1 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/device_ui.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/device_ui.pb.swift @@ -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/device_ui.proto @@ -20,7 +21,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP typealias Version = _2 } -public enum Theme: SwiftProtobuf.Enum { +public enum Theme: SwiftProtobuf.Enum, Swift.CaseIterable { public typealias RawValue = Int /// @@ -58,24 +59,18 @@ public enum Theme: SwiftProtobuf.Enum { } } -} - -#if swift(>=4.2) - -extension Theme: CaseIterable { // The compiler won't synthesize support with the UNRECOGNIZED case. public static let allCases: [Theme] = [ .dark, .light, .red, ] -} -#endif // swift(>=4.2) +} /// /// Localization -public enum Language: SwiftProtobuf.Enum { +public enum Language: SwiftProtobuf.Enum, Swift.CaseIterable { public typealias RawValue = Int /// @@ -203,11 +198,6 @@ public enum Language: SwiftProtobuf.Enum { } } -} - -#if swift(>=4.2) - -extension Language: CaseIterable { // The compiler won't synthesize support with the UNRECOGNIZED case. public static let allCases: [Language] = [ .english, @@ -229,11 +219,10 @@ extension Language: CaseIterable { .simplifiedChinese, .traditionalChinese, ] + } -#endif // swift(>=4.2) - -public struct DeviceUIConfig { +public struct DeviceUIConfig: @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. @@ -246,21 +235,21 @@ public struct DeviceUIConfig { } /// - /// TFT display brightness 1..255 + /// TFT display brightness 1..255 public var screenBrightness: UInt32 { get {return _storage._screenBrightness} set {_uniqueStorage()._screenBrightness = newValue} } /// - /// Screen timeout 0..900 + /// Screen timeout 0..900 public var screenTimeout: UInt32 { get {return _storage._screenTimeout} set {_uniqueStorage()._screenTimeout = newValue} } /// - /// Screen/Settings lock enabled + /// Screen/Settings lock enabled public var screenLock: Bool { get {return _storage._screenLock} set {_uniqueStorage()._screenLock = newValue} @@ -277,7 +266,7 @@ public struct DeviceUIConfig { } /// - /// Color theme + /// Color theme public var theme: Theme { get {return _storage._theme} set {_uniqueStorage()._theme = newValue} @@ -301,14 +290,14 @@ public struct DeviceUIConfig { } /// - /// Localization + /// Localization public var language: Language { get {return _storage._language} set {_uniqueStorage()._language = newValue} } /// - /// Node list filter + /// Node list filter public var nodeFilter: NodeFilter { get {return _storage._nodeFilter ?? NodeFilter()} set {_uniqueStorage()._nodeFilter = newValue} @@ -336,6 +325,17 @@ public struct DeviceUIConfig { set {_uniqueStorage()._calibrationData = newValue} } + /// + /// Map related data + public var mapData: Map { + get {return _storage._mapData ?? Map()} + set {_uniqueStorage()._mapData = newValue} + } + /// Returns true if `mapData` has been explicitly set. + public var hasMapData: Bool {return _storage._mapData != nil} + /// Clears the value of `mapData`. Subsequent reads from it will return its default value. + public mutating func clearMapData() {_uniqueStorage()._mapData = nil} + public var unknownFields = SwiftProtobuf.UnknownStorage() public init() {} @@ -343,7 +343,7 @@ public struct DeviceUIConfig { fileprivate var _storage = _StorageClass.defaultInstance } -public struct NodeFilter { +public struct NodeFilter: 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. @@ -381,7 +381,7 @@ public struct NodeFilter { public init() {} } -public struct NodeHighlight { +public struct NodeHighlight: 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. @@ -411,13 +411,58 @@ public struct NodeHighlight { public init() {} } -#if swift(>=5.5) && canImport(_Concurrency) -extension Theme: @unchecked Sendable {} -extension Language: @unchecked Sendable {} -extension DeviceUIConfig: @unchecked Sendable {} -extension NodeFilter: @unchecked Sendable {} -extension NodeHighlight: @unchecked Sendable {} -#endif // swift(>=5.5) && canImport(_Concurrency) +public struct GeoPoint: 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. + + /// + /// Zoom level + public var zoom: Int32 = 0 + + /// + /// Coordinate: latitude + public var latitude: Int32 = 0 + + /// + /// Coordinate: longitude + public var longitude: Int32 = 0 + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} +} + +public struct Map: 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. + + /// + /// Home coordinates + public var home: GeoPoint { + get {return _home ?? GeoPoint()} + set {_home = newValue} + } + /// Returns true if `home` has been explicitly set. + public var hasHome: Bool {return self._home != nil} + /// Clears the value of `home`. Subsequent reads from it will return its default value. + public mutating func clearHome() {self._home = nil} + + /// + /// Map tile style + public var style: String = String() + + /// + /// Map scroll follows GPS + public var followGps: Bool = false + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _home: GeoPoint? = nil +} // MARK: - Code below here is support for the SwiftProtobuf runtime. @@ -471,6 +516,7 @@ extension DeviceUIConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplement 12: .standard(proto: "node_filter"), 13: .standard(proto: "node_highlight"), 14: .standard(proto: "calibration_data"), + 15: .standard(proto: "map_data"), ] fileprivate class _StorageClass { @@ -488,6 +534,7 @@ extension DeviceUIConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplement var _nodeFilter: NodeFilter? = nil var _nodeHighlight: NodeHighlight? = nil var _calibrationData: Data = Data() + var _mapData: Map? = nil #if swift(>=5.10) // This property is used as the initial default value for new instances of the type. @@ -516,6 +563,7 @@ extension DeviceUIConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplement _nodeFilter = source._nodeFilter _nodeHighlight = source._nodeHighlight _calibrationData = source._calibrationData + _mapData = source._mapData } } @@ -548,6 +596,7 @@ extension DeviceUIConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplement case 12: try { try decoder.decodeSingularMessageField(value: &_storage._nodeFilter) }() case 13: try { try decoder.decodeSingularMessageField(value: &_storage._nodeHighlight) }() case 14: try { try decoder.decodeSingularBytesField(value: &_storage._calibrationData) }() + case 15: try { try decoder.decodeSingularMessageField(value: &_storage._mapData) }() default: break } } @@ -602,6 +651,9 @@ extension DeviceUIConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplement if !_storage._calibrationData.isEmpty { try visitor.visitSingularBytesField(value: _storage._calibrationData, fieldNumber: 14) } + try { if let v = _storage._mapData { + try visitor.visitSingularMessageField(value: v, fieldNumber: 15) + } }() } try unknownFields.traverse(visitor: &visitor) } @@ -625,6 +677,7 @@ extension DeviceUIConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplement if _storage._nodeFilter != rhs_storage._nodeFilter {return false} if _storage._nodeHighlight != rhs_storage._nodeHighlight {return false} if _storage._calibrationData != rhs_storage._calibrationData {return false} + if _storage._mapData != rhs_storage._mapData {return false} return true } if !storagesAreEqual {return false} @@ -757,3 +810,95 @@ extension NodeHighlight: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementa return true } } + +extension GeoPoint: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".GeoPoint" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "zoom"), + 2: .same(proto: "latitude"), + 3: .same(proto: "longitude"), + ] + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // 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 fieldNumber { + case 1: try { try decoder.decodeSingularInt32Field(value: &self.zoom) }() + case 2: try { try decoder.decodeSingularInt32Field(value: &self.latitude) }() + case 3: try { try decoder.decodeSingularInt32Field(value: &self.longitude) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if self.zoom != 0 { + try visitor.visitSingularInt32Field(value: self.zoom, fieldNumber: 1) + } + if self.latitude != 0 { + try visitor.visitSingularInt32Field(value: self.latitude, fieldNumber: 2) + } + if self.longitude != 0 { + try visitor.visitSingularInt32Field(value: self.longitude, fieldNumber: 3) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: GeoPoint, rhs: GeoPoint) -> Bool { + if lhs.zoom != rhs.zoom {return false} + if lhs.latitude != rhs.latitude {return false} + if lhs.longitude != rhs.longitude {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Map: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".Map" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "home"), + 2: .same(proto: "style"), + 3: .standard(proto: "follow_gps"), + ] + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // 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 fieldNumber { + case 1: try { try decoder.decodeSingularMessageField(value: &self._home) }() + case 2: try { try decoder.decodeSingularStringField(value: &self.style) }() + case 3: try { try decoder.decodeSingularBoolField(value: &self.followGps) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._home { + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + } }() + if !self.style.isEmpty { + try visitor.visitSingularStringField(value: self.style, fieldNumber: 2) + } + if self.followGps != false { + try visitor.visitSingularBoolField(value: self.followGps, fieldNumber: 3) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Map, rhs: Map) -> Bool { + if lhs._home != rhs._home {return false} + if lhs.style != rhs.style {return false} + if lhs.followGps != rhs.followGps {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} diff --git a/MeshtasticProtobufs/Sources/meshtastic/deviceonly.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/deviceonly.pb.swift index 34a33373..72248719 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/deviceonly.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/deviceonly.pb.swift @@ -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/deviceonly.proto @@ -22,7 +23,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP /// /// Position with static location information only for NodeDBLite -public struct PositionLite { +public struct PositionLite: 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. @@ -57,13 +58,15 @@ public struct PositionLite { public init() {} } -public struct UserLite { +public struct UserLite: @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. /// /// This is the addr of the radio. + /// + /// NOTE: This field was marked as deprecated in the .proto file. public var macaddr: Data = Data() /// @@ -102,7 +105,7 @@ public struct UserLite { public init() {} } -public struct NodeInfoLite { +public struct NodeInfoLite: @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. @@ -205,7 +208,7 @@ public struct NodeInfoLite { } /// - /// Last byte of the node number of the node that should be used as the next hop to reach this node. + /// Last byte of the node number of the node that should be used as the next hop to reach this node. public var nextHop: UInt32 { get {return _storage._nextHop} set {_uniqueStorage()._nextHop = newValue} @@ -224,7 +227,7 @@ public struct NodeInfoLite { /// FIXME, since we write this each time we enter deep sleep (and have infinite /// flash) it would be better to use some sort of append only data structure for /// the receive queue and use the preferences store for the other stuff -public struct DeviceState { +public struct DeviceState: @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. @@ -284,13 +287,18 @@ public struct DeviceState { /// Used only during development. /// Indicates developer is testing and changes should never be saved to flash. /// Deprecated in 2.3.1 + /// + /// NOTE: This field was marked as deprecated in the .proto file. public var noSave: Bool { get {return _storage._noSave} set {_uniqueStorage()._noSave = newValue} } /// - /// Some GPS receivers seem to have bogus settings from the factory, so we always do one factory reset. + /// Previously used to manage GPS factory resets. + /// Deprecated in 2.5.23 + /// + /// NOTE: This field was marked as deprecated in the .proto file. public var didGpsReset: Bool { get {return _storage._didGpsReset} set {_uniqueStorage()._didGpsReset = newValue} @@ -316,13 +324,6 @@ public struct DeviceState { set {_uniqueStorage()._nodeRemoteHardwarePins = newValue} } - /// - /// New lite version of NodeDB to decrease memory footprint - public var nodeDbLite: [NodeInfoLite] { - get {return _storage._nodeDbLite} - set {_uniqueStorage()._nodeDbLite = newValue} - } - public var unknownFields = SwiftProtobuf.UnknownStorage() public init() {} @@ -330,9 +331,29 @@ public struct DeviceState { fileprivate var _storage = _StorageClass.defaultInstance } +public struct NodeDatabase: 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. + + /// + /// A version integer used to invalidate old save files when we make + /// incompatible changes This integer is set at build time and is private to + /// NodeDB.cpp in the device code. + public var version: UInt32 = 0 + + /// + /// New lite version of NodeDB to decrease memory footprint + public var nodes: [NodeInfoLite] = [] + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} +} + /// /// The on-disk saved channels -public struct ChannelFile { +public struct ChannelFile: 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. @@ -352,13 +373,74 @@ public struct ChannelFile { public init() {} } -#if swift(>=5.5) && canImport(_Concurrency) -extension PositionLite: @unchecked Sendable {} -extension UserLite: @unchecked Sendable {} -extension NodeInfoLite: @unchecked Sendable {} -extension DeviceState: @unchecked Sendable {} -extension ChannelFile: @unchecked Sendable {} -#endif // swift(>=5.5) && canImport(_Concurrency) +/// +/// The on-disk backup of the node's preferences +public struct BackupPreferences: 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. + + /// + /// The version of the backup + public var version: UInt32 = 0 + + /// + /// The timestamp of the backup (if node has time) + public var timestamp: UInt32 = 0 + + /// + /// The node's configuration + public var config: LocalConfig { + get {return _config ?? LocalConfig()} + set {_config = newValue} + } + /// Returns true if `config` has been explicitly set. + public var hasConfig: Bool {return self._config != nil} + /// Clears the value of `config`. Subsequent reads from it will return its default value. + public mutating func clearConfig() {self._config = nil} + + /// + /// The node's module configuration + public var moduleConfig: LocalModuleConfig { + get {return _moduleConfig ?? LocalModuleConfig()} + set {_moduleConfig = newValue} + } + /// Returns true if `moduleConfig` has been explicitly set. + public var hasModuleConfig: Bool {return self._moduleConfig != nil} + /// Clears the value of `moduleConfig`. Subsequent reads from it will return its default value. + public mutating func clearModuleConfig() {self._moduleConfig = nil} + + /// + /// The node's channels + public var channels: ChannelFile { + get {return _channels ?? ChannelFile()} + set {_channels = newValue} + } + /// Returns true if `channels` has been explicitly set. + public var hasChannels: Bool {return self._channels != nil} + /// Clears the value of `channels`. Subsequent reads from it will return its default value. + public mutating func clearChannels() {self._channels = nil} + + /// + /// The node's user (owner) information + public var owner: User { + get {return _owner ?? User()} + set {_owner = newValue} + } + /// Returns true if `owner` has been explicitly set. + public var hasOwner: Bool {return self._owner != nil} + /// Clears the value of `owner`. Subsequent reads from it will return its default value. + public mutating func clearOwner() {self._owner = nil} + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _config: LocalConfig? = nil + fileprivate var _moduleConfig: LocalModuleConfig? = nil + fileprivate var _channels: ChannelFile? = nil + fileprivate var _owner: User? = nil +} // MARK: - Code below here is support for the SwiftProtobuf runtime. @@ -595,7 +677,7 @@ extension NodeInfoLite: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat try { if let v = _storage._position { try visitor.visitSingularMessageField(value: v, fieldNumber: 3) } }() - if _storage._snr != 0 { + if _storage._snr.bitPattern != 0 { try visitor.visitSingularFloatField(value: _storage._snr, fieldNumber: 4) } if _storage._lastHeard != 0 { @@ -664,7 +746,6 @@ extension DeviceState: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementati 11: .standard(proto: "did_gps_reset"), 12: .standard(proto: "rx_waypoint"), 13: .standard(proto: "node_remote_hardware_pins"), - 14: .standard(proto: "node_db_lite"), ] fileprivate class _StorageClass { @@ -677,7 +758,6 @@ extension DeviceState: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementati var _didGpsReset: Bool = false var _rxWaypoint: MeshPacket? = nil var _nodeRemoteHardwarePins: [NodeRemoteHardwarePin] = [] - var _nodeDbLite: [NodeInfoLite] = [] #if swift(>=5.10) // This property is used as the initial default value for new instances of the type. @@ -701,7 +781,6 @@ extension DeviceState: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementati _didGpsReset = source._didGpsReset _rxWaypoint = source._rxWaypoint _nodeRemoteHardwarePins = source._nodeRemoteHardwarePins - _nodeDbLite = source._nodeDbLite } } @@ -729,7 +808,6 @@ extension DeviceState: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementati case 11: try { try decoder.decodeSingularBoolField(value: &_storage._didGpsReset) }() case 12: try { try decoder.decodeSingularMessageField(value: &_storage._rxWaypoint) }() case 13: try { try decoder.decodeRepeatedMessageField(value: &_storage._nodeRemoteHardwarePins) }() - case 14: try { try decoder.decodeRepeatedMessageField(value: &_storage._nodeDbLite) }() default: break } } @@ -769,9 +847,6 @@ extension DeviceState: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementati if !_storage._nodeRemoteHardwarePins.isEmpty { try visitor.visitRepeatedMessageField(value: _storage._nodeRemoteHardwarePins, fieldNumber: 13) } - if !_storage._nodeDbLite.isEmpty { - try visitor.visitRepeatedMessageField(value: _storage._nodeDbLite, fieldNumber: 14) - } } try unknownFields.traverse(visitor: &visitor) } @@ -790,7 +865,6 @@ extension DeviceState: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementati if _storage._didGpsReset != rhs_storage._didGpsReset {return false} if _storage._rxWaypoint != rhs_storage._rxWaypoint {return false} if _storage._nodeRemoteHardwarePins != rhs_storage._nodeRemoteHardwarePins {return false} - if _storage._nodeDbLite != rhs_storage._nodeDbLite {return false} return true } if !storagesAreEqual {return false} @@ -800,6 +874,44 @@ extension DeviceState: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementati } } +extension NodeDatabase: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".NodeDatabase" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "version"), + 2: .same(proto: "nodes"), + ] + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // 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 fieldNumber { + case 1: try { try decoder.decodeSingularUInt32Field(value: &self.version) }() + case 2: try { try decoder.decodeRepeatedMessageField(value: &self.nodes) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if self.version != 0 { + try visitor.visitSingularUInt32Field(value: self.version, fieldNumber: 1) + } + if !self.nodes.isEmpty { + try visitor.visitRepeatedMessageField(value: self.nodes, fieldNumber: 2) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: NodeDatabase, rhs: NodeDatabase) -> Bool { + if lhs.version != rhs.version {return false} + if lhs.nodes != rhs.nodes {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + extension ChannelFile: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".ChannelFile" public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ @@ -837,3 +949,69 @@ extension ChannelFile: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementati return true } } + +extension BackupPreferences: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".BackupPreferences" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "version"), + 2: .same(proto: "timestamp"), + 3: .same(proto: "config"), + 4: .standard(proto: "module_config"), + 5: .same(proto: "channels"), + 6: .same(proto: "owner"), + ] + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // 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 fieldNumber { + case 1: try { try decoder.decodeSingularUInt32Field(value: &self.version) }() + case 2: try { try decoder.decodeSingularFixed32Field(value: &self.timestamp) }() + case 3: try { try decoder.decodeSingularMessageField(value: &self._config) }() + case 4: try { try decoder.decodeSingularMessageField(value: &self._moduleConfig) }() + case 5: try { try decoder.decodeSingularMessageField(value: &self._channels) }() + case 6: try { try decoder.decodeSingularMessageField(value: &self._owner) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + if self.version != 0 { + try visitor.visitSingularUInt32Field(value: self.version, fieldNumber: 1) + } + if self.timestamp != 0 { + try visitor.visitSingularFixed32Field(value: self.timestamp, fieldNumber: 2) + } + try { if let v = self._config { + try visitor.visitSingularMessageField(value: v, fieldNumber: 3) + } }() + try { if let v = self._moduleConfig { + try visitor.visitSingularMessageField(value: v, fieldNumber: 4) + } }() + try { if let v = self._channels { + try visitor.visitSingularMessageField(value: v, fieldNumber: 5) + } }() + try { if let v = self._owner { + try visitor.visitSingularMessageField(value: v, fieldNumber: 6) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: BackupPreferences, rhs: BackupPreferences) -> Bool { + if lhs.version != rhs.version {return false} + if lhs.timestamp != rhs.timestamp {return false} + if lhs._config != rhs._config {return false} + if lhs._moduleConfig != rhs._moduleConfig {return false} + if lhs._channels != rhs._channels {return false} + if lhs._owner != rhs._owner {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} diff --git a/MeshtasticProtobufs/Sources/meshtastic/interdevice.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/interdevice.pb.swift new file mode 100644 index 00000000..165ed685 --- /dev/null +++ b/MeshtasticProtobufs/Sources/meshtastic/interdevice.pb.swift @@ -0,0 +1,328 @@ +// DO NOT EDIT. +// swift-format-ignore-file +// swiftlint:disable all +// +// Generated by the Swift generator plugin for the protocol buffer compiler. +// Source: meshtastic/interdevice.proto +// +// For information on using the generated types, please see the documentation: +// https://github.com/apple/swift-protobuf/ + +import SwiftProtobuf + +// If the compiler emits an error on this type, it is because this file +// was generated by a version of the `protoc` Swift plug-in that is +// incompatible with the version of SwiftProtobuf to which you are linking. +// Please ensure that you are building against the same version of the API +// that was used to generate this file. +fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { + struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {} + typealias Version = _2 +} + +public enum MessageType: SwiftProtobuf.Enum, Swift.CaseIterable { + public typealias RawValue = Int + case ack // = 0 + + /// in ms + case collectInterval // = 160 + + /// duration ms + case beepOn // = 161 + + /// cancel prematurely + case beepOff // = 162 + case shutdown // = 163 + case powerOn // = 164 + case scd41Temp // = 176 + case scd41Humidity // = 177 + case scd41Co2 // = 178 + case aht20Temp // = 179 + case aht20Humidity // = 180 + case tvocIndex // = 181 + case UNRECOGNIZED(Int) + + public init() { + self = .ack + } + + public init?(rawValue: Int) { + switch rawValue { + case 0: self = .ack + case 160: self = .collectInterval + case 161: self = .beepOn + case 162: self = .beepOff + case 163: self = .shutdown + case 164: self = .powerOn + case 176: self = .scd41Temp + case 177: self = .scd41Humidity + case 178: self = .scd41Co2 + case 179: self = .aht20Temp + case 180: self = .aht20Humidity + case 181: self = .tvocIndex + default: self = .UNRECOGNIZED(rawValue) + } + } + + public var rawValue: Int { + switch self { + case .ack: return 0 + case .collectInterval: return 160 + case .beepOn: return 161 + case .beepOff: return 162 + case .shutdown: return 163 + case .powerOn: return 164 + case .scd41Temp: return 176 + case .scd41Humidity: return 177 + case .scd41Co2: return 178 + case .aht20Temp: return 179 + case .aht20Humidity: return 180 + case .tvocIndex: return 181 + case .UNRECOGNIZED(let i): return i + } + } + + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [MessageType] = [ + .ack, + .collectInterval, + .beepOn, + .beepOff, + .shutdown, + .powerOn, + .scd41Temp, + .scd41Humidity, + .scd41Co2, + .aht20Temp, + .aht20Humidity, + .tvocIndex, + ] + +} + +public struct SensorData: 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. + + /// The message type + public var type: MessageType = .ack + + /// The sensor data, either as a float or an uint32 + public var data: SensorData.OneOf_Data? = nil + + public var floatValue: Float { + get { + if case .floatValue(let v)? = data {return v} + return 0 + } + set {data = .floatValue(newValue)} + } + + public var uint32Value: UInt32 { + get { + if case .uint32Value(let v)? = data {return v} + return 0 + } + set {data = .uint32Value(newValue)} + } + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + /// The sensor data, either as a float or an uint32 + public enum OneOf_Data: Equatable, Sendable { + case floatValue(Float) + case uint32Value(UInt32) + + } + + public init() {} +} + +public struct InterdeviceMessage: 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. + + /// The message data + public var data: InterdeviceMessage.OneOf_Data? = nil + + public var nmea: String { + get { + if case .nmea(let v)? = data {return v} + return String() + } + set {data = .nmea(newValue)} + } + + public var sensor: SensorData { + get { + if case .sensor(let v)? = data {return v} + return SensorData() + } + set {data = .sensor(newValue)} + } + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + /// The message data + public enum OneOf_Data: Equatable, Sendable { + case nmea(String) + case sensor(SensorData) + + } + + public init() {} +} + +// MARK: - Code below here is support for the SwiftProtobuf runtime. + +fileprivate let _protobuf_package = "meshtastic" + +extension MessageType: SwiftProtobuf._ProtoNameProviding { + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "ACK"), + 160: .same(proto: "COLLECT_INTERVAL"), + 161: .same(proto: "BEEP_ON"), + 162: .same(proto: "BEEP_OFF"), + 163: .same(proto: "SHUTDOWN"), + 164: .same(proto: "POWER_ON"), + 176: .same(proto: "SCD41_TEMP"), + 177: .same(proto: "SCD41_HUMIDITY"), + 178: .same(proto: "SCD41_CO2"), + 179: .same(proto: "AHT20_TEMP"), + 180: .same(proto: "AHT20_HUMIDITY"), + 181: .same(proto: "TVOC_INDEX"), + ] +} + +extension SensorData: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".SensorData" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "type"), + 2: .standard(proto: "float_value"), + 3: .standard(proto: "uint32_value"), + ] + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // 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 fieldNumber { + case 1: try { try decoder.decodeSingularEnumField(value: &self.type) }() + case 2: try { + var v: Float? + try decoder.decodeSingularFloatField(value: &v) + if let v = v { + if self.data != nil {try decoder.handleConflictingOneOf()} + self.data = .floatValue(v) + } + }() + case 3: try { + var v: UInt32? + try decoder.decodeSingularUInt32Field(value: &v) + if let v = v { + if self.data != nil {try decoder.handleConflictingOneOf()} + self.data = .uint32Value(v) + } + }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + if self.type != .ack { + try visitor.visitSingularEnumField(value: self.type, fieldNumber: 1) + } + switch self.data { + case .floatValue?: try { + guard case .floatValue(let v)? = self.data else { preconditionFailure() } + try visitor.visitSingularFloatField(value: v, fieldNumber: 2) + }() + case .uint32Value?: try { + guard case .uint32Value(let v)? = self.data else { preconditionFailure() } + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 3) + }() + case nil: break + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: SensorData, rhs: SensorData) -> Bool { + if lhs.type != rhs.type {return false} + if lhs.data != rhs.data {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension InterdeviceMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".InterdeviceMessage" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "nmea"), + 2: .same(proto: "sensor"), + ] + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // 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 fieldNumber { + case 1: try { + var v: String? + try decoder.decodeSingularStringField(value: &v) + if let v = v { + if self.data != nil {try decoder.handleConflictingOneOf()} + self.data = .nmea(v) + } + }() + case 2: try { + var v: SensorData? + var hadOneofValue = false + if let current = self.data { + hadOneofValue = true + if case .sensor(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.data = .sensor(v) + } + }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + switch self.data { + case .nmea?: try { + guard case .nmea(let v)? = self.data else { preconditionFailure() } + try visitor.visitSingularStringField(value: v, fieldNumber: 1) + }() + case .sensor?: try { + guard case .sensor(let v)? = self.data else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + }() + case nil: break + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: InterdeviceMessage, rhs: InterdeviceMessage) -> Bool { + if lhs.data != rhs.data {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} diff --git a/MeshtasticProtobufs/Sources/meshtastic/localonly.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/localonly.pb.swift index 0af27466..c3356286 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/localonly.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/localonly.pb.swift @@ -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/localonly.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 LocalConfig { +public struct LocalConfig: @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. @@ -129,7 +129,7 @@ public struct LocalConfig { fileprivate var _storage = _StorageClass.defaultInstance } -public struct LocalModuleConfig { +public struct LocalModuleConfig: @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. @@ -293,11 +293,6 @@ public struct LocalModuleConfig { fileprivate var _storage = _StorageClass.defaultInstance } -#if swift(>=5.5) && canImport(_Concurrency) -extension LocalConfig: @unchecked Sendable {} -extension LocalModuleConfig: @unchecked Sendable {} -#endif // swift(>=5.5) && canImport(_Concurrency) - // MARK: - Code below here is support for the SwiftProtobuf runtime. fileprivate let _protobuf_package = "meshtastic" diff --git a/MeshtasticProtobufs/Sources/meshtastic/mesh.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/mesh.pb.swift index bea1d14d..8c869ba8 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/mesh.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/mesh.pb.swift @@ -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/mesh.proto @@ -25,7 +26,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP /// bin/build-all.sh script. /// Because they will be used to find firmware filenames in the android app for OTA updates. /// To match the old style filenames, _ is converted to -, p is converted to . -public enum HardwareModel: SwiftProtobuf.Enum { +public enum HardwareModel: SwiftProtobuf.Enum, Swift.CaseIterable { public typealias RawValue = Int /// @@ -290,7 +291,7 @@ public enum HardwareModel: SwiftProtobuf.Enum { case cdebyteEoraS3 // = 61 /// - /// TWC_MESH_V4 + /// TWC_MESH_V4 /// Adafruit NRF52840 feather express with SX1262, SSD1306 OLED and NEO6M GPS case twcMeshV4 // = 62 @@ -402,6 +403,10 @@ public enum HardwareModel: SwiftProtobuf.Enum { /// https://www.loraitalia.it case meshlink // = 87 + /// + /// Seeed XIAO nRF52840 + Wio SX1262 kit + case xiaoNrf52Kit // = 88 + /// /// ------------------------------------------------------------------------------------------------------------------------------------------ /// Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits. @@ -503,6 +508,7 @@ public enum HardwareModel: SwiftProtobuf.Enum { case 85: self = .routastic case 86: self = .meshTab case 87: self = .meshlink + case 88: self = .xiaoNrf52Kit case 255: self = .privateHw default: self = .UNRECOGNIZED(rawValue) } @@ -598,16 +604,12 @@ public enum HardwareModel: SwiftProtobuf.Enum { case .routastic: return 85 case .meshTab: return 86 case .meshlink: return 87 + case .xiaoNrf52Kit: return 88 case .privateHw: return 255 case .UNRECOGNIZED(let i): return i } } -} - -#if swift(>=4.2) - -extension HardwareModel: CaseIterable { // The compiler won't synthesize support with the UNRECOGNIZED case. public static let allCases: [HardwareModel] = [ .unset, @@ -698,15 +700,15 @@ extension HardwareModel: CaseIterable { .routastic, .meshTab, .meshlink, + .xiaoNrf52Kit, .privateHw, ] -} -#endif // swift(>=4.2) +} /// /// Shared constants between device and phone -public enum Constants: SwiftProtobuf.Enum { +public enum Constants: SwiftProtobuf.Enum, Swift.CaseIterable { public typealias RawValue = Int /// @@ -741,26 +743,20 @@ public enum Constants: SwiftProtobuf.Enum { } } -} - -#if swift(>=4.2) - -extension Constants: CaseIterable { // The compiler won't synthesize support with the UNRECOGNIZED case. public static let allCases: [Constants] = [ .zero, .dataPayloadLen, ] -} -#endif // swift(>=4.2) +} /// /// Error codes for critical errors /// The device might report these fault codes on the screen. /// If you encounter a fault code, please post on the meshtastic.discourse.group /// and we'll try to help. -public enum CriticalErrorCode: SwiftProtobuf.Enum { +public enum CriticalErrorCode: SwiftProtobuf.Enum, Swift.CaseIterable { public typealias RawValue = Int /// @@ -869,11 +865,6 @@ public enum CriticalErrorCode: SwiftProtobuf.Enum { } } -} - -#if swift(>=4.2) - -extension CriticalErrorCode: CaseIterable { // The compiler won't synthesize support with the UNRECOGNIZED case. public static let allCases: [CriticalErrorCode] = [ .none, @@ -891,15 +882,14 @@ extension CriticalErrorCode: CaseIterable { .flashCorruptionRecoverable, .flashCorruptionUnrecoverable, ] -} -#endif // swift(>=4.2) +} /// /// Enum for modules excluded from a device's configuration. /// Each value represents a ModuleConfigType that can be toggled as excluded /// by setting its corresponding bit in the `excluded_modules` bitmask field. -public enum ExcludedModules: SwiftProtobuf.Enum { +public enum ExcludedModules: SwiftProtobuf.Enum, Swift.CaseIterable { public typealias RawValue = Int /// @@ -1003,11 +993,6 @@ public enum ExcludedModules: SwiftProtobuf.Enum { } } -} - -#if swift(>=4.2) - -extension ExcludedModules: CaseIterable { // The compiler won't synthesize support with the UNRECOGNIZED case. public static let allCases: [ExcludedModules] = [ .excludedNone, @@ -1025,13 +1010,12 @@ extension ExcludedModules: CaseIterable { .detectionsensorConfig, .paxcounterConfig, ] -} -#endif // swift(>=4.2) +} /// /// A GPS Position -public struct Position { +public struct Position: @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. @@ -1248,7 +1232,7 @@ public struct Position { /// /// How the location was acquired: manual, onboard GPS, external (EUD) GPS - public enum LocSource: SwiftProtobuf.Enum { + public enum LocSource: SwiftProtobuf.Enum, Swift.CaseIterable { public typealias RawValue = Int /// @@ -1292,12 +1276,20 @@ public struct Position { } } + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [Position.LocSource] = [ + .locUnset, + .locManual, + .locInternal, + .locExternal, + ] + } /// /// How the altitude was acquired: manual, GPS int/ext, etc /// Default: same as location_source if present - public enum AltSource: SwiftProtobuf.Enum { + public enum AltSource: SwiftProtobuf.Enum, Swift.CaseIterable { public typealias RawValue = Int /// @@ -1347,6 +1339,15 @@ public struct Position { } } + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [Position.AltSource] = [ + .altUnset, + .altManual, + .altInternal, + .altExternal, + .altBarometric, + ] + } public init() {} @@ -1354,31 +1355,6 @@ public struct Position { fileprivate var _storage = _StorageClass.defaultInstance } -#if swift(>=4.2) - -extension Position.LocSource: CaseIterable { - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [Position.LocSource] = [ - .locUnset, - .locManual, - .locInternal, - .locExternal, - ] -} - -extension Position.AltSource: CaseIterable { - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [Position.AltSource] = [ - .altUnset, - .altManual, - .altInternal, - .altExternal, - .altBarometric, - ] -} - -#endif // swift(>=4.2) - /// /// Broadcast when a newly powered mesh node wants to find a node num it can use /// Sent from the phone over bluetooth to set the user id for the owner of this node. @@ -1400,7 +1376,7 @@ extension Position.AltSource: CaseIterable { /// A few nodenums are reserved and will never be requested: /// 0xff - broadcast /// 0 through 3 - for future use -public struct User { +public struct User: @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. @@ -1425,6 +1401,8 @@ public struct User { /// Deprecated in Meshtastic 2.1.x /// This is the addr of the radio. /// Not populated by the phone, but added by the esp32 when broadcasting + /// + /// NOTE: This field was marked as deprecated in the .proto file. public var macaddr: Data = Data() /// @@ -1456,7 +1434,7 @@ public struct User { /// /// A message used in a traceroute -public struct RouteDiscovery { +public struct RouteDiscovery: 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. @@ -1484,7 +1462,7 @@ public struct RouteDiscovery { /// /// A Routing control Data packet handled by the routing module -public struct Routing { +public struct Routing: 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. @@ -1524,7 +1502,7 @@ public struct Routing { public var unknownFields = SwiftProtobuf.UnknownStorage() - public enum OneOf_Variant: Equatable { + public enum OneOf_Variant: Equatable, Sendable { /// /// A route request going from the requester case routeRequest(RouteDiscovery) @@ -1536,34 +1514,12 @@ public struct Routing { /// in addition to ack.fail_id to provide details on the type of failure). case errorReason(Routing.Error) - #if !swift(>=4.1) - public static func ==(lhs: Routing.OneOf_Variant, rhs: Routing.OneOf_Variant) -> 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 (.routeRequest, .routeRequest): return { - guard case .routeRequest(let l) = lhs, case .routeRequest(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.routeReply, .routeReply): return { - guard case .routeReply(let l) = lhs, case .routeReply(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.errorReason, .errorReason): return { - guard case .errorReason(let l) = lhs, case .errorReason(let r) = rhs else { preconditionFailure() } - return l == r - }() - default: return false - } - } - #endif } /// /// A failure in delivering a message (usually used for routing control messages, but might be provided in addition to ack.fail_id to provide /// details on the type of failure). - public enum Error: SwiftProtobuf.Enum { + public enum Error: SwiftProtobuf.Enum, Swift.CaseIterable { public typealias RawValue = Int /// @@ -1681,42 +1637,36 @@ public struct Routing { } } + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [Routing.Error] = [ + .none, + .noRoute, + .gotNak, + .timeout, + .noInterface, + .maxRetransmit, + .noChannel, + .tooLarge, + .noResponse, + .dutyCycleLimit, + .badRequest, + .notAuthorized, + .pkiFailed, + .pkiUnknownPubkey, + .adminBadSessionKey, + .adminPublicKeyUnauthorized, + ] + } public init() {} } -#if swift(>=4.2) - -extension Routing.Error: CaseIterable { - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [Routing.Error] = [ - .none, - .noRoute, - .gotNak, - .timeout, - .noInterface, - .maxRetransmit, - .noChannel, - .tooLarge, - .noResponse, - .dutyCycleLimit, - .badRequest, - .notAuthorized, - .pkiFailed, - .pkiUnknownPubkey, - .adminBadSessionKey, - .adminPublicKeyUnauthorized, - ] -} - -#endif // swift(>=4.2) - /// /// (Formerly called SubPacket) /// The payload portion fo a packet, this is the actual bytes that are sent /// inside a radio packet (because from/to are broken out by the comms library) -public struct DataMessage { +public struct DataMessage: @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. @@ -1783,7 +1733,7 @@ public struct DataMessage { /// /// Waypoint message, used to share arbitrary locations across the mesh -public struct Waypoint { +public struct Waypoint: 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. @@ -1845,7 +1795,7 @@ public struct Waypoint { /// /// This message will be proxied over the PhoneAPI for the client to deliver to the MQTT server -public struct MqttClientProxyMessage { +public struct MqttClientProxyMessage: @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. @@ -1886,7 +1836,7 @@ public struct MqttClientProxyMessage { /// /// The actual service envelope payload or text for mqtt pub / sub - public enum OneOf_PayloadVariant: Equatable { + public enum OneOf_PayloadVariant: Equatable, @unchecked Sendable { /// /// Bytes case data(Data) @@ -1894,24 +1844,6 @@ public struct MqttClientProxyMessage { /// Text case text(String) - #if !swift(>=4.1) - public static func ==(lhs: MqttClientProxyMessage.OneOf_PayloadVariant, rhs: MqttClientProxyMessage.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 (.data, .data): return { - guard case .data(let l) = lhs, case .data(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.text, .text): return { - guard case .text(let l) = lhs, case .text(let r) = rhs else { preconditionFailure() } - return l == r - }() - default: return false - } - } - #endif } public init() {} @@ -1921,7 +1853,7 @@ public struct MqttClientProxyMessage { /// A packet envelope sent/received over the mesh /// only payload_variant is sent in the payload portion of the LORA packet. /// The other fields are either not sent at all, or sent in the special 16 byte LORA header. -public struct MeshPacket { +public struct MeshPacket: @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. @@ -2055,6 +1987,8 @@ public struct MeshPacket { /// /// Describe if this message is delayed + /// + /// NOTE: This field was marked as deprecated in the .proto file. public var delayed: MeshPacket.Delayed { get {return _storage._delayed} set {_uniqueStorage()._delayed = newValue} @@ -2090,7 +2024,7 @@ public struct MeshPacket { } /// - /// Last byte of the node number of the node that should be used as the next hop in routing. + /// Last byte of the node number of the node that should be used as the next hop in routing. /// Set by the firmware internally, clients are not supposed to set this. public var nextHop: UInt32 { get {return _storage._nextHop} @@ -2116,7 +2050,7 @@ public struct MeshPacket { public var unknownFields = SwiftProtobuf.UnknownStorage() - public enum OneOf_PayloadVariant: Equatable { + public enum OneOf_PayloadVariant: Equatable, @unchecked Sendable { /// /// TODO: REPLACE case decoded(DataMessage) @@ -2124,24 +2058,6 @@ public struct MeshPacket { /// TODO: REPLACE case encrypted(Data) - #if !swift(>=4.1) - public static func ==(lhs: MeshPacket.OneOf_PayloadVariant, rhs: MeshPacket.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 (.decoded, .decoded): return { - guard case .decoded(let l) = lhs, case .decoded(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.encrypted, .encrypted): return { - guard case .encrypted(let l) = lhs, case .encrypted(let r) = rhs else { preconditionFailure() } - return l == r - }() - default: return false - } - } - #endif } /// @@ -2163,7 +2079,7 @@ public struct MeshPacket { /// So I bit the bullet and implemented a new (internal - not sent over the air) /// field in MeshPacket called 'priority'. /// And the transmission queue in the router object is now a priority queue. - public enum Priority: SwiftProtobuf.Enum { + public enum Priority: SwiftProtobuf.Enum, Swift.CaseIterable { public typealias RawValue = Int /// @@ -2247,11 +2163,25 @@ public struct MeshPacket { } } + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [MeshPacket.Priority] = [ + .unset, + .min, + .background, + .default, + .reliable, + .response, + .high, + .alert, + .ack, + .max, + ] + } /// /// Identify if this is a delayed packet - public enum Delayed: SwiftProtobuf.Enum { + public enum Delayed: SwiftProtobuf.Enum, Swift.CaseIterable { public typealias RawValue = Int /// @@ -2289,6 +2219,13 @@ public struct MeshPacket { } } + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [MeshPacket.Delayed] = [ + .noDelay, + .broadcast, + .direct, + ] + } public init() {} @@ -2296,35 +2233,6 @@ public struct MeshPacket { fileprivate var _storage = _StorageClass.defaultInstance } -#if swift(>=4.2) - -extension MeshPacket.Priority: CaseIterable { - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [MeshPacket.Priority] = [ - .unset, - .min, - .background, - .default, - .reliable, - .response, - .high, - .alert, - .ack, - .max, - ] -} - -extension MeshPacket.Delayed: CaseIterable { - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [MeshPacket.Delayed] = [ - .noDelay, - .broadcast, - .direct, - ] -} - -#endif // swift(>=4.2) - /// /// The bluetooth to device link: /// Old BTLE protocol docs from TODO, merge in above and make real docs... @@ -2342,7 +2250,7 @@ extension MeshPacket.Delayed: CaseIterable { /// level etc) SET_CONFIG (switches device to a new set of radio params and /// preshared key, drops all existing nodes, force our node to rejoin this new group) /// Full information about a node on the mesh -public struct NodeInfo { +public struct NodeInfo: @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. @@ -2455,7 +2363,7 @@ public struct NodeInfo { /// Unique local debugging info for this node /// Note: we don't include position or the user info, because that will come in the /// Sent to the phone in response to WantNodes. -public struct MyNodeInfo { +public struct MyNodeInfo: @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. @@ -2494,7 +2402,7 @@ public struct MyNodeInfo { /// on the message it is assumed to be a continuation of the previously sent message. /// This allows the device code to use fixed maxlen 64 byte strings for messages, /// and then extend as needed by emitting multiple records. -public struct LogRecord { +public struct LogRecord: 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. @@ -2519,7 +2427,7 @@ public struct LogRecord { /// /// Log levels, chosen to match python logging conventions. - public enum Level: SwiftProtobuf.Enum { + public enum Level: SwiftProtobuf.Enum, Swift.CaseIterable { public typealias RawValue = Int /// @@ -2581,29 +2489,23 @@ public struct LogRecord { } } + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [LogRecord.Level] = [ + .unset, + .critical, + .error, + .warning, + .info, + .debug, + .trace, + ] + } public init() {} } -#if swift(>=4.2) - -extension LogRecord.Level: CaseIterable { - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [LogRecord.Level] = [ - .unset, - .critical, - .error, - .warning, - .info, - .debug, - .trace, - ] -} - -#endif // swift(>=4.2) - -public struct QueueStatus { +public struct QueueStatus: 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. @@ -2630,7 +2532,7 @@ public struct QueueStatus { /// It will support READ and NOTIFY. When a new packet arrives the device will BLE notify? /// It will sit in that descriptor until consumed by the phone, /// at which point the next item in the FIFO will be populated. -public struct FromRadio { +public struct FromRadio: 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. @@ -2816,7 +2718,7 @@ public struct FromRadio { /// /// Log levels, chosen to match python logging conventions. - public enum OneOf_PayloadVariant: Equatable { + public enum OneOf_PayloadVariant: Equatable, Sendable { /// /// Log levels, chosen to match python logging conventions. case packet(MeshPacket) @@ -2874,80 +2776,6 @@ public struct FromRadio { /// Persistent data for device-ui case deviceuiConfig(DeviceUIConfig) - #if !swift(>=4.1) - public static func ==(lhs: FromRadio.OneOf_PayloadVariant, rhs: FromRadio.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 (.packet, .packet): return { - guard case .packet(let l) = lhs, case .packet(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.myInfo, .myInfo): return { - guard case .myInfo(let l) = lhs, case .myInfo(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.nodeInfo, .nodeInfo): return { - guard case .nodeInfo(let l) = lhs, case .nodeInfo(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.config, .config): return { - guard case .config(let l) = lhs, case .config(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.logRecord, .logRecord): return { - guard case .logRecord(let l) = lhs, case .logRecord(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.configCompleteID, .configCompleteID): return { - guard case .configCompleteID(let l) = lhs, case .configCompleteID(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.rebooted, .rebooted): return { - guard case .rebooted(let l) = lhs, case .rebooted(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.moduleConfig, .moduleConfig): return { - guard case .moduleConfig(let l) = lhs, case .moduleConfig(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.channel, .channel): return { - guard case .channel(let l) = lhs, case .channel(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.queueStatus, .queueStatus): return { - guard case .queueStatus(let l) = lhs, case .queueStatus(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.xmodemPacket, .xmodemPacket): return { - guard case .xmodemPacket(let l) = lhs, case .xmodemPacket(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.metadata, .metadata): return { - guard case .metadata(let l) = lhs, case .metadata(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.mqttClientProxyMessage, .mqttClientProxyMessage): return { - guard case .mqttClientProxyMessage(let l) = lhs, case .mqttClientProxyMessage(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.fileInfo, .fileInfo): return { - guard case .fileInfo(let l) = lhs, case .fileInfo(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.clientNotification, .clientNotification): return { - guard case .clientNotification(let l) = lhs, case .clientNotification(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.deviceuiConfig, .deviceuiConfig): return { - guard case .deviceuiConfig(let l) = lhs, case .deviceuiConfig(let r) = rhs else { preconditionFailure() } - return l == r - }() - default: return false - } - } - #endif } public init() {} @@ -2958,7 +2786,7 @@ public struct FromRadio { /// To be used for important messages that should to be displayed to the user /// in the form of push notifications or validation messages when saving /// invalid configuration. -public struct ClientNotification { +public struct ClientNotification: 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. @@ -2995,7 +2823,7 @@ public struct ClientNotification { /// /// Individual File info for the device -public struct FileInfo { +public struct FileInfo: 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. @@ -3016,7 +2844,7 @@ public struct FileInfo { /// /// Packets/commands to the radio will be written (reliably) to the toRadio characteristic. /// Once the write completes the phone can assume it is handled. -public struct ToRadio { +public struct ToRadio: 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. @@ -3096,7 +2924,7 @@ public struct ToRadio { /// /// Log levels, chosen to match python logging conventions. - public enum OneOf_PayloadVariant: Equatable { + public enum OneOf_PayloadVariant: Equatable, Sendable { /// /// Send this packet on the mesh case packet(MeshPacket) @@ -3123,40 +2951,6 @@ public struct ToRadio { /// Heartbeat message (used to keep the device connection awake on serial) case heartbeat(Heartbeat) - #if !swift(>=4.1) - public static func ==(lhs: ToRadio.OneOf_PayloadVariant, rhs: ToRadio.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 (.packet, .packet): return { - guard case .packet(let l) = lhs, case .packet(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.wantConfigID, .wantConfigID): return { - guard case .wantConfigID(let l) = lhs, case .wantConfigID(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.disconnect, .disconnect): return { - guard case .disconnect(let l) = lhs, case .disconnect(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.xmodemPacket, .xmodemPacket): return { - guard case .xmodemPacket(let l) = lhs, case .xmodemPacket(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.mqttClientProxyMessage, .mqttClientProxyMessage): return { - guard case .mqttClientProxyMessage(let l) = lhs, case .mqttClientProxyMessage(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.heartbeat, .heartbeat): return { - guard case .heartbeat(let l) = lhs, case .heartbeat(let r) = rhs else { preconditionFailure() } - return l == r - }() - default: return false - } - } - #endif } public init() {} @@ -3164,7 +2958,7 @@ public struct ToRadio { /// /// Compressed message payload -public struct Compressed { +public struct Compressed: @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. @@ -3184,7 +2978,7 @@ public struct Compressed { /// /// Full info on edges for a single node -public struct NeighborInfo { +public struct NeighborInfo: 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. @@ -3212,7 +3006,7 @@ public struct NeighborInfo { /// /// A single edge in the mesh -public struct Neighbor { +public struct Neighbor: 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. @@ -3242,7 +3036,7 @@ public struct Neighbor { /// /// Device metadata response -public struct DeviceMetadata { +public struct DeviceMetadata: 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. @@ -3304,7 +3098,7 @@ public struct DeviceMetadata { /// /// A heartbeat message is sent to the node from the client to keep the connection alive. /// This is currently only needed to keep serial connections alive, but can be used by any PhoneAPI. -public struct Heartbeat { +public struct Heartbeat: 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. @@ -3316,7 +3110,7 @@ public struct Heartbeat { /// /// RemoteHardwarePins associated with a node -public struct NodeRemoteHardwarePin { +public struct NodeRemoteHardwarePin: 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. @@ -3343,7 +3137,7 @@ public struct NodeRemoteHardwarePin { fileprivate var _pin: RemoteHardwarePin? = nil } -public struct ChunkedPayload { +public struct ChunkedPayload: @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. @@ -3371,7 +3165,7 @@ public struct ChunkedPayload { /// /// Wrapper message for broken repeated oneof support -public struct resend_chunks { +public struct resend_chunks: 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. @@ -3385,7 +3179,7 @@ public struct resend_chunks { /// /// Responses to a ChunkedPayload request -public struct ChunkedPayloadResponse { +public struct ChunkedPayloadResponse: 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. @@ -3428,7 +3222,7 @@ public struct ChunkedPayloadResponse { public var unknownFields = SwiftProtobuf.UnknownStorage() - public enum OneOf_PayloadVariant: Equatable { + public enum OneOf_PayloadVariant: Equatable, Sendable { /// /// Request to transfer chunked payload case requestTransfer(Bool) @@ -3439,77 +3233,11 @@ public struct ChunkedPayloadResponse { /// Request missing indexes in the chunked payload case resendChunks(resend_chunks) - #if !swift(>=4.1) - public static func ==(lhs: ChunkedPayloadResponse.OneOf_PayloadVariant, rhs: ChunkedPayloadResponse.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 (.requestTransfer, .requestTransfer): return { - guard case .requestTransfer(let l) = lhs, case .requestTransfer(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.acceptTransfer, .acceptTransfer): return { - guard case .acceptTransfer(let l) = lhs, case .acceptTransfer(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.resendChunks, .resendChunks): return { - guard case .resendChunks(let l) = lhs, case .resendChunks(let r) = rhs else { preconditionFailure() } - return l == r - }() - default: return false - } - } - #endif } public init() {} } -#if swift(>=5.5) && canImport(_Concurrency) -extension HardwareModel: @unchecked Sendable {} -extension Constants: @unchecked Sendable {} -extension CriticalErrorCode: @unchecked Sendable {} -extension ExcludedModules: @unchecked Sendable {} -extension Position: @unchecked Sendable {} -extension Position.LocSource: @unchecked Sendable {} -extension Position.AltSource: @unchecked Sendable {} -extension User: @unchecked Sendable {} -extension RouteDiscovery: @unchecked Sendable {} -extension Routing: @unchecked Sendable {} -extension Routing.OneOf_Variant: @unchecked Sendable {} -extension Routing.Error: @unchecked Sendable {} -extension DataMessage: @unchecked Sendable {} -extension Waypoint: @unchecked Sendable {} -extension MqttClientProxyMessage: @unchecked Sendable {} -extension MqttClientProxyMessage.OneOf_PayloadVariant: @unchecked Sendable {} -extension MeshPacket: @unchecked Sendable {} -extension MeshPacket.OneOf_PayloadVariant: @unchecked Sendable {} -extension MeshPacket.Priority: @unchecked Sendable {} -extension MeshPacket.Delayed: @unchecked Sendable {} -extension NodeInfo: @unchecked Sendable {} -extension MyNodeInfo: @unchecked Sendable {} -extension LogRecord: @unchecked Sendable {} -extension LogRecord.Level: @unchecked Sendable {} -extension QueueStatus: @unchecked Sendable {} -extension FromRadio: @unchecked Sendable {} -extension FromRadio.OneOf_PayloadVariant: @unchecked Sendable {} -extension ClientNotification: @unchecked Sendable {} -extension FileInfo: @unchecked Sendable {} -extension ToRadio: @unchecked Sendable {} -extension ToRadio.OneOf_PayloadVariant: @unchecked Sendable {} -extension Compressed: @unchecked Sendable {} -extension NeighborInfo: @unchecked Sendable {} -extension Neighbor: @unchecked Sendable {} -extension DeviceMetadata: @unchecked Sendable {} -extension Heartbeat: @unchecked Sendable {} -extension NodeRemoteHardwarePin: @unchecked Sendable {} -extension ChunkedPayload: @unchecked Sendable {} -extension resend_chunks: @unchecked Sendable {} -extension ChunkedPayloadResponse: @unchecked Sendable {} -extension ChunkedPayloadResponse.OneOf_PayloadVariant: @unchecked Sendable {} -#endif // swift(>=5.5) && canImport(_Concurrency) - // MARK: - Code below here is support for the SwiftProtobuf runtime. fileprivate let _protobuf_package = "meshtastic" @@ -3604,6 +3332,7 @@ extension HardwareModel: SwiftProtobuf._ProtoNameProviding { 85: .same(proto: "ROUTASTIC"), 86: .same(proto: "MESH_TAB"), 87: .same(proto: "MESHLINK"), + 88: .same(proto: "XIAO_NRF52_KIT"), 255: .same(proto: "PRIVATE_HW"), ] } @@ -4559,7 +4288,7 @@ extension MeshPacket: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio if _storage._rxTime != 0 { try visitor.visitSingularFixed32Field(value: _storage._rxTime, fieldNumber: 7) } - if _storage._rxSnr != 0 { + if _storage._rxSnr.bitPattern != 0 { try visitor.visitSingularFloatField(value: _storage._rxSnr, fieldNumber: 8) } if _storage._hopLimit != 0 { @@ -4761,7 +4490,7 @@ extension NodeInfo: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB try { if let v = _storage._position { try visitor.visitSingularMessageField(value: v, fieldNumber: 3) } }() - if _storage._snr != 0 { + if _storage._snr.bitPattern != 0 { try visitor.visitSingularFloatField(value: _storage._snr, fieldNumber: 4) } if _storage._lastHeard != 0 { @@ -5640,7 +5369,7 @@ extension Neighbor: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB if self.nodeID != 0 { try visitor.visitSingularUInt32Field(value: self.nodeID, fieldNumber: 1) } - if self.snr != 0 { + if self.snr.bitPattern != 0 { try visitor.visitSingularFloatField(value: self.snr, fieldNumber: 2) } if self.lastRxTime != 0 { @@ -5765,8 +5494,8 @@ extension Heartbeat: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementation public static let _protobuf_nameMap = SwiftProtobuf._NameMap() public mutating func decodeMessage(decoder: inout D) throws { - while let _ = try decoder.nextFieldNumber() { - } + // Load everything into unknown fields + while try decoder.nextFieldNumber() != nil {} } public func traverse(visitor: inout V) throws { diff --git a/MeshtasticProtobufs/Sources/meshtastic/module_config.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/module_config.pb.swift index bcf4041c..0138ccff 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/module_config.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/module_config.pb.swift @@ -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/module_config.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 enum RemoteHardwarePinType: SwiftProtobuf.Enum { +public enum RemoteHardwarePinType: SwiftProtobuf.Enum, Swift.CaseIterable { public typealias RawValue = Int /// @@ -58,24 +58,18 @@ public enum RemoteHardwarePinType: SwiftProtobuf.Enum { } } -} - -#if swift(>=4.2) - -extension RemoteHardwarePinType: CaseIterable { // The compiler won't synthesize support with the UNRECOGNIZED case. public static let allCases: [RemoteHardwarePinType] = [ .unknown, .digitalRead, .digitalWrite, ] -} -#endif // swift(>=4.2) +} /// /// Module Config -public struct ModuleConfig { +public struct ModuleConfig: 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. @@ -218,7 +212,7 @@ public struct ModuleConfig { /// /// TODO: REPLACE - public enum OneOf_PayloadVariant: Equatable { + public enum OneOf_PayloadVariant: Equatable, Sendable { /// /// TODO: REPLACE case mqtt(ModuleConfig.MQTTConfig) @@ -259,73 +253,11 @@ public struct ModuleConfig { /// TODO: REPLACE case paxcounter(ModuleConfig.PaxcounterConfig) - #if !swift(>=4.1) - public static func ==(lhs: ModuleConfig.OneOf_PayloadVariant, rhs: ModuleConfig.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 (.mqtt, .mqtt): return { - guard case .mqtt(let l) = lhs, case .mqtt(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.serial, .serial): return { - guard case .serial(let l) = lhs, case .serial(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.externalNotification, .externalNotification): return { - guard case .externalNotification(let l) = lhs, case .externalNotification(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.storeForward, .storeForward): return { - guard case .storeForward(let l) = lhs, case .storeForward(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.rangeTest, .rangeTest): return { - guard case .rangeTest(let l) = lhs, case .rangeTest(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.telemetry, .telemetry): return { - guard case .telemetry(let l) = lhs, case .telemetry(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.cannedMessage, .cannedMessage): return { - guard case .cannedMessage(let l) = lhs, case .cannedMessage(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.audio, .audio): return { - guard case .audio(let l) = lhs, case .audio(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.remoteHardware, .remoteHardware): return { - guard case .remoteHardware(let l) = lhs, case .remoteHardware(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.neighborInfo, .neighborInfo): return { - guard case .neighborInfo(let l) = lhs, case .neighborInfo(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.ambientLighting, .ambientLighting): return { - guard case .ambientLighting(let l) = lhs, case .ambientLighting(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.detectionSensor, .detectionSensor): return { - guard case .detectionSensor(let l) = lhs, case .detectionSensor(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.paxcounter, .paxcounter): return { - guard case .paxcounter(let l) = lhs, case .paxcounter(let r) = rhs else { preconditionFailure() } - return l == r - }() - default: return false - } - } - #endif } /// /// MQTT Client Config - public struct MQTTConfig { + public struct MQTTConfig: 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. @@ -400,7 +332,7 @@ public struct ModuleConfig { /// /// Settings for reporting unencrypted information about our node to a map via MQTT - public struct MapReportSettings { + public struct MapReportSettings: 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. @@ -420,7 +352,7 @@ public struct ModuleConfig { /// /// RemoteHardwareModule Config - public struct RemoteHardwareConfig { + public struct RemoteHardwareConfig: 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. @@ -444,7 +376,7 @@ public struct ModuleConfig { /// /// NeighborInfoModule Config - public struct NeighborInfoConfig { + public struct NeighborInfoConfig: 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. @@ -470,7 +402,7 @@ public struct ModuleConfig { /// /// Detection Sensor Module Config - public struct DetectionSensorConfig { + public struct DetectionSensorConfig: 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. @@ -517,7 +449,7 @@ public struct ModuleConfig { public var unknownFields = SwiftProtobuf.UnknownStorage() - public enum TriggerType: SwiftProtobuf.Enum { + public enum TriggerType: SwiftProtobuf.Enum, Swift.CaseIterable { public typealias RawValue = Int /// Event is triggered if pin is low @@ -569,6 +501,16 @@ public struct ModuleConfig { } } + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [ModuleConfig.DetectionSensorConfig.TriggerType] = [ + .logicLow, + .logicHigh, + .fallingEdge, + .risingEdge, + .eitherEdgeActiveLow, + .eitherEdgeActiveHigh, + ] + } public init() {} @@ -576,7 +518,7 @@ public struct ModuleConfig { /// /// Audio Config for codec2 voice - public struct AudioConfig { + public struct AudioConfig: 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. @@ -613,7 +555,7 @@ public struct ModuleConfig { /// /// Baudrate for codec2 voice - public enum Audio_Baud: SwiftProtobuf.Enum { + public enum Audio_Baud: SwiftProtobuf.Enum, Swift.CaseIterable { public typealias RawValue = Int case codec2Default // = 0 case codec23200 // = 1 @@ -660,6 +602,19 @@ public struct ModuleConfig { } } + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [ModuleConfig.AudioConfig.Audio_Baud] = [ + .codec2Default, + .codec23200, + .codec22400, + .codec21600, + .codec21400, + .codec21300, + .codec21200, + .codec2700, + .codec2700B, + ] + } public init() {} @@ -667,7 +622,7 @@ public struct ModuleConfig { /// /// Config for the Paxcounter Module - public struct PaxcounterConfig { + public struct PaxcounterConfig: 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. @@ -693,7 +648,7 @@ public struct ModuleConfig { /// /// Serial Config - public struct SerialConfig { + public struct SerialConfig: 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. @@ -736,7 +691,7 @@ public struct ModuleConfig { /// /// TODO: REPLACE - public enum Serial_Baud: SwiftProtobuf.Enum { + public enum Serial_Baud: SwiftProtobuf.Enum, Swift.CaseIterable { public typealias RawValue = Int case baudDefault // = 0 case baud110 // = 1 @@ -804,11 +759,31 @@ public struct ModuleConfig { } } + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [ModuleConfig.SerialConfig.Serial_Baud] = [ + .baudDefault, + .baud110, + .baud300, + .baud600, + .baud1200, + .baud2400, + .baud4800, + .baud9600, + .baud19200, + .baud38400, + .baud57600, + .baud115200, + .baud230400, + .baud460800, + .baud576000, + .baud921600, + ] + } /// /// TODO: REPLACE - public enum Serial_Mode: SwiftProtobuf.Enum { + public enum Serial_Mode: SwiftProtobuf.Enum, Swift.CaseIterable { public typealias RawValue = Int case `default` // = 0 case simple // = 1 @@ -853,6 +828,17 @@ public struct ModuleConfig { } } + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [ModuleConfig.SerialConfig.Serial_Mode] = [ + .default, + .simple, + .proto, + .textmsg, + .nmea, + .caltopo, + .ws85, + ] + } public init() {} @@ -860,7 +846,7 @@ public struct ModuleConfig { /// /// External Notifications Config - public struct ExternalNotificationConfig { + public struct ExternalNotificationConfig: 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. @@ -943,7 +929,7 @@ public struct ModuleConfig { /// /// Store and Forward Module Config - public struct StoreForwardConfig { + public struct StoreForwardConfig: 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. @@ -979,7 +965,7 @@ public struct ModuleConfig { /// /// Preferences for the RangeTestModule - public struct RangeTestConfig { + public struct RangeTestConfig: 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. @@ -1004,7 +990,7 @@ public struct ModuleConfig { /// /// Configuration for both device and environment metrics - public struct TelemetryConfig { + public struct TelemetryConfig: 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. @@ -1073,7 +1059,7 @@ public struct ModuleConfig { /// /// Canned Messages Module Config - public struct CannedMessageConfig { + public struct CannedMessageConfig: 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. @@ -1128,7 +1114,7 @@ public struct ModuleConfig { /// /// TODO: REPLACE - public enum InputEventChar: SwiftProtobuf.Enum { + public enum InputEventChar: SwiftProtobuf.Enum, Swift.CaseIterable { public typealias RawValue = Int /// @@ -1196,6 +1182,18 @@ public struct ModuleConfig { } } + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [ModuleConfig.CannedMessageConfig.InputEventChar] = [ + .none, + .up, + .down, + .left, + .right, + .select, + .back, + .cancel, + ] + } public init() {} @@ -1204,7 +1202,7 @@ public struct ModuleConfig { /// ///Ambient Lighting Module - Settings for control of onboard LEDs to allow users to adjust the brightness levels and respective color levels. ///Initially created for the RAK14001 RGB LED module. - public struct AmbientLightingConfig { + public struct AmbientLightingConfig: 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. @@ -1237,89 +1235,9 @@ public struct ModuleConfig { public init() {} } -#if swift(>=4.2) - -extension ModuleConfig.DetectionSensorConfig.TriggerType: CaseIterable { - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [ModuleConfig.DetectionSensorConfig.TriggerType] = [ - .logicLow, - .logicHigh, - .fallingEdge, - .risingEdge, - .eitherEdgeActiveLow, - .eitherEdgeActiveHigh, - ] -} - -extension ModuleConfig.AudioConfig.Audio_Baud: CaseIterable { - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [ModuleConfig.AudioConfig.Audio_Baud] = [ - .codec2Default, - .codec23200, - .codec22400, - .codec21600, - .codec21400, - .codec21300, - .codec21200, - .codec2700, - .codec2700B, - ] -} - -extension ModuleConfig.SerialConfig.Serial_Baud: CaseIterable { - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [ModuleConfig.SerialConfig.Serial_Baud] = [ - .baudDefault, - .baud110, - .baud300, - .baud600, - .baud1200, - .baud2400, - .baud4800, - .baud9600, - .baud19200, - .baud38400, - .baud57600, - .baud115200, - .baud230400, - .baud460800, - .baud576000, - .baud921600, - ] -} - -extension ModuleConfig.SerialConfig.Serial_Mode: CaseIterable { - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [ModuleConfig.SerialConfig.Serial_Mode] = [ - .default, - .simple, - .proto, - .textmsg, - .nmea, - .caltopo, - .ws85, - ] -} - -extension ModuleConfig.CannedMessageConfig.InputEventChar: CaseIterable { - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [ModuleConfig.CannedMessageConfig.InputEventChar] = [ - .none, - .up, - .down, - .left, - .right, - .select, - .back, - .cancel, - ] -} - -#endif // swift(>=4.2) - /// /// A GPIO pin definition for remote hardware module -public struct RemoteHardwarePin { +public struct RemoteHardwarePin: 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. @@ -1341,32 +1259,6 @@ public struct RemoteHardwarePin { public init() {} } -#if swift(>=5.5) && canImport(_Concurrency) -extension RemoteHardwarePinType: @unchecked Sendable {} -extension ModuleConfig: @unchecked Sendable {} -extension ModuleConfig.OneOf_PayloadVariant: @unchecked Sendable {} -extension ModuleConfig.MQTTConfig: @unchecked Sendable {} -extension ModuleConfig.MapReportSettings: @unchecked Sendable {} -extension ModuleConfig.RemoteHardwareConfig: @unchecked Sendable {} -extension ModuleConfig.NeighborInfoConfig: @unchecked Sendable {} -extension ModuleConfig.DetectionSensorConfig: @unchecked Sendable {} -extension ModuleConfig.DetectionSensorConfig.TriggerType: @unchecked Sendable {} -extension ModuleConfig.AudioConfig: @unchecked Sendable {} -extension ModuleConfig.AudioConfig.Audio_Baud: @unchecked Sendable {} -extension ModuleConfig.PaxcounterConfig: @unchecked Sendable {} -extension ModuleConfig.SerialConfig: @unchecked Sendable {} -extension ModuleConfig.SerialConfig.Serial_Baud: @unchecked Sendable {} -extension ModuleConfig.SerialConfig.Serial_Mode: @unchecked Sendable {} -extension ModuleConfig.ExternalNotificationConfig: @unchecked Sendable {} -extension ModuleConfig.StoreForwardConfig: @unchecked Sendable {} -extension ModuleConfig.RangeTestConfig: @unchecked Sendable {} -extension ModuleConfig.TelemetryConfig: @unchecked Sendable {} -extension ModuleConfig.CannedMessageConfig: @unchecked Sendable {} -extension ModuleConfig.CannedMessageConfig.InputEventChar: @unchecked Sendable {} -extension ModuleConfig.AmbientLightingConfig: @unchecked Sendable {} -extension RemoteHardwarePin: @unchecked Sendable {} -#endif // swift(>=5.5) && canImport(_Concurrency) - // MARK: - Code below here is support for the SwiftProtobuf runtime. fileprivate let _protobuf_package = "meshtastic" diff --git a/MeshtasticProtobufs/Sources/meshtastic/mqtt.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/mqtt.pb.swift index efe6cdd5..006fd9c8 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/mqtt.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/mqtt.pb.swift @@ -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/mqtt.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 /// /// This message wraps a MeshPacket with extra metadata about the sender and how it arrived. -public struct ServiceEnvelope { +public struct ServiceEnvelope: 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. @@ -57,7 +57,7 @@ public struct ServiceEnvelope { /// /// Information about a node intended to be reported unencrypted to a map using MQTT. -public struct MapReport { +public struct MapReport: 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. @@ -121,11 +121,6 @@ public struct MapReport { public init() {} } -#if swift(>=5.5) && canImport(_Concurrency) -extension ServiceEnvelope: @unchecked Sendable {} -extension MapReport: @unchecked Sendable {} -#endif // swift(>=5.5) && canImport(_Concurrency) - // MARK: - Code below here is support for the SwiftProtobuf runtime. fileprivate let _protobuf_package = "meshtastic" diff --git a/MeshtasticProtobufs/Sources/meshtastic/paxcount.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/paxcount.pb.swift index cf8aa463..e24ed371 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/paxcount.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/paxcount.pb.swift @@ -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/paxcount.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 /// /// TODO: REPLACE -public struct Paxcount { +public struct Paxcount: 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. @@ -44,10 +44,6 @@ public struct Paxcount { public init() {} } -#if swift(>=5.5) && canImport(_Concurrency) -extension Paxcount: @unchecked Sendable {} -#endif // swift(>=5.5) && canImport(_Concurrency) - // MARK: - Code below here is support for the SwiftProtobuf runtime. fileprivate let _protobuf_package = "meshtastic" diff --git a/MeshtasticProtobufs/Sources/meshtastic/portnums.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/portnums.pb.swift index 3f9afc46..cac96bc4 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/portnums.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/portnums.pb.swift @@ -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/portnums.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 @@ -33,7 +33,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP /// Note: This was formerly a Type enum named 'typ' with the same id # /// We have change to this 'portnum' based scheme for specifying app handlers for particular payloads. /// This change is backwards compatible by treating the legacy OPAQUE/CLEAR_TEXT values identically. -public enum PortNum: SwiftProtobuf.Enum { +public enum PortNum: SwiftProtobuf.Enum, Swift.CaseIterable { public typealias RawValue = Int /// @@ -193,6 +193,11 @@ public enum PortNum: SwiftProtobuf.Enum { /// PowerStress based monitoring support (for automated power consumption testing) case powerstressApp // = 74 + /// + /// Reticulum Network Stack Tunnel App + /// ENCODING: Fragmented RNS Packet. Handled by Meshtastic RNS interface + case reticulumTunnelApp // = 76 + /// /// Private applications should use portnums >= 256. /// To simplify initial development and testing you can use "PRIVATE_APP" @@ -241,6 +246,7 @@ public enum PortNum: SwiftProtobuf.Enum { case 72: self = .atakPlugin case 73: self = .mapReportApp case 74: self = .powerstressApp + case 76: self = .reticulumTunnelApp case 256: self = .privateApp case 257: self = .atakForwarder case 511: self = .max @@ -276,6 +282,7 @@ public enum PortNum: SwiftProtobuf.Enum { case .atakPlugin: return 72 case .mapReportApp: return 73 case .powerstressApp: return 74 + case .reticulumTunnelApp: return 76 case .privateApp: return 256 case .atakForwarder: return 257 case .max: return 511 @@ -283,11 +290,6 @@ public enum PortNum: SwiftProtobuf.Enum { } } -} - -#if swift(>=4.2) - -extension PortNum: CaseIterable { // The compiler won't synthesize support with the UNRECOGNIZED case. public static let allCases: [PortNum] = [ .unknownApp, @@ -316,18 +318,14 @@ extension PortNum: CaseIterable { .atakPlugin, .mapReportApp, .powerstressApp, + .reticulumTunnelApp, .privateApp, .atakForwarder, .max, ] + } -#endif // swift(>=4.2) - -#if swift(>=5.5) && canImport(_Concurrency) -extension PortNum: @unchecked Sendable {} -#endif // swift(>=5.5) && canImport(_Concurrency) - // MARK: - Code below here is support for the SwiftProtobuf runtime. extension PortNum: SwiftProtobuf._ProtoNameProviding { @@ -358,6 +356,7 @@ extension PortNum: SwiftProtobuf._ProtoNameProviding { 72: .same(proto: "ATAK_PLUGIN"), 73: .same(proto: "MAP_REPORT_APP"), 74: .same(proto: "POWERSTRESS_APP"), + 76: .same(proto: "RETICULUM_TUNNEL_APP"), 256: .same(proto: "PRIVATE_APP"), 257: .same(proto: "ATAK_FORWARDER"), 511: .same(proto: "MAX"), diff --git a/MeshtasticProtobufs/Sources/meshtastic/powermon.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/powermon.pb.swift index 5f51e948..58c21701 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/powermon.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/powermon.pb.swift @@ -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/powermon.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 /// Note: There are no 'PowerMon' messages normally in use (PowerMons are sent only as structured logs - slogs). ///But we wrap our State enum in this message to effectively nest a namespace (without our linter yelling at us) -public struct PowerMon { +public struct PowerMon: 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. @@ -31,7 +31,7 @@ public struct PowerMon { /// Any significant power changing event in meshtastic should be tagged with a powermon state transition. ///If you are making new meshtastic features feel free to add new entries at the end of this definition. - public enum State: SwiftProtobuf.Enum { + public enum State: SwiftProtobuf.Enum, Swift.CaseIterable { public typealias RawValue = Int case none // = 0 case cpuDeepSleep // = 1 @@ -104,37 +104,31 @@ public struct PowerMon { } } + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [PowerMon.State] = [ + .none, + .cpuDeepSleep, + .cpuLightSleep, + .vext1On, + .loraRxon, + .loraTxon, + .loraRxactive, + .btOn, + .ledOn, + .screenOn, + .screenDrawing, + .wifiOn, + .gpsActive, + ] + } public init() {} } -#if swift(>=4.2) - -extension PowerMon.State: CaseIterable { - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [PowerMon.State] = [ - .none, - .cpuDeepSleep, - .cpuLightSleep, - .vext1On, - .loraRxon, - .loraTxon, - .loraRxactive, - .btOn, - .ledOn, - .screenOn, - .screenDrawing, - .wifiOn, - .gpsActive, - ] -} - -#endif // swift(>=4.2) - /// /// PowerStress testing support via the C++ PowerStress module -public struct PowerStressMessage { +public struct PowerStressMessage: 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. @@ -151,7 +145,7 @@ public struct PowerStressMessage { /// What operation would we like the UUT to perform. ///note: senders should probably set want_response in their request packets, so that they can know when the state ///machine has started processing their request - public enum Opcode: SwiftProtobuf.Enum { + public enum Opcode: SwiftProtobuf.Enum, Swift.CaseIterable { public typealias RawValue = Int /// @@ -272,48 +266,35 @@ public struct PowerStressMessage { } } + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [PowerStressMessage.Opcode] = [ + .unset, + .printInfo, + .forceQuiet, + .endQuiet, + .screenOn, + .screenOff, + .cpuIdle, + .cpuDeepsleep, + .cpuFullon, + .ledOn, + .ledOff, + .loraOff, + .loraTx, + .loraRx, + .btOff, + .btOn, + .wifiOff, + .wifiOn, + .gpsOff, + .gpsOn, + ] + } public init() {} } -#if swift(>=4.2) - -extension PowerStressMessage.Opcode: CaseIterable { - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [PowerStressMessage.Opcode] = [ - .unset, - .printInfo, - .forceQuiet, - .endQuiet, - .screenOn, - .screenOff, - .cpuIdle, - .cpuDeepsleep, - .cpuFullon, - .ledOn, - .ledOff, - .loraOff, - .loraTx, - .loraRx, - .btOff, - .btOn, - .wifiOff, - .wifiOn, - .gpsOff, - .gpsOn, - ] -} - -#endif // swift(>=4.2) - -#if swift(>=5.5) && canImport(_Concurrency) -extension PowerMon: @unchecked Sendable {} -extension PowerMon.State: @unchecked Sendable {} -extension PowerStressMessage: @unchecked Sendable {} -extension PowerStressMessage.Opcode: @unchecked Sendable {} -#endif // swift(>=5.5) && canImport(_Concurrency) - // MARK: - Code below here is support for the SwiftProtobuf runtime. fileprivate let _protobuf_package = "meshtastic" @@ -323,8 +304,8 @@ extension PowerMon: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB public static let _protobuf_nameMap = SwiftProtobuf._NameMap() public mutating func decodeMessage(decoder: inout D) throws { - while let _ = try decoder.nextFieldNumber() { - } + // Load everything into unknown fields + while try decoder.nextFieldNumber() != nil {} } public func traverse(visitor: inout V) throws { @@ -379,7 +360,7 @@ extension PowerStressMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImple if self.cmd != .unset { try visitor.visitSingularEnumField(value: self.cmd, fieldNumber: 1) } - if self.numSeconds != 0 { + if self.numSeconds.bitPattern != 0 { try visitor.visitSingularFloatField(value: self.numSeconds, fieldNumber: 2) } try unknownFields.traverse(visitor: &visitor) diff --git a/MeshtasticProtobufs/Sources/meshtastic/remote_hardware.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/remote_hardware.pb.swift index ac6eeb26..d23dc07b 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/remote_hardware.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/remote_hardware.pb.swift @@ -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/remote_hardware.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 @@ -30,7 +30,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP /// because no security yet (beyond the channel mechanism). /// It should be off by default and then protected based on some TBD mechanism /// (a special channel once multichannel support is included?) -public struct HardwareMessage { +public struct HardwareMessage: 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. @@ -52,7 +52,7 @@ public struct HardwareMessage { /// /// TODO: REPLACE - public enum TypeEnum: SwiftProtobuf.Enum { + public enum TypeEnum: SwiftProtobuf.Enum, Swift.CaseIterable { public typealias RawValue = Int /// @@ -110,32 +110,21 @@ public struct HardwareMessage { } } + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [HardwareMessage.TypeEnum] = [ + .unset, + .writeGpios, + .watchGpios, + .gpiosChanged, + .readGpios, + .readGpiosReply, + ] + } public init() {} } -#if swift(>=4.2) - -extension HardwareMessage.TypeEnum: CaseIterable { - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [HardwareMessage.TypeEnum] = [ - .unset, - .writeGpios, - .watchGpios, - .gpiosChanged, - .readGpios, - .readGpiosReply, - ] -} - -#endif // swift(>=4.2) - -#if swift(>=5.5) && canImport(_Concurrency) -extension HardwareMessage: @unchecked Sendable {} -extension HardwareMessage.TypeEnum: @unchecked Sendable {} -#endif // swift(>=5.5) && canImport(_Concurrency) - // MARK: - Code below here is support for the SwiftProtobuf runtime. fileprivate let _protobuf_package = "meshtastic" diff --git a/MeshtasticProtobufs/Sources/meshtastic/rtttl.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/rtttl.pb.swift index 6fdf3208..38d0c880 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/rtttl.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/rtttl.pb.swift @@ -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/rtttl.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 RTTTLConfig { +public struct RTTTLConfig: 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 RTTTLConfig { public init() {} } -#if swift(>=5.5) && canImport(_Concurrency) -extension RTTTLConfig: @unchecked Sendable {} -#endif // swift(>=5.5) && canImport(_Concurrency) - // MARK: - Code below here is support for the SwiftProtobuf runtime. fileprivate let _protobuf_package = "meshtastic" diff --git a/MeshtasticProtobufs/Sources/meshtastic/storeforward.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/storeforward.pb.swift index 54efa77b..deb96569 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/storeforward.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/storeforward.pb.swift @@ -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/storeforward.proto @@ -22,7 +23,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP /// /// TODO: REPLACE -public struct StoreAndForward { +public struct StoreAndForward: @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. @@ -79,7 +80,7 @@ public struct StoreAndForward { /// /// TODO: REPLACE - public enum OneOf_Variant: Equatable { + public enum OneOf_Variant: Equatable, @unchecked Sendable { /// /// TODO: REPLACE case stats(StoreAndForward.Statistics) @@ -93,38 +94,12 @@ public struct StoreAndForward { /// Text from history message. case text(Data) - #if !swift(>=4.1) - public static func ==(lhs: StoreAndForward.OneOf_Variant, rhs: StoreAndForward.OneOf_Variant) -> 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 (.stats, .stats): return { - guard case .stats(let l) = lhs, case .stats(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.history, .history): return { - guard case .history(let l) = lhs, case .history(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.heartbeat, .heartbeat): return { - guard case .heartbeat(let l) = lhs, case .heartbeat(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.text, .text): return { - guard case .text(let l) = lhs, case .text(let r) = rhs else { preconditionFailure() } - return l == r - }() - default: return false - } - } - #endif } /// /// 001 - 063 = From Router /// 064 - 127 = From Client - public enum RequestResponse: SwiftProtobuf.Enum { + public enum RequestResponse: SwiftProtobuf.Enum, Swift.CaseIterable { public typealias RawValue = Int /// @@ -242,11 +217,31 @@ public struct StoreAndForward { } } + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [StoreAndForward.RequestResponse] = [ + .unset, + .routerError, + .routerHeartbeat, + .routerPing, + .routerPong, + .routerBusy, + .routerHistory, + .routerStats, + .routerTextDirect, + .routerTextBroadcast, + .clientError, + .clientHistory, + .clientStats, + .clientPing, + .clientPong, + .clientAbort, + ] + } /// /// TODO: REPLACE - public struct Statistics { + public struct Statistics: 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. @@ -294,7 +289,7 @@ public struct StoreAndForward { /// /// TODO: REPLACE - public struct History { + public struct History: 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. @@ -319,7 +314,7 @@ public struct StoreAndForward { /// /// TODO: REPLACE - public struct Heartbeat { + public struct Heartbeat: 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. @@ -340,41 +335,6 @@ public struct StoreAndForward { public init() {} } -#if swift(>=4.2) - -extension StoreAndForward.RequestResponse: CaseIterable { - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [StoreAndForward.RequestResponse] = [ - .unset, - .routerError, - .routerHeartbeat, - .routerPing, - .routerPong, - .routerBusy, - .routerHistory, - .routerStats, - .routerTextDirect, - .routerTextBroadcast, - .clientError, - .clientHistory, - .clientStats, - .clientPing, - .clientPong, - .clientAbort, - ] -} - -#endif // swift(>=4.2) - -#if swift(>=5.5) && canImport(_Concurrency) -extension StoreAndForward: @unchecked Sendable {} -extension StoreAndForward.OneOf_Variant: @unchecked Sendable {} -extension StoreAndForward.RequestResponse: @unchecked Sendable {} -extension StoreAndForward.Statistics: @unchecked Sendable {} -extension StoreAndForward.History: @unchecked Sendable {} -extension StoreAndForward.Heartbeat: @unchecked Sendable {} -#endif // swift(>=5.5) && canImport(_Concurrency) - // MARK: - Code below here is support for the SwiftProtobuf runtime. fileprivate let _protobuf_package = "meshtastic" diff --git a/MeshtasticProtobufs/Sources/meshtastic/telemetry.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/telemetry.pb.swift index e652a89c..4cd8e939 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/telemetry.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/telemetry.pb.swift @@ -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/telemetry.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 /// /// Supported I2C Sensors for telemetry in Meshtastic -public enum TelemetrySensorType: SwiftProtobuf.Enum { +public enum TelemetrySensorType: SwiftProtobuf.Enum, Swift.CaseIterable { public typealias RawValue = Int /// @@ -122,7 +122,7 @@ public enum TelemetrySensorType: SwiftProtobuf.Enum { case aht10 // = 23 /// - /// DFRobot Lark Weather station (temperature, humidity, pressure, wind speed and direction) + /// DFRobot Lark Weather station (temperature, humidity, pressure, wind speed and direction) case dfrobotLark // = 24 /// @@ -146,7 +146,7 @@ public enum TelemetrySensorType: SwiftProtobuf.Enum { case customSensor // = 29 /// - /// MAX30102 Pulse Oximeter and Heart-Rate Sensor + /// MAX30102 Pulse Oximeter and Heart-Rate Sensor case max30102 // = 30 /// @@ -168,6 +168,14 @@ public enum TelemetrySensorType: SwiftProtobuf.Enum { /// /// DFRobot Gravity tipping bucket rain gauge case dfrobotRain // = 35 + + /// + /// Infineon DPS310 High accuracy pressure and temperature + case dps310 // = 36 + + /// + /// RAKWireless RAK12035 Soil Moisture Sensor Module + case rak12035 // = 37 case UNRECOGNIZED(Int) public init() { @@ -212,6 +220,8 @@ public enum TelemetrySensorType: SwiftProtobuf.Enum { case 33: self = .radsens case 34: self = .ina226 case 35: self = .dfrobotRain + case 36: self = .dps310 + case 37: self = .rak12035 default: self = .UNRECOGNIZED(rawValue) } } @@ -254,15 +264,12 @@ public enum TelemetrySensorType: SwiftProtobuf.Enum { case .radsens: return 33 case .ina226: return 34 case .dfrobotRain: return 35 + case .dps310: return 36 + case .rak12035: return 37 case .UNRECOGNIZED(let i): return i } } -} - -#if swift(>=4.2) - -extension TelemetrySensorType: CaseIterable { // The compiler won't synthesize support with the UNRECOGNIZED case. public static let allCases: [TelemetrySensorType] = [ .sensorUnset, @@ -301,14 +308,15 @@ extension TelemetrySensorType: CaseIterable { .radsens, .ina226, .dfrobotRain, + .dps310, + .rak12035, ] -} -#endif // swift(>=4.2) +} /// /// Key native device metrics such as battery level -public struct DeviceMetrics { +public struct DeviceMetrics: 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. @@ -381,7 +389,7 @@ public struct DeviceMetrics { /// /// Weather station or other environmental metrics -public struct EnvironmentMetrics { +public struct EnvironmentMetrics: @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. @@ -452,7 +460,7 @@ public struct EnvironmentMetrics { /// Clears the value of `current`. Subsequent reads from it will return its default value. public mutating func clearCurrent() {_uniqueStorage()._current = nil} - /// + /// /// relative scale IAQ value as measured by Bosch BME680 . value 0-500. /// Belongs to Air Quality but is not particle but VOC measurement. Other VOC values can also be put in here. public var iaq: UInt32 { @@ -608,6 +616,28 @@ public struct EnvironmentMetrics { /// Clears the value of `rainfall24H`. Subsequent reads from it will return its default value. public mutating func clearRainfall24H() {_uniqueStorage()._rainfall24H = nil} + /// + /// Soil moisture measured (% 1-100) + public var soilMoisture: UInt32 { + get {return _storage._soilMoisture ?? 0} + set {_uniqueStorage()._soilMoisture = newValue} + } + /// Returns true if `soilMoisture` has been explicitly set. + public var hasSoilMoisture: Bool {return _storage._soilMoisture != nil} + /// Clears the value of `soilMoisture`. Subsequent reads from it will return its default value. + public mutating func clearSoilMoisture() {_uniqueStorage()._soilMoisture = nil} + + /// + /// Soil temperature measured (*C) + public var soilTemperature: Float { + get {return _storage._soilTemperature ?? 0} + set {_uniqueStorage()._soilTemperature = newValue} + } + /// Returns true if `soilTemperature` has been explicitly set. + public var hasSoilTemperature: Bool {return _storage._soilTemperature != nil} + /// Clears the value of `soilTemperature`. Subsequent reads from it will return its default value. + public mutating func clearSoilTemperature() {_uniqueStorage()._soilTemperature = nil} + public var unknownFields = SwiftProtobuf.UnknownStorage() public init() {} @@ -617,7 +647,7 @@ public struct EnvironmentMetrics { /// /// Power Metrics (voltage / current / etc) -public struct PowerMetrics { +public struct PowerMetrics: 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. @@ -702,7 +732,7 @@ public struct PowerMetrics { /// /// Air quality metrics -public struct AirQualityMetrics { +public struct AirQualityMetrics: 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. @@ -871,7 +901,7 @@ public struct AirQualityMetrics { /// /// Local device mesh statistics -public struct LocalStats { +public struct LocalStats: 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. @@ -929,7 +959,7 @@ public struct LocalStats { /// /// Health telemetry metrics -public struct HealthMetrics { +public struct HealthMetrics: 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. @@ -978,7 +1008,7 @@ public struct HealthMetrics { /// /// Types of Measurements the telemetry module is equipped to handle -public struct Telemetry { +public struct Telemetry: 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. @@ -1051,7 +1081,7 @@ public struct Telemetry { public var unknownFields = SwiftProtobuf.UnknownStorage() - public enum OneOf_Variant: Equatable { + public enum OneOf_Variant: Equatable, Sendable { /// /// Key native device metrics such as battery level case deviceMetrics(DeviceMetrics) @@ -1071,40 +1101,6 @@ public struct Telemetry { /// Health telemetry metrics case healthMetrics(HealthMetrics) - #if !swift(>=4.1) - public static func ==(lhs: Telemetry.OneOf_Variant, rhs: Telemetry.OneOf_Variant) -> 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 (.deviceMetrics, .deviceMetrics): return { - guard case .deviceMetrics(let l) = lhs, case .deviceMetrics(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.environmentMetrics, .environmentMetrics): return { - guard case .environmentMetrics(let l) = lhs, case .environmentMetrics(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.airQualityMetrics, .airQualityMetrics): return { - guard case .airQualityMetrics(let l) = lhs, case .airQualityMetrics(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.powerMetrics, .powerMetrics): return { - guard case .powerMetrics(let l) = lhs, case .powerMetrics(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.localStats, .localStats): return { - guard case .localStats(let l) = lhs, case .localStats(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.healthMetrics, .healthMetrics): return { - guard case .healthMetrics(let l) = lhs, case .healthMetrics(let r) = rhs else { preconditionFailure() } - return l == r - }() - default: return false - } - } - #endif } public init() {} @@ -1112,7 +1108,7 @@ public struct Telemetry { /// /// NAU7802 Telemetry configuration, for saving to flash -public struct Nau7802Config { +public struct Nau7802Config: 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. @@ -1130,19 +1126,6 @@ public struct Nau7802Config { public init() {} } -#if swift(>=5.5) && canImport(_Concurrency) -extension TelemetrySensorType: @unchecked Sendable {} -extension DeviceMetrics: @unchecked Sendable {} -extension EnvironmentMetrics: @unchecked Sendable {} -extension PowerMetrics: @unchecked Sendable {} -extension AirQualityMetrics: @unchecked Sendable {} -extension LocalStats: @unchecked Sendable {} -extension HealthMetrics: @unchecked Sendable {} -extension Telemetry: @unchecked Sendable {} -extension Telemetry.OneOf_Variant: @unchecked Sendable {} -extension Nau7802Config: @unchecked Sendable {} -#endif // swift(>=5.5) && canImport(_Concurrency) - // MARK: - Code below here is support for the SwiftProtobuf runtime. fileprivate let _protobuf_package = "meshtastic" @@ -1185,6 +1168,8 @@ extension TelemetrySensorType: SwiftProtobuf._ProtoNameProviding { 33: .same(proto: "RADSENS"), 34: .same(proto: "INA226"), 35: .same(proto: "DFROBOT_RAIN"), + 36: .same(proto: "DPS310"), + 37: .same(proto: "RAK12035"), ] } @@ -1271,6 +1256,8 @@ extension EnvironmentMetrics: SwiftProtobuf.Message, SwiftProtobuf._MessageImple 18: .same(proto: "radiation"), 19: .standard(proto: "rainfall_1h"), 20: .standard(proto: "rainfall_24h"), + 21: .standard(proto: "soil_moisture"), + 22: .standard(proto: "soil_temperature"), ] fileprivate class _StorageClass { @@ -1294,6 +1281,8 @@ extension EnvironmentMetrics: SwiftProtobuf.Message, SwiftProtobuf._MessageImple var _radiation: Float? = nil var _rainfall1H: Float? = nil var _rainfall24H: Float? = nil + var _soilMoisture: UInt32? = nil + var _soilTemperature: Float? = nil #if swift(>=5.10) // This property is used as the initial default value for new instances of the type. @@ -1328,6 +1317,8 @@ extension EnvironmentMetrics: SwiftProtobuf.Message, SwiftProtobuf._MessageImple _radiation = source._radiation _rainfall1H = source._rainfall1H _rainfall24H = source._rainfall24H + _soilMoisture = source._soilMoisture + _soilTemperature = source._soilTemperature } } @@ -1366,6 +1357,8 @@ extension EnvironmentMetrics: SwiftProtobuf.Message, SwiftProtobuf._MessageImple case 18: try { try decoder.decodeSingularFloatField(value: &_storage._radiation) }() case 19: try { try decoder.decodeSingularFloatField(value: &_storage._rainfall1H) }() case 20: try { try decoder.decodeSingularFloatField(value: &_storage._rainfall24H) }() + case 21: try { try decoder.decodeSingularUInt32Field(value: &_storage._soilMoisture) }() + case 22: try { try decoder.decodeSingularFloatField(value: &_storage._soilTemperature) }() default: break } } @@ -1438,6 +1431,12 @@ extension EnvironmentMetrics: SwiftProtobuf.Message, SwiftProtobuf._MessageImple try { if let v = _storage._rainfall24H { try visitor.visitSingularFloatField(value: v, fieldNumber: 20) } }() + try { if let v = _storage._soilMoisture { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 21) + } }() + try { if let v = _storage._soilTemperature { + try visitor.visitSingularFloatField(value: v, fieldNumber: 22) + } }() } try unknownFields.traverse(visitor: &visitor) } @@ -1467,6 +1466,8 @@ extension EnvironmentMetrics: SwiftProtobuf.Message, SwiftProtobuf._MessageImple if _storage._radiation != rhs_storage._radiation {return false} if _storage._rainfall1H != rhs_storage._rainfall1H {return false} if _storage._rainfall24H != rhs_storage._rainfall24H {return false} + if _storage._soilMoisture != rhs_storage._soilMoisture {return false} + if _storage._soilTemperature != rhs_storage._soilTemperature {return false} return true } if !storagesAreEqual {return false} @@ -1692,10 +1693,10 @@ extension LocalStats: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio if self.uptimeSeconds != 0 { try visitor.visitSingularUInt32Field(value: self.uptimeSeconds, fieldNumber: 1) } - if self.channelUtilization != 0 { + if self.channelUtilization.bitPattern != 0 { try visitor.visitSingularFloatField(value: self.channelUtilization, fieldNumber: 2) } - if self.airUtilTx != 0 { + if self.airUtilTx.bitPattern != 0 { try visitor.visitSingularFloatField(value: self.airUtilTx, fieldNumber: 3) } if self.numPacketsTx != 0 { @@ -1962,7 +1963,7 @@ extension Nau7802Config: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementa if self.zeroOffset != 0 { try visitor.visitSingularInt32Field(value: self.zeroOffset, fieldNumber: 1) } - if self.calibrationFactor != 0 { + if self.calibrationFactor.bitPattern != 0 { try visitor.visitSingularFloatField(value: self.calibrationFactor, fieldNumber: 2) } try unknownFields.traverse(visitor: &visitor) diff --git a/MeshtasticProtobufs/Sources/meshtastic/xmodem.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/xmodem.pb.swift index 1f41fe0b..46907a58 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/xmodem.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/xmodem.pb.swift @@ -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/xmodem.proto @@ -20,7 +21,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP typealias Version = _2 } -public struct XModem { +public struct XModem: @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. @@ -35,7 +36,7 @@ public struct XModem { public var unknownFields = SwiftProtobuf.UnknownStorage() - public enum Control: SwiftProtobuf.Enum { + public enum Control: SwiftProtobuf.Enum, Swift.CaseIterable { public typealias RawValue = Int case nul // = 0 case soh // = 1 @@ -79,34 +80,23 @@ public struct XModem { } } + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [XModem.Control] = [ + .nul, + .soh, + .stx, + .eot, + .ack, + .nak, + .can, + .ctrlz, + ] + } public init() {} } -#if swift(>=4.2) - -extension XModem.Control: CaseIterable { - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [XModem.Control] = [ - .nul, - .soh, - .stx, - .eot, - .ack, - .nak, - .can, - .ctrlz, - ] -} - -#endif // swift(>=4.2) - -#if swift(>=5.5) && canImport(_Concurrency) -extension XModem: @unchecked Sendable {} -extension XModem.Control: @unchecked Sendable {} -#endif // swift(>=5.5) && canImport(_Concurrency) - // MARK: - Code below here is support for the SwiftProtobuf runtime. fileprivate let _protobuf_package = "meshtastic" diff --git a/README.md b/README.md index bbaf49b0..d2ab6c35 100644 --- a/README.md +++ b/README.md @@ -9,16 +9,22 @@ SwiftUI client applications for iOS, iPadOS and macOS. This project always uses the latest release version of XCode. 1. Clone the repo. -2. Set up git hooks to automatically lint the project when you commit changes. -2. Open `Meshtastic.xcworkspace` -2. Build and run the `Meshtastic` target. - -```sh -git clone git@github.com:meshtastic/Meshtastic-Apple.git -cd Meshtastic-Apple -./scripts/setup-hooks.sh -open Meshtastic.xcworkspace -``` + ```sh + git clone git@github.com:meshtastic/Meshtastic-Apple.git + ``` +2. Open the local directory. + ```sh + cd Meshtastic-Apple + ``` +3. Set up git hooks to automatically lint the project when you commit changes. + ```sh + ./scripts/setup-hooks.sh + ``` +4. Open `Meshtastic.xcworkspace` + ```sh + open Meshtastic.xcworkspace + ``` +5. Build and run the `Meshtastic` target. ## Technical Standards