mirror of
https://github.com/meshtastic/Meshtastic-Apple.git
synced 2026-04-20 22:13:56 +00:00
Merge branch 'main' of github.com:meshtastic/Meshtastic-Apple
This commit is contained in:
commit
4534676fc7
119 changed files with 5067 additions and 4031 deletions
2
.github/pull_request_template.md
vendored
2
.github/pull_request_template.md
vendored
|
|
@ -16,6 +16,6 @@
|
|||
- [ ] My code adheres to the project's coding and style guidelines.
|
||||
- [ ] I have conducted a self-review of my code.
|
||||
- [ ] I have commented my code, particularly in complex areas.
|
||||
- [ ] I have made corresponding changes to the documentation.
|
||||
- [ ] I have verified whether these changes require an update to existing documentation or if new documentation is needed, and created an issue in the [docs repo](http://github.com/meshtastic/meshtastic/issues) if applicable.
|
||||
- [ ] I have tested the change to ensure that it works as intended.
|
||||
|
||||
|
|
|
|||
1
.github/workflows/stale.yml
vendored
1
.github/workflows/stale.yml
vendored
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -4,29 +4,31 @@ Thank you for considering contributing to Meshtastic! We appreciate your time an
|
|||
|
||||
## Table of Contents
|
||||
|
||||
1. [Getting Started](#getting-started)
|
||||
2. [Development Workflow](#development-workflow)
|
||||
- [Targeting `main`](#targeting-main)
|
||||
- [Small, Incremental Changes](#small-incremental-changes)
|
||||
- [Rebase Commits](#rebase-commits)
|
||||
3. [Creating a Branch](#creating-a-branch)
|
||||
4. [Making Changes](#making-changes)
|
||||
5. [Commit Messages](#commit-messages)
|
||||
6. [Merging Changes](#merging-changes)
|
||||
7. [Testing](#testing)
|
||||
8. [Code Review](#code-review)
|
||||
9. [Documentation](#documentation)
|
||||
10. [Style Guides](#style-guides)
|
||||
- [Contributing to Meshtastic](#contributing-to-meshtastic)
|
||||
- [Table of Contents](#table-of-contents)
|
||||
- [Getting Started](#getting-started)
|
||||
- [Development Workflow](#development-workflow)
|
||||
- [Targeting `main`](#targeting-main)
|
||||
- [Small, Incremental Changes](#small-incremental-changes)
|
||||
- [Rebase Commits](#rebase-commits)
|
||||
- [Creating a Branch](#creating-a-branch)
|
||||
- [Making Changes](#making-changes)
|
||||
- [Commit Messages](#commit-messages)
|
||||
- [Merging Changes](#merging-changes)
|
||||
- [Testing](#testing)
|
||||
- [Code Review](#code-review)
|
||||
- [Documentation](#documentation)
|
||||
- [Style Guides](#style-guides)
|
||||
- [Git Commit Messages](#git-commit-messages)
|
||||
- [Code Style](#code-style)
|
||||
11. [Community](#community)
|
||||
- [Community](#community)
|
||||
|
||||
## Getting Started
|
||||
|
||||
1. Fork the repository on GitLab.
|
||||
1. Fork the repository on GitHub.
|
||||
2. Clone your fork to your local machine:
|
||||
```sh
|
||||
git clone https://gitlab.com/<your-username>/Meshtastic-Apple.git
|
||||
git clone https://github.com/<your-username>/Meshtastic-Apple.git
|
||||
```
|
||||
3. Navigate to the project directory:
|
||||
```sh
|
||||
|
|
|
|||
|
|
@ -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" : {
|
||||
|
|
|
|||
|
|
@ -11,13 +11,23 @@
|
|||
231B3F222D087A4C0069A07D /* MetricsColumnList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 231B3F1F2D087A4C0069A07D /* MetricsColumnList.swift */; };
|
||||
231B3F252D087C3C0069A07D /* EnvironmentDefaultColumns.swift in Sources */ = {isa = PBXBuildFile; fileRef = 231B3F242D087C3C0069A07D /* EnvironmentDefaultColumns.swift */; };
|
||||
231B3F272D0885240069A07D /* MetricsColumnDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = 231B3F262D0885240069A07D /* MetricsColumnDetail.swift */; };
|
||||
233E99B62D849C3D00CC3A77 /* WeatherConditionsCompactWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 233E99B52D849C3D00CC3A77 /* WeatherConditionsCompactWidget.swift */; };
|
||||
233E99B82D849C6500CC3A77 /* HumidityCompactWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 233E99B72D849C6500CC3A77 /* HumidityCompactWidget.swift */; };
|
||||
233E99BA2D849C7000CC3A77 /* PressureCompactWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 233E99B92D849C7000CC3A77 /* PressureCompactWidget.swift */; };
|
||||
233E99BC2D849C8C00CC3A77 /* WindCompactWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 233E99BB2D849C8C00CC3A77 /* WindCompactWidget.swift */; };
|
||||
233E99BE2D849D3200CC3A77 /* RadiationCompactWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 233E99BD2D849D3200CC3A77 /* RadiationCompactWidget.swift */; };
|
||||
233E99C12D849D6000CC3A77 /* DistanceCompactWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 233E99C02D849D6000CC3A77 /* DistanceCompactWidget.swift */; };
|
||||
233E99C32D849D7A00CC3A77 /* WeightCompactWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 233E99C22D849D7A00CC3A77 /* WeightCompactWidget.swift */; };
|
||||
233E99C52D84A0B600CC3A77 /* CompactWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 233E99C42D84A0B600CC3A77 /* CompactWidget.swift */; };
|
||||
233E99C72D84A70900CC3A77 /* SoilCompactWidgets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 233E99C62D84A70900CC3A77 /* SoilCompactWidgets.swift */; };
|
||||
233E99CB2D85AAA900CC3A77 /* RainfallCompactWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 233E99CA2D85AAA900CC3A77 /* RainfallCompactWidget.swift */; };
|
||||
2344A2AB2D66974300170A77 /* ManagedAttributePropertyWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2344A2AA2D66973D00170A77 /* ManagedAttributePropertyWrapper.swift */; };
|
||||
2344A2AF2D6697A700170A77 /* TelemetryEntity+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2344A2AD2D6697A700170A77 /* TelemetryEntity+CoreDataClass.swift */; };
|
||||
2344A2B02D6697A700170A77 /* TelemetryEntity+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2344A2AE2D6697A700170A77 /* TelemetryEntity+CoreDataProperties.swift */; };
|
||||
2344A2B12D68DFF800170A77 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25C49D8F2C471AEA0024FBD1 /* Constants.swift */; };
|
||||
2373AE132D0A216C0086C749 /* MetricsChartSeries.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2373AE122D0A216C0086C749 /* MetricsChartSeries.swift */; };
|
||||
2373AE152D0A24930086C749 /* MetricsSeriesList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2373AE142D0A24930086C749 /* MetricsSeriesList.swift */; };
|
||||
2373AE172D0A26620086C749 /* EnviornmentDefaultSeries.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2373AE162D0A26620086C749 /* EnviornmentDefaultSeries.swift */; };
|
||||
2373AE172D0A26620086C749 /* EnvironmentDefaultSeries.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2373AE162D0A26620086C749 /* EnvironmentDefaultSeries.swift */; };
|
||||
251926852C3BA97800249DF5 /* FavoriteNodeButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 251926842C3BA97800249DF5 /* FavoriteNodeButton.swift */; };
|
||||
251926872C3BAE2200249DF5 /* NodeAlertsButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 251926862C3BAE2200249DF5 /* NodeAlertsButton.swift */; };
|
||||
2519268A2C3BB1B200249DF5 /* ExchangePositionsButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 251926892C3BB1B200249DF5 /* ExchangePositionsButton.swift */; };
|
||||
|
|
@ -56,7 +66,6 @@
|
|||
BCE2D3C52C7AE369008E6199 /* RestartNodeIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCE2D3C42C7AE369008E6199 /* RestartNodeIntent.swift */; };
|
||||
BCE2D3C72C7B0D0A008E6199 /* ShortcutsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCE2D3C62C7B0D0A008E6199 /* ShortcutsProvider.swift */; };
|
||||
BCE2D3C92C7C377F008E6199 /* FactoryResetNodeIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCE2D3C82C7C377F008E6199 /* FactoryResetNodeIntent.swift */; };
|
||||
C9697FA527933B8C00250207 /* SQLite in Frameworks */ = {isa = PBXBuildFile; productRef = C9697FA427933B8C00250207 /* SQLite */; };
|
||||
D93068D32B8129510066FBC8 /* MessageContextMenuItems.swift in Sources */ = {isa = PBXBuildFile; fileRef = D93068D22B8129510066FBC8 /* MessageContextMenuItems.swift */; };
|
||||
D93068D52B812B700066FBC8 /* MessageDestination.swift in Sources */ = {isa = PBXBuildFile; fileRef = D93068D42B812B700066FBC8 /* MessageDestination.swift */; };
|
||||
D93068D72B8146690066FBC8 /* MessageText.swift in Sources */ = {isa = PBXBuildFile; fileRef = D93068D62B8146690066FBC8 /* MessageText.swift */; };
|
||||
|
|
@ -64,7 +73,6 @@
|
|||
D93068DB2B81C85E0066FBC8 /* PowerConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = D93068DA2B81C85E0066FBC8 /* PowerConfig.swift */; };
|
||||
D93068DD2B81CA820066FBC8 /* ConfigHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = D93068DC2B81CA820066FBC8 /* ConfigHeader.swift */; };
|
||||
D93069082B81DF040066FBC8 /* SaveConfigButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D93069072B81DF040066FBC8 /* SaveConfigButton.swift */; };
|
||||
D9BC22DB2B7DE8E2006A37D5 /* TileDownloadStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9BC22DA2B7DE8E2006A37D5 /* TileDownloadStatus.swift */; };
|
||||
D9C9839D2B79CFD700BDBE6A /* TextMessageSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9C9839C2B79CFD700BDBE6A /* TextMessageSize.swift */; };
|
||||
D9C983A02B79D0E800BDBE6A /* AlertButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9C9839F2B79D0E800BDBE6A /* AlertButton.swift */; };
|
||||
D9C983A22B79D1A600BDBE6A /* RequestPositionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9C983A12B79D1A600BDBE6A /* RequestPositionButton.swift */; };
|
||||
|
|
@ -90,7 +98,6 @@
|
|||
DD2553572855B02500E55709 /* LoRaConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2553562855B02500E55709 /* LoRaConfig.swift */; };
|
||||
DD2553592855B52700E55709 /* PositionConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2553582855B52700E55709 /* PositionConfig.swift */; };
|
||||
DD268D8E2BCC90E2008073AE /* RouteEnums.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD268D8D2BCC90E2008073AE /* RouteEnums.swift */; };
|
||||
DD2AD8A8296D2DF9001FF0E7 /* MapViewSwiftUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2AD8A7296D2DF9001FF0E7 /* MapViewSwiftUI.swift */; };
|
||||
DD33DB622B3D27C7003E1EA0 /* FirmwareApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD33DB612B3D27C7003E1EA0 /* FirmwareApi.swift */; };
|
||||
DD3501892852FC3B000FC853 /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3501882852FC3B000FC853 /* Settings.swift */; };
|
||||
DD354FD92BD96A0B0061A25F /* IAQScale.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD354FD82BD96A0B0061A25F /* IAQScale.swift */; };
|
||||
|
|
@ -144,9 +151,7 @@
|
|||
DD93800E2BA74D0C008BEC06 /* ChannelForm.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD93800D2BA74D0C008BEC06 /* ChannelForm.swift */; };
|
||||
DD94B7402ACCE3BE00DCD1D1 /* MapSettingsForm.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD94B73F2ACCE3BE00DCD1D1 /* MapSettingsForm.swift */; };
|
||||
DD964FBD296E6B01007C176F /* EmojiOnlyTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD964FBC296E6B01007C176F /* EmojiOnlyTextField.swift */; };
|
||||
DD964FBF296E76EF007C176F /* WaypointFormMapKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD964FBE296E76EF007C176F /* WaypointFormMapKit.swift */; };
|
||||
DD964FC2297272AE007C176F /* WaypointEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD964FC1297272AE007C176F /* WaypointEntityExtension.swift */; };
|
||||
DD964FC42974767D007C176F /* MapViewFitExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD964FC32974767D007C176F /* MapViewFitExtension.swift */; };
|
||||
DD964FC62975DBFD007C176F /* QueryCoreData.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD964FC52975DBFD007C176F /* QueryCoreData.swift */; };
|
||||
DD97E96628EFD9820056DDA4 /* MeshtasticLogo.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD97E96528EFD9820056DDA4 /* MeshtasticLogo.swift */; };
|
||||
DD97E96828EFE9A00056DDA4 /* About.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD97E96728EFE9A00056DDA4 /* About.swift */; };
|
||||
|
|
@ -203,8 +208,6 @@
|
|||
DDDB26422AABF655003AFCB7 /* NodeListItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDDB26412AABF655003AFCB7 /* NodeListItem.swift */; };
|
||||
DDDB26442AAC0206003AFCB7 /* NodeDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDDB26432AAC0206003AFCB7 /* NodeDetail.swift */; };
|
||||
DDDB26462AACC0B7003AFCB7 /* NodeInfoItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDDB26452AACC0B7003AFCB7 /* NodeInfoItem.swift */; };
|
||||
DDDB26482AACD6D1003AFCB7 /* NodeMapMapkit.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDDB26472AACD6D1003AFCB7 /* NodeMapMapkit.swift */; };
|
||||
DDDB443629F6287000EE2349 /* MapButtons.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDDB443529F6287000EE2349 /* MapButtons.swift */; };
|
||||
DDDB444029F79AB000EE2349 /* UserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDDB443F29F79AB000EE2349 /* UserDefaults.swift */; };
|
||||
DDDB444229F8A88700EE2349 /* Double.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDDB444129F8A88700EE2349 /* Double.swift */; };
|
||||
DDDB444429F8A8DD00EE2349 /* Float.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDDB444329F8A8DD00EE2349 /* Float.swift */; };
|
||||
|
|
@ -277,12 +280,23 @@
|
|||
231B3F202D087A4C0069A07D /* MetricTableColumn.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetricTableColumn.swift; sourceTree = "<group>"; };
|
||||
231B3F242D087C3C0069A07D /* EnvironmentDefaultColumns.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnvironmentDefaultColumns.swift; sourceTree = "<group>"; };
|
||||
231B3F262D0885240069A07D /* MetricsColumnDetail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetricsColumnDetail.swift; sourceTree = "<group>"; };
|
||||
233E99B32D84969500CC3A77 /* MeshtasticDataModelV 50.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 50.xcdatamodel"; sourceTree = "<group>"; };
|
||||
233E99B52D849C3D00CC3A77 /* WeatherConditionsCompactWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeatherConditionsCompactWidget.swift; sourceTree = "<group>"; };
|
||||
233E99B72D849C6500CC3A77 /* HumidityCompactWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HumidityCompactWidget.swift; sourceTree = "<group>"; };
|
||||
233E99B92D849C7000CC3A77 /* PressureCompactWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PressureCompactWidget.swift; sourceTree = "<group>"; };
|
||||
233E99BB2D849C8C00CC3A77 /* WindCompactWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindCompactWidget.swift; sourceTree = "<group>"; };
|
||||
233E99BD2D849D3200CC3A77 /* RadiationCompactWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadiationCompactWidget.swift; sourceTree = "<group>"; };
|
||||
233E99C02D849D6000CC3A77 /* DistanceCompactWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DistanceCompactWidget.swift; sourceTree = "<group>"; };
|
||||
233E99C22D849D7A00CC3A77 /* WeightCompactWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeightCompactWidget.swift; sourceTree = "<group>"; };
|
||||
233E99C42D84A0B600CC3A77 /* CompactWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompactWidget.swift; sourceTree = "<group>"; };
|
||||
233E99C62D84A70900CC3A77 /* SoilCompactWidgets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoilCompactWidgets.swift; sourceTree = "<group>"; };
|
||||
233E99CA2D85AAA900CC3A77 /* RainfallCompactWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RainfallCompactWidget.swift; sourceTree = "<group>"; };
|
||||
2344A2AA2D66973D00170A77 /* ManagedAttributePropertyWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManagedAttributePropertyWrapper.swift; sourceTree = "<group>"; };
|
||||
2344A2AD2D6697A700170A77 /* TelemetryEntity+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TelemetryEntity+CoreDataClass.swift"; sourceTree = "<group>"; };
|
||||
2344A2AE2D6697A700170A77 /* TelemetryEntity+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TelemetryEntity+CoreDataProperties.swift"; sourceTree = "<group>"; };
|
||||
2373AE122D0A216C0086C749 /* MetricsChartSeries.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetricsChartSeries.swift; sourceTree = "<group>"; };
|
||||
2373AE142D0A24930086C749 /* MetricsSeriesList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetricsSeriesList.swift; sourceTree = "<group>"; };
|
||||
2373AE162D0A26620086C749 /* EnviornmentDefaultSeries.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnviornmentDefaultSeries.swift; sourceTree = "<group>"; };
|
||||
2373AE162D0A26620086C749 /* EnvironmentDefaultSeries.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnvironmentDefaultSeries.swift; sourceTree = "<group>"; };
|
||||
251926842C3BA97800249DF5 /* FavoriteNodeButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoriteNodeButton.swift; sourceTree = "<group>"; };
|
||||
251926862C3BAE2200249DF5 /* NodeAlertsButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeAlertsButton.swift; sourceTree = "<group>"; };
|
||||
251926892C3BB1B200249DF5 /* ExchangePositionsButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExchangePositionsButton.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -324,7 +338,6 @@
|
|||
D93068DC2B81CA820066FBC8 /* ConfigHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigHeader.swift; sourceTree = "<group>"; };
|
||||
D93069062B81D8900066FBC8 /* MeshtasticDataModelV 27.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 27.xcdatamodel"; sourceTree = "<group>"; };
|
||||
D93069072B81DF040066FBC8 /* SaveConfigButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SaveConfigButton.swift; sourceTree = "<group>"; };
|
||||
D9BC22DA2B7DE8E2006A37D5 /* TileDownloadStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TileDownloadStatus.swift; sourceTree = "<group>"; };
|
||||
D9C9839C2B79CFD700BDBE6A /* TextMessageSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextMessageSize.swift; sourceTree = "<group>"; };
|
||||
D9C9839F2B79D0E800BDBE6A /* AlertButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertButton.swift; sourceTree = "<group>"; };
|
||||
D9C983A12B79D1A600BDBE6A /* RequestPositionButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestPositionButton.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -359,7 +372,6 @@
|
|||
DD268D8D2BCC90E2008073AE /* RouteEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RouteEnums.swift; sourceTree = "<group>"; };
|
||||
DD295CE92B323ED9002CC4AC /* MeshtasticDataModelV22.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV22.xcdatamodel; sourceTree = "<group>"; };
|
||||
DD2984A82C5AEF7500B1268D /* MeshtasticDataModelV 41.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 41.xcdatamodel"; sourceTree = "<group>"; };
|
||||
DD2AD8A7296D2DF9001FF0E7 /* MapViewSwiftUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapViewSwiftUI.swift; sourceTree = "<group>"; };
|
||||
DD2CC2E52ABFE04E00EDFDA7 /* MeshtasticDataModelV19.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV19.xcdatamodel; sourceTree = "<group>"; };
|
||||
DD31B04D2BDC6FD30024FA63 /* MeshtasticDataModelV 36.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 36.xcdatamodel"; sourceTree = "<group>"; };
|
||||
DD33DB602B3D1ECC003E1EA0 /* MeshtasticDataModelV 23.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 23.xcdatamodel"; sourceTree = "<group>"; };
|
||||
|
|
@ -431,10 +443,8 @@
|
|||
DD93800D2BA74D0C008BEC06 /* ChannelForm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelForm.swift; sourceTree = "<group>"; };
|
||||
DD94B73F2ACCE3BE00DCD1D1 /* MapSettingsForm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapSettingsForm.swift; sourceTree = "<group>"; };
|
||||
DD964FBC296E6B01007C176F /* EmojiOnlyTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiOnlyTextField.swift; sourceTree = "<group>"; };
|
||||
DD964FBE296E76EF007C176F /* WaypointFormMapKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaypointFormMapKit.swift; sourceTree = "<group>"; };
|
||||
DD964FC029724F6D007C176F /* MeshtasticDataModelV6.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV6.xcdatamodel; sourceTree = "<group>"; };
|
||||
DD964FC1297272AE007C176F /* WaypointEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaypointEntityExtension.swift; sourceTree = "<group>"; };
|
||||
DD964FC32974767D007C176F /* MapViewFitExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapViewFitExtension.swift; sourceTree = "<group>"; };
|
||||
DD964FC52975DBFD007C176F /* QueryCoreData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueryCoreData.swift; sourceTree = "<group>"; };
|
||||
DD9681A22BBB22BE00FD2C47 /* MeshtasticDataModelV32.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV32.xcdatamodel; sourceTree = "<group>"; };
|
||||
DD97E96528EFD9820056DDA4 /* MeshtasticLogo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshtasticLogo.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -508,9 +518,7 @@
|
|||
DDDB26412AABF655003AFCB7 /* NodeListItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeListItem.swift; sourceTree = "<group>"; };
|
||||
DDDB26432AAC0206003AFCB7 /* NodeDetail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeDetail.swift; sourceTree = "<group>"; };
|
||||
DDDB26452AACC0B7003AFCB7 /* NodeInfoItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeInfoItem.swift; sourceTree = "<group>"; };
|
||||
DDDB26472AACD6D1003AFCB7 /* NodeMapMapkit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeMapMapkit.swift; sourceTree = "<group>"; };
|
||||
DDDB26492AAD743E003AFCB7 /* MeshtasticDataModelV18.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV18.xcdatamodel; sourceTree = "<group>"; };
|
||||
DDDB443529F6287000EE2349 /* MapButtons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapButtons.swift; sourceTree = "<group>"; };
|
||||
DDDB443F29F79AB000EE2349 /* UserDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaults.swift; sourceTree = "<group>"; };
|
||||
DDDB444129F8A88700EE2349 /* Double.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Double.swift; sourceTree = "<group>"; };
|
||||
DDDB444329F8A8DD00EE2349 /* Float.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Float.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -567,7 +575,6 @@
|
|||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
25A978BA2C13F8ED0003AAE7 /* MeshtasticProtobufs in Frameworks */,
|
||||
C9697FA527933B8C00250207 /* SQLite in Frameworks */,
|
||||
DD0D3D222A55CEB10066DB71 /* CocoaMQTT in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
|
@ -600,12 +607,30 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
231B3F242D087C3C0069A07D /* EnvironmentDefaultColumns.swift */,
|
||||
2373AE162D0A26620086C749 /* EnviornmentDefaultSeries.swift */,
|
||||
2373AE162D0A26620086C749 /* EnvironmentDefaultSeries.swift */,
|
||||
231B3F262D0885240069A07D /* MetricsColumnDetail.swift */,
|
||||
);
|
||||
path = "Metrics Columns";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
233E99B42D849C2D00CC3A77 /* Compact Widgets */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
233E99C42D84A0B600CC3A77 /* CompactWidget.swift */,
|
||||
233E99B72D849C6500CC3A77 /* HumidityCompactWidget.swift */,
|
||||
233E99B52D849C3D00CC3A77 /* WeatherConditionsCompactWidget.swift */,
|
||||
233E99B92D849C7000CC3A77 /* PressureCompactWidget.swift */,
|
||||
233E99BB2D849C8C00CC3A77 /* WindCompactWidget.swift */,
|
||||
DDFEB3BA29900C1200EE7472 /* CurrentConditionsCompact.swift */,
|
||||
233E99BD2D849D3200CC3A77 /* RadiationCompactWidget.swift */,
|
||||
233E99C02D849D6000CC3A77 /* DistanceCompactWidget.swift */,
|
||||
233E99C22D849D7A00CC3A77 /* WeightCompactWidget.swift */,
|
||||
233E99C62D84A70900CC3A77 /* SoilCompactWidgets.swift */,
|
||||
233E99CA2D85AAA900CC3A77 /* RainfallCompactWidget.swift */,
|
||||
);
|
||||
path = "Compact Widgets";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
2344A2AC2D66978000170A77 /* CoreData */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
|
@ -665,27 +690,6 @@
|
|||
path = AppIntents;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C9483F6B2773016700998F6B /* MapKitMap */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C9A7BC0E27759A6800760B50 /* Custom */,
|
||||
DDDB26472AACD6D1003AFCB7 /* NodeMapMapkit.swift */,
|
||||
DD964FBE296E76EF007C176F /* WaypointFormMapKit.swift */,
|
||||
);
|
||||
path = MapKitMap;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C9A7BC0E27759A6800760B50 /* Custom */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DD964FC32974767D007C176F /* MapViewFitExtension.swift */,
|
||||
DD2AD8A7296D2DF9001FF0E7 /* MapViewSwiftUI.swift */,
|
||||
DDDB443529F6287000EE2349 /* MapButtons.swift */,
|
||||
D9BC22DA2B7DE8E2006A37D5 /* TileDownloadStatus.swift */,
|
||||
);
|
||||
path = Custom;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D9C9839E2B79D0C600BDBE6A /* TextMessageField */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
|
@ -777,7 +781,6 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
DD5E523E298F5A9E00D21B61 /* AirQualityIndex.swift */,
|
||||
DDFEB3BA29900C1200EE7472 /* CurrentConditionsCompact.swift */,
|
||||
DDA9515D2BC6F56F00CEA535 /* IndoorAirQuality.swift */,
|
||||
DD354FD82BD96A0B0061A25F /* IAQScale.swift */,
|
||||
DD41A61429AB0035003C5A37 /* NodeWeatherForecast.swift */,
|
||||
|
|
@ -987,7 +990,6 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
DD6D5A312CA1176A00ED3032 /* Layouts */,
|
||||
C9483F6B2773016700998F6B /* MapKitMap */,
|
||||
DDC2E18D26CE25CB0042C5E4 /* Helpers */,
|
||||
DD47E3D726F2F21A00029299 /* Bluetooth */,
|
||||
DDC2E18B26CE25A70042C5E4 /* Messages */,
|
||||
|
|
@ -1038,6 +1040,7 @@
|
|||
DDC2E18D26CE25CB0042C5E4 /* Helpers */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
233E99B42D849C2D00CC3A77 /* Compact Widgets */,
|
||||
DD6F65772C6EAB860053C113 /* Help */,
|
||||
DD3CC6BD28E4CD9800FA9159 /* BatteryGauge.swift */,
|
||||
DD3CC24B2C498D6C001BD3A2 /* BatteryCompact.swift */,
|
||||
|
|
@ -1213,7 +1216,6 @@
|
|||
);
|
||||
name = Meshtastic;
|
||||
packageProductDependencies = (
|
||||
C9697FA427933B8C00250207 /* SQLite */,
|
||||
DD0D3D212A55CEB10066DB71 /* CocoaMQTT */,
|
||||
25A978B92C13F8ED0003AAE7 /* MeshtasticProtobufs */,
|
||||
);
|
||||
|
|
@ -1283,7 +1285,6 @@
|
|||
);
|
||||
mainGroup = DDC2E14B26CE248E0042C5E4;
|
||||
packageReferences = (
|
||||
C9697FA327933B8C00250207 /* XCRemoteSwiftPackageReference "SQLite.swift" */,
|
||||
DD0D3D202A55CEB10066DB71 /* XCRemoteSwiftPackageReference "CocoaMQTT" */,
|
||||
25A978B82C13F8ED0003AAE7 /* XCLocalSwiftPackageReference "MeshtasticProtobufs" */,
|
||||
259792242C2F10B600AD1659 /* XCRemoteSwiftPackageReference "swift-protobuf" */,
|
||||
|
|
@ -1380,11 +1381,9 @@
|
|||
DD836AE726F6B38600ABCC23 /* Connect.swift in Sources */,
|
||||
D93069082B81DF040066FBC8 /* SaveConfigButton.swift in Sources */,
|
||||
DD5E523F298F5A9E00D21B61 /* AirQualityIndex.swift in Sources */,
|
||||
DD964FBF296E76EF007C176F /* WaypointFormMapKit.swift in Sources */,
|
||||
DDD5BB182C2F9C36007E03CA /* OSLogEntryLog.swift in Sources */,
|
||||
DD3501892852FC3B000FC853 /* Settings.swift in Sources */,
|
||||
DDDC22382BA92344002C44F1 /* MeshMapContent.swift in Sources */,
|
||||
DDDB443629F6287000EE2349 /* MapButtons.swift in Sources */,
|
||||
DD5D0A9C2931B9F200F7EA61 /* EthernetModes.swift in Sources */,
|
||||
6DEDA55A2A957B8E00321D2E /* DetectionSensorLog.swift in Sources */,
|
||||
DD798B072915928D005217CD /* ChannelMessageList.swift in Sources */,
|
||||
|
|
@ -1405,8 +1404,10 @@
|
|||
DDB6ABD628AE742000384BA1 /* BluetoothConfig.swift in Sources */,
|
||||
251926902C3CB44900249DF5 /* ClientHistoryButton.swift in Sources */,
|
||||
DDD5BB102C285FB3007E03CA /* AppLogFilter.swift in Sources */,
|
||||
2373AE172D0A26620086C749 /* EnviornmentDefaultSeries.swift in Sources */,
|
||||
2373AE172D0A26620086C749 /* EnvironmentDefaultSeries.swift in Sources */,
|
||||
233E99B82D849C6500CC3A77 /* HumidityCompactWidget.swift in Sources */,
|
||||
DD4640202AFF10F4002A5ECB /* WaypointForm.swift in Sources */,
|
||||
233E99C12D849D6000CC3A77 /* DistanceCompactWidget.swift in Sources */,
|
||||
DD769E0328D18BF1001A3F05 /* DeviceMetricsLog.swift in Sources */,
|
||||
DDAF8C5326EB1DF10058C060 /* BLEManager.swift in Sources */,
|
||||
DD15E4F32B8BA56E00654F61 /* PaxCounterConfig.swift in Sources */,
|
||||
|
|
@ -1427,7 +1428,6 @@
|
|||
DD354FD92BD96A0B0061A25F /* IAQScale.swift in Sources */,
|
||||
DDDB445429F8AD1600EE2349 /* Data.swift in Sources */,
|
||||
DDDB26462AACC0B7003AFCB7 /* NodeInfoItem.swift in Sources */,
|
||||
DD2AD8A8296D2DF9001FF0E7 /* MapViewSwiftUI.swift in Sources */,
|
||||
DDE5B4042B2279A700FCDD05 /* TraceRouteLog.swift in Sources */,
|
||||
DD6193792863875F00E59241 /* SerialConfig.swift in Sources */,
|
||||
DDDB263F2AABEE20003AFCB7 /* NodeList.swift in Sources */,
|
||||
|
|
@ -1436,7 +1436,7 @@
|
|||
DDE9659C2B1C3B6A00531070 /* RouteRecorder.swift in Sources */,
|
||||
B399E8A42B6F486400E4488E /* RetryButton.swift in Sources */,
|
||||
DDB8F4102A9EE5B400230ECE /* Messages.swift in Sources */,
|
||||
DDDB26482AACD6D1003AFCB7 /* NodeMapMapkit.swift in Sources */,
|
||||
233E99C32D849D7A00CC3A77 /* WeightCompactWidget.swift in Sources */,
|
||||
DD4A911E2708C65400501B7E /* AppSettings.swift in Sources */,
|
||||
DD1BD0F32C63C65E008C0C70 /* SecurityConfig.swift in Sources */,
|
||||
DD2160AF28C5552500C17253 /* MQTTConfig.swift in Sources */,
|
||||
|
|
@ -1453,6 +1453,7 @@
|
|||
DD6193772862F90F00E59241 /* CannedMessagesConfig.swift in Sources */,
|
||||
DD3619152B1EF9F900C41C8C /* LocationsHandler.swift in Sources */,
|
||||
DD6F65792C6EADE60053C113 /* DirectMessagesHelp.swift in Sources */,
|
||||
233E99B62D849C3D00CC3A77 /* WeatherConditionsCompactWidget.swift in Sources */,
|
||||
25F5D5C02C3F6DA6008036E3 /* Router.swift in Sources */,
|
||||
DDDB444A29F8AA3A00EE2349 /* CLLocationCoordinate2D.swift in Sources */,
|
||||
25C49D902C471AEA0024FBD1 /* Constants.swift in Sources */,
|
||||
|
|
@ -1479,7 +1480,6 @@
|
|||
D93068DB2B81C85E0066FBC8 /* PowerConfig.swift in Sources */,
|
||||
D93068D32B8129510066FBC8 /* MessageContextMenuItems.swift in Sources */,
|
||||
DD8EBF43285058FA00426DCA /* DisplayConfig.swift in Sources */,
|
||||
DD964FC42974767D007C176F /* MapViewFitExtension.swift in Sources */,
|
||||
BCE2D3C72C7B0D0A008E6199 /* ShortcutsProvider.swift in Sources */,
|
||||
DD47E3D626F17ED900029299 /* CircleText.swift in Sources */,
|
||||
DDC2E18F26CE25FE0042C5E4 /* ContentView.swift in Sources */,
|
||||
|
|
@ -1488,18 +1488,20 @@
|
|||
DD1BD0EE2C603C91008C0C70 /* CustomFormatters.swift in Sources */,
|
||||
DD0BE3102CB9FDC4000BA445 /* DetectionSensorEnums.swift in Sources */,
|
||||
DDD9E4E4284B208E003777C5 /* UserEntityExtension.swift in Sources */,
|
||||
233E99CB2D85AAA900CC3A77 /* RainfallCompactWidget.swift in Sources */,
|
||||
DD2553592855B52700E55709 /* PositionConfig.swift in Sources */,
|
||||
DD97E96828EFE9A00056DDA4 /* About.swift in Sources */,
|
||||
DDDB444029F79AB000EE2349 /* UserDefaults.swift in Sources */,
|
||||
233E99BA2D849C7000CC3A77 /* PressureCompactWidget.swift in Sources */,
|
||||
DDB6ABE028B13AC700384BA1 /* DeviceEnums.swift in Sources */,
|
||||
DD86D40C287F401000BAEB7A /* SaveChannelQRCode.swift in Sources */,
|
||||
D93068DD2B81CA820066FBC8 /* ConfigHeader.swift in Sources */,
|
||||
251926872C3BAE2200249DF5 /* NodeAlertsButton.swift in Sources */,
|
||||
DDA1C48E28DB49D3009933EC /* ChannelRoles.swift in Sources */,
|
||||
D9BC22DB2B7DE8E2006A37D5 /* TileDownloadStatus.swift in Sources */,
|
||||
DDD5BB092C285DDC007E03CA /* AppLog.swift in Sources */,
|
||||
BC5EBA3C2D002A2000C442FF /* MessageNodeIntent.swift in Sources */,
|
||||
DD8ED9C8289CE4B900B3B0AB /* RoutingError.swift in Sources */,
|
||||
233E99C52D84A0B600CC3A77 /* CompactWidget.swift in Sources */,
|
||||
DDC1B81A2AB5377B00C71E39 /* MessagesTips.swift in Sources */,
|
||||
DD964FC62975DBFD007C176F /* QueryCoreData.swift in Sources */,
|
||||
DDB75A112A059258006ED576 /* Url.swift in Sources */,
|
||||
|
|
@ -1535,6 +1537,7 @@
|
|||
8D3F8A3F2D44BB02009EAAA4 /* PowerMetrics.swift in Sources */,
|
||||
2519268A2C3BB1B200249DF5 /* ExchangePositionsButton.swift in Sources */,
|
||||
DD86D40A287F04F100BAEB7A /* InvalidVersion.swift in Sources */,
|
||||
233E99BE2D849D3200CC3A77 /* RadiationCompactWidget.swift in Sources */,
|
||||
DDD94A502845C8F5004A87A0 /* DateTimeText.swift in Sources */,
|
||||
DDB6ABE228B13FB500384BA1 /* PositionConfigEnums.swift in Sources */,
|
||||
DD994B69295F88B60013760A /* IntervalEnums.swift in Sources */,
|
||||
|
|
@ -1566,12 +1569,14 @@
|
|||
DDDB26442AAC0206003AFCB7 /* NodeDetail.swift in Sources */,
|
||||
DD77093F2AA1B146007A8BF0 /* UIColor.swift in Sources */,
|
||||
DDF6B2482A9AEBF500BA6931 /* StoreForwardConfig.swift in Sources */,
|
||||
233E99C72D84A70900CC3A77 /* SoilCompactWidgets.swift in Sources */,
|
||||
BCE2D3C92C7C377F008E6199 /* FactoryResetNodeIntent.swift in Sources */,
|
||||
DD93800B2BA3F968008BEC06 /* NodeMapContent.swift in Sources */,
|
||||
DD41582A28585C32009B0E59 /* RangeTestConfig.swift in Sources */,
|
||||
DD1925B728CDA5A400720036 /* CannedMessagesConfigEnums.swift in Sources */,
|
||||
DDDB444429F8A8DD00EE2349 /* Float.swift in Sources */,
|
||||
DDAB580F2B0DAFBC00147258 /* LocationEntityExtension.swift in Sources */,
|
||||
233E99BC2D849C8C00CC3A77 /* WindCompactWidget.swift in Sources */,
|
||||
B3E905B12B71F7F300654D07 /* TextMessageField.swift in Sources */,
|
||||
BC6B45FF2CB2F98900723CEB /* SaveChannelSettingsIntent.swift in Sources */,
|
||||
D93068D72B8146690066FBC8 /* MessageText.swift in Sources */,
|
||||
|
|
@ -1800,7 +1805,7 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.5.20;
|
||||
MARKETING_VERSION = 2.5.22;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
|
|
@ -1834,7 +1839,7 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.5.20;
|
||||
MARKETING_VERSION = 2.5.22;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
|
|
@ -1866,7 +1871,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.5.20;
|
||||
MARKETING_VERSION = 2.5.22;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
|
|
@ -1899,7 +1904,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.5.20;
|
||||
MARKETING_VERSION = 2.5.22;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
|
|
@ -1968,14 +1973,6 @@
|
|||
minimumVersion = 1.26.0;
|
||||
};
|
||||
};
|
||||
C9697FA327933B8C00250207 /* XCRemoteSwiftPackageReference "SQLite.swift" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/stephencelis/SQLite.swift.git";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 0.9.2;
|
||||
};
|
||||
};
|
||||
DD0D3D202A55CEB10066DB71 /* XCRemoteSwiftPackageReference "CocoaMQTT" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/emqx/CocoaMQTT";
|
||||
|
|
@ -1995,11 +1992,6 @@
|
|||
isa = XCSwiftPackageProductDependency;
|
||||
productName = MeshtasticProtobufs;
|
||||
};
|
||||
C9697FA427933B8C00250207 /* SQLite */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = C9697FA327933B8C00250207 /* XCRemoteSwiftPackageReference "SQLite.swift" */;
|
||||
productName = SQLite;
|
||||
};
|
||||
DD0D3D212A55CEB10066DB71 /* CocoaMQTT */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = DD0D3D202A55CEB10066DB71 /* XCRemoteSwiftPackageReference "CocoaMQTT" */;
|
||||
|
|
@ -2011,6 +2003,7 @@
|
|||
DD3CC6BA28E366DF00FA9159 /* Meshtastic.xcdatamodeld */ = {
|
||||
isa = XCVersionGroup;
|
||||
children = (
|
||||
233E99B32D84969500CC3A77 /* MeshtasticDataModelV 50.xcdatamodel */,
|
||||
8D3F8A3D2D44B137009EAAA4 /* MeshtasticDataModelV 49.xcdatamodel */,
|
||||
DDA28B1B2D32C89200EF726F /* MeshtasticDataModelV 48.xcdatamodel */,
|
||||
DDDFE7402D0D4A070044463C /* MeshtasticDataModelV 47.xcdatamodel */,
|
||||
|
|
@ -2061,7 +2054,7 @@
|
|||
DD5D0A9A2931AD6B00F7EA61 /* MeshtasticDataModelV2.xcdatamodel */,
|
||||
DD3CC6BB28E366DF00FA9159 /* MeshtasticDataModel.xcdatamodel */,
|
||||
);
|
||||
currentVersion = 8D3F8A3D2D44B137009EAAA4 /* MeshtasticDataModelV 49.xcdatamodel */;
|
||||
currentVersion = 233E99B32D84969500CC3A77 /* MeshtasticDataModelV 50.xcdatamodel */;
|
||||
name = Meshtastic.xcdatamodeld;
|
||||
path = Meshtastic/Meshtastic.xcdatamodeld;
|
||||
sourceTree = "<group>";
|
||||
|
|
|
|||
|
|
@ -1,51 +0,0 @@
|
|||
{
|
||||
"originHash" : "2d0b85469585b0d6079eac292d63864096062c24848a49380b9d9727f0ceb96c",
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "cocoamqtt",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/emqx/CocoaMQTT",
|
||||
"state" : {
|
||||
"revision" : "85387a2478551ad84f39be8a3c8587d34dd2bcf5",
|
||||
"version" : "2.1.5"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "mqttcocoaasyncsocket",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/leeway1208/MqttCocoaAsyncSocket",
|
||||
"state" : {
|
||||
"revision" : "ce3e18607fd01079495f86ff6195d8a3ca469f73",
|
||||
"version" : "1.0.8"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "sqlite.swift",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/stephencelis/SQLite.swift.git",
|
||||
"state" : {
|
||||
"revision" : "7a2e3cd27de56f6d396e84f63beefd0267b55ccb",
|
||||
"version" : "0.14.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "starscream",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/daltoniam/Starscream.git",
|
||||
"state" : {
|
||||
"revision" : "a063fda2b8145a231953c20e7a646be254365396",
|
||||
"version" : "3.1.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-protobuf",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-protobuf.git",
|
||||
"state" : {
|
||||
"revision" : "9f0c76544701845ad98716f3f6a774a892152bcb",
|
||||
"version" : "1.26.0"
|
||||
}
|
||||
}
|
||||
],
|
||||
"version" : 3
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict/>
|
||||
</plist>
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"originHash" : "1571e0d09fede5d57a2c415019f30868d90fde5a53a863cc277593881c2dc4a5",
|
||||
"originHash" : "a3033aea781828906c453276e3723177901ce64df5757de7ada28c854c9662eb",
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "cocoamqtt",
|
||||
|
|
@ -19,15 +19,6 @@
|
|||
"version" : "1.0.8"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "sqlite.swift",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/stephencelis/SQLite.swift.git",
|
||||
"state" : {
|
||||
"revision" : "a95fc6df17d108bd99210db5e8a9bac90fe984b8",
|
||||
"version" : "0.15.3"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "starscream",
|
||||
"kind" : "remoteSourceControl",
|
||||
|
|
@ -42,8 +33,8 @@
|
|||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-protobuf.git",
|
||||
"state" : {
|
||||
"revision" : "edb6ed4919f7756157fe02f2552b7e3850a538e5",
|
||||
"version" : "1.28.1"
|
||||
"revision" : "d72aed98f8253ec1aa9ea1141e28150f408cf17f",
|
||||
"version" : "1.29.0"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ class AppIntentErrors {
|
|||
var localizedStringResource: LocalizedStringResource {
|
||||
switch self {
|
||||
case let .message(message):
|
||||
Logger.services.error("App Intent: \(message)")
|
||||
Logger.services.error("App Intent: \(message,privacy: .public)")
|
||||
return "Error: \(message)"
|
||||
case .notConnected:
|
||||
Logger.services.error("App Intent: No Connected Node")
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"symbols" : [
|
||||
{
|
||||
"filename" : "soilMoisture.variable.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,366 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!--Generator: Apple Native CoreSVG 341-->
|
||||
|
||||
<svg
|
||||
version="1.1"
|
||||
viewBox="0 0 3300 2200"
|
||||
id="svg681"
|
||||
sodipodi:docname="groundmoisture.variable.svg"
|
||||
inkscape:version="1.2.1 (9c6d41e, 2022-07-14)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs685" />
|
||||
<sodipodi:namedview
|
||||
id="namedview683"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="false"
|
||||
inkscape:zoom="0.55606061"
|
||||
inkscape:cx="1991.6894"
|
||||
inkscape:cy="935.14986"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1440"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="Symbols"
|
||||
showguides="false" />
|
||||
<g
|
||||
id="Notes">
|
||||
<rect
|
||||
height="2200"
|
||||
id="artboard"
|
||||
style="fill:white;opacity:1"
|
||||
width="3300"
|
||||
x="0"
|
||||
y="0" />
|
||||
<line
|
||||
style="fill:none;stroke:black;opacity:1;stroke-width:0.5;"
|
||||
x1="263"
|
||||
x2="3036"
|
||||
y1="292"
|
||||
y2="292"
|
||||
id="line562" />
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;font-weight:bold;"
|
||||
transform="matrix(1 0 0 1 263 322)"
|
||||
id="text564">Weight/Scale Variations</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;"
|
||||
transform="matrix(1 0 0 1 559.711 322)"
|
||||
id="text566">Ultralight</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;"
|
||||
transform="matrix(1 0 0 1 856.422 322)"
|
||||
id="text568">Thin</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;"
|
||||
transform="matrix(1 0 0 1 1153.13 322)"
|
||||
id="text570">Light</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;"
|
||||
transform="matrix(1 0 0 1 1449.84 322)"
|
||||
id="text572">Regular</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;"
|
||||
transform="matrix(1 0 0 1 1746.56 322)"
|
||||
id="text574">Medium</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;"
|
||||
transform="matrix(1 0 0 1 2043.27 322)"
|
||||
id="text576">Semibold</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;"
|
||||
transform="matrix(1 0 0 1 2339.98 322)"
|
||||
id="text578">Bold</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;"
|
||||
transform="matrix(1 0 0 1 2636.69 322)"
|
||||
id="text580">Heavy</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;"
|
||||
transform="matrix(1 0 0 1 2933.4 322)"
|
||||
id="text582">Black</text>
|
||||
<line
|
||||
style="fill:none;stroke:black;opacity:1;stroke-width:0.5;"
|
||||
x1="263"
|
||||
x2="3036"
|
||||
y1="1903"
|
||||
y2="1903"
|
||||
id="line584" />
|
||||
<g
|
||||
transform="matrix(0.2 0 0 0.2 263 1933)"
|
||||
id="g588">
|
||||
<path
|
||||
d="m46.2402 4.15039c21.7773 0 39.4531-17.627 39.4531-39.4043s-17.6758-39.4043-39.4531-39.4043c-21.7285 0-39.4043 17.627-39.4043 39.4043s17.6758 39.4043 39.4043 39.4043Zm0-7.42188c-17.6758 0-31.9336-14.3066-31.9336-31.9824s14.2578-31.9824 31.9336-31.9824 31.9824 14.3066 31.9824 31.9824-14.3066 31.9824-31.9824 31.9824Zm-17.9688-31.9824c0 2.14844 1.51367 3.61328 3.75977 3.61328h10.498v10.5957c0 2.19727 1.46484 3.71094 3.61328 3.71094 2.24609 0 3.71094-1.51367 3.71094-3.71094v-10.5957h10.5957c2.19727 0 3.71094-1.46484 3.71094-3.61328 0-2.19727-1.51367-3.71094-3.71094-3.71094h-10.5957v-10.5469c0-2.24609-1.46484-3.75977-3.71094-3.75977-2.14844 0-3.61328 1.51367-3.61328 3.75977v10.5469h-10.498c-2.24609 0-3.75977 1.51367-3.75977 3.71094Z"
|
||||
id="path586" />
|
||||
</g>
|
||||
<g
|
||||
transform="matrix(0.2 0 0 0.2 281.506 1933)"
|
||||
id="g592">
|
||||
<path
|
||||
d="m58.5449 14.5508c27.4902 0 49.8047-22.3145 49.8047-49.8047s-22.3145-49.8047-49.8047-49.8047-49.8047 22.3145-49.8047 49.8047 22.3145 49.8047 49.8047 49.8047Zm0-8.30078c-22.9492 0-41.5039-18.5547-41.5039-41.5039s18.5547-41.5039 41.5039-41.5039 41.5039 18.5547 41.5039 41.5039-18.5547 41.5039-41.5039 41.5039Zm-22.6562-41.5039c0 2.39258 1.66016 4.00391 4.15039 4.00391h14.3555v14.4043c0 2.44141 1.66016 4.15039 4.05273 4.15039 2.44141 0 4.15039-1.66016 4.15039-4.15039v-14.4043h14.4043c2.44141 0 4.15039-1.61133 4.15039-4.00391 0-2.44141-1.70898-4.15039-4.15039-4.15039h-14.4043v-14.3555c0-2.49023-1.70898-4.19922-4.15039-4.19922-2.39258 0-4.05273 1.70898-4.05273 4.19922v14.3555h-14.3555c-2.49023 0-4.15039 1.70898-4.15039 4.15039Z"
|
||||
id="path590" />
|
||||
</g>
|
||||
<g
|
||||
transform="matrix(0.2 0 0 0.2 304.924 1933)"
|
||||
id="g596">
|
||||
<path
|
||||
d="m74.8535 28.3203c35.1074 0 63.623-28.4668 63.623-63.5742s-28.5156-63.623-63.623-63.623-63.5742 28.5156-63.5742 63.623 28.4668 63.5742 63.5742 63.5742Zm0-9.08203c-30.127 0-54.4922-24.3652-54.4922-54.4922s24.3652-54.4922 54.4922-54.4922 54.4922 24.3652 54.4922 54.4922-24.3652 54.4922-54.4922 54.4922Zm-28.8574-54.4922c0 2.58789 1.85547 4.39453 4.58984 4.39453h19.7266v19.7754c0 2.68555 1.85547 4.58984 4.44336 4.58984 2.68555 0 4.54102-1.85547 4.54102-4.58984v-19.7754h19.7754c2.68555 0 4.58984-1.80664 4.58984-4.39453 0-2.73438-1.85547-4.58984-4.58984-4.58984h-19.7754v-19.7266c0-2.73438-1.85547-4.63867-4.54102-4.63867-2.58789 0-4.44336 1.9043-4.44336 4.63867v19.7266h-19.7266c-2.73438 0-4.58984 1.85547-4.58984 4.58984Z"
|
||||
id="path594" />
|
||||
</g>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;font-weight:bold;"
|
||||
transform="matrix(1 0 0 1 263 1953)"
|
||||
id="text598">Design Variations</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;"
|
||||
transform="matrix(1 0 0 1 263 1971)"
|
||||
id="text600">Symbols are supported in up to nine weights and three scales.</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;"
|
||||
transform="matrix(1 0 0 1 263 1989)"
|
||||
id="text602">For optimal layout with text and other symbols, vertically align</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;"
|
||||
transform="matrix(1 0 0 1 263 2007)"
|
||||
id="text604">symbols with the adjacent text.</text>
|
||||
<line
|
||||
style="fill:none;stroke:#00AEEF;stroke-width:0.5;opacity:1.0;"
|
||||
x1="776"
|
||||
x2="776"
|
||||
y1="1919"
|
||||
y2="1933"
|
||||
id="line606" />
|
||||
<g
|
||||
transform="matrix(0.2 0 0 0.2 776 1933)"
|
||||
id="g610">
|
||||
<path
|
||||
d="m16.5527 0.78125c2.58789 0 3.85742-0.976562 4.78516-3.71094l6.29883-17.2363h28.8086l6.29883 17.2363c0.927734 2.73438 2.19727 3.71094 4.73633 3.71094 2.58789 0 4.24805-1.5625 4.24805-4.00391 0-0.830078-0.146484-1.61133-0.537109-2.63672l-22.9004-60.9863c-1.12305-2.97852-3.125-4.49219-6.25-4.49219-3.02734 0-5.07812 1.46484-6.15234 4.44336l-22.9004 61.084c-0.390625 1.02539-0.537109 1.80664-0.537109 2.63672 0 2.44141 1.5625 3.95508 4.10156 3.95508Zm13.4766-28.3691 11.8652-32.8613h0.244141l11.8652 32.8613Z"
|
||||
id="path608" />
|
||||
</g>
|
||||
<line
|
||||
style="fill:none;stroke:#00AEEF;stroke-width:0.5;opacity:1.0;"
|
||||
x1="792.836"
|
||||
x2="792.836"
|
||||
y1="1919"
|
||||
y2="1933"
|
||||
id="line612" />
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;font-weight:bold;"
|
||||
transform="matrix(1 0 0 1 776 1953)"
|
||||
id="text614">Margins</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;"
|
||||
transform="matrix(1 0 0 1 776 1971)"
|
||||
id="text616">Leading and trailing margins on the left and right side of each symbol</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;"
|
||||
transform="matrix(1 0 0 1 776 1989)"
|
||||
id="text618">can be adjusted by modifying the x-location of the margin guidelines.</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;"
|
||||
transform="matrix(1 0 0 1 776 2007)"
|
||||
id="text620">Modifications are automatically applied proportionally to all</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;"
|
||||
transform="matrix(1 0 0 1 776 2025)"
|
||||
id="text622">scales and weights.</text>
|
||||
<g
|
||||
transform="matrix(0.2 0 0 0.2 1289 1933)"
|
||||
id="g626">
|
||||
<path
|
||||
d="m14.209 9.32617 8.49609 8.54492c4.29688 4.3457 9.22852 4.05273 13.8672-1.07422l53.4668-58.9355-4.83398-4.88281-53.0762 58.3984c-1.75781 2.00195-3.41797 2.49023-5.76172 0.146484l-5.85938-5.81055c-2.34375-2.29492-1.80664-4.00391 0.195312-5.81055l57.373-54.0039-4.88281-4.83398-57.959 54.4434c-4.93164 4.58984-5.32227 9.47266-1.02539 13.8184Zm32.0801-90.9668c-2.09961 2.05078-2.24609 4.93164-1.07422 6.88477 1.17188 1.80664 3.4668 2.97852 6.68945 2.14844 7.32422-1.70898 14.9414-2.00195 22.0703 2.68555l-2.92969 7.27539c-1.70898 4.15039-0.830078 7.08008 1.85547 9.81445l11.4746 11.5723c2.44141 2.44141 4.49219 2.53906 7.32422 2.05078l5.32227-0.976562 3.32031 3.36914-0.195312 2.7832c-0.195312 2.49023 0.439453 4.39453 2.88086 6.78711l3.80859 3.71094c2.39258 2.39258 5.46875 2.53906 7.8125 0.195312l14.5508-14.5996c2.34375-2.34375 2.24609-5.32227-0.146484-7.71484l-3.85742-3.80859c-2.39258-2.39258-4.24805-3.17383-6.64062-2.97852l-2.88086 0.244141-3.22266-3.17383 1.2207-5.61523c0.634766-2.83203-0.146484-5.0293-3.07617-7.95898l-10.9863-10.9375c-16.6992-16.6016-38.8672-16.2109-53.3203-1.75781Zm7.4707 1.85547c12.1582-8.88672 28.6133-7.37305 39.7461 3.75977l12.1582 12.0605c1.17188 1.17188 1.36719 2.09961 1.02539 3.80859l-1.61133 7.42188 7.51953 7.42188 4.93164-0.292969c1.26953-0.0488281 1.66016 0.0488281 2.63672 1.02539l2.88086 2.88086-12.207 12.207-2.88086-2.88086c-0.976562-0.976562-1.12305-1.36719-1.07422-2.68555l0.341797-4.88281-7.4707-7.42188-7.61719 1.26953c-1.61133 0.341797-2.34375 0.195312-3.56445-0.976562l-10.0098-10.0098c-1.26953-1.17188-1.41602-2.00195-0.634766-3.85742l4.39453-10.4492c-7.8125-7.27539-17.9688-10.4004-28.125-7.42188-0.78125 0.195312-1.07422-0.439453-0.439453-0.976562Z"
|
||||
id="path624" />
|
||||
</g>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;font-weight:bold;"
|
||||
transform="matrix(1 0 0 1 1289 1953)"
|
||||
id="text628">Exporting</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;"
|
||||
transform="matrix(1 0 0 1 1289 1971)"
|
||||
id="text630">Symbols should be outlined when exporting to ensure the</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;"
|
||||
transform="matrix(1 0 0 1 1289 1989)"
|
||||
id="text632">design is preserved when submitting to Xcode.</text>
|
||||
<text
|
||||
id="template-version"
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:end;"
|
||||
transform="matrix(1 0 0 1 3036 1933)">Template v.6.0</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:end;"
|
||||
transform="matrix(1 0 0 1 3036 1951)"
|
||||
id="text635">Requires Xcode 16 or greater</text>
|
||||
<text
|
||||
id="descriptive-name"
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:end;"
|
||||
transform="matrix(1 0 0 1 3036 1969)">Generated from thermometer.variable</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:end;"
|
||||
transform="matrix(1 0 0 1 3036 1987)"
|
||||
id="text638">Typeset at 100.0 points</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;"
|
||||
transform="matrix(1 0 0 1 263 726)"
|
||||
id="text640">Small</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;"
|
||||
transform="matrix(1 0 0 1 263 1156)"
|
||||
id="text642">Medium</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;"
|
||||
transform="matrix(1 0 0 1 263 1586)"
|
||||
id="text644">Large</text>
|
||||
</g>
|
||||
<g
|
||||
id="Guides">
|
||||
<g
|
||||
id="H-reference"
|
||||
style="fill:#27AAE1;stroke:none;"
|
||||
transform="matrix(1 0 0 1 339 696)">
|
||||
<path
|
||||
d="M0.993654 0L3.63775 0L29.3281-67.1323L30.0303-67.1323L30.0303-70.459L28.1226-70.459ZM11.6885-24.4799L46.9815-24.4799L46.2315-26.7285L12.4385-26.7285ZM55.1196 0L57.7637 0L30.6382-70.459L29.4326-70.459L29.4326-67.1323Z"
|
||||
id="path647" />
|
||||
</g>
|
||||
<line
|
||||
id="Baseline-S"
|
||||
style="fill:none;stroke:#27AAE1;opacity:1;stroke-width:0.5;"
|
||||
x1="263"
|
||||
x2="3036"
|
||||
y1="696"
|
||||
y2="696" />
|
||||
<line
|
||||
id="Capline-S"
|
||||
style="fill:none;stroke:#27AAE1;opacity:1;stroke-width:0.5;"
|
||||
x1="263"
|
||||
x2="3036"
|
||||
y1="625.541"
|
||||
y2="625.541" />
|
||||
<g
|
||||
id="g654"
|
||||
style="fill:#27AAE1;stroke:none;"
|
||||
transform="matrix(1 0 0 1 339 1126)">
|
||||
<path
|
||||
d="M0.993654 0L3.63775 0L29.3281-67.1323L30.0303-67.1323L30.0303-70.459L28.1226-70.459ZM11.6885-24.4799L46.9815-24.4799L46.2315-26.7285L12.4385-26.7285ZM55.1196 0L57.7637 0L30.6382-70.459L29.4326-70.459L29.4326-67.1323Z"
|
||||
id="path652" />
|
||||
</g>
|
||||
<line
|
||||
id="Baseline-M"
|
||||
style="fill:none;stroke:#27AAE1;opacity:1;stroke-width:0.5;"
|
||||
x1="263"
|
||||
x2="3036"
|
||||
y1="1126"
|
||||
y2="1126" />
|
||||
<line
|
||||
id="Capline-M"
|
||||
style="fill:none;stroke:#27AAE1;opacity:1;stroke-width:0.5;"
|
||||
x1="263"
|
||||
x2="3036"
|
||||
y1="1055.54"
|
||||
y2="1055.54" />
|
||||
<g
|
||||
id="g660"
|
||||
style="fill:#27AAE1;stroke:none;"
|
||||
transform="matrix(1 0 0 1 339 1556)">
|
||||
<path
|
||||
d="M0.993654 0L3.63775 0L29.3281-67.1323L30.0303-67.1323L30.0303-70.459L28.1226-70.459ZM11.6885-24.4799L46.9815-24.4799L46.2315-26.7285L12.4385-26.7285ZM55.1196 0L57.7637 0L30.6382-70.459L29.4326-70.459L29.4326-67.1323Z"
|
||||
id="path658" />
|
||||
</g>
|
||||
<line
|
||||
id="Baseline-L"
|
||||
style="fill:none;stroke:#27AAE1;opacity:1;stroke-width:0.5;"
|
||||
x1="263"
|
||||
x2="3036"
|
||||
y1="1556"
|
||||
y2="1556" />
|
||||
<line
|
||||
id="Capline-L"
|
||||
style="fill:none;stroke:#27AAE1;opacity:1;stroke-width:0.5;"
|
||||
x1="263"
|
||||
x2="3036"
|
||||
y1="1485.54"
|
||||
y2="1485.54" />
|
||||
<line
|
||||
id="right-margin-Black-S"
|
||||
style="opacity:1;fill:none;stroke:#00aeef;stroke-width:0.5"
|
||||
x1="2994.5601"
|
||||
x2="2994.5601"
|
||||
y1="600.78497"
|
||||
y2="720.12097" />
|
||||
<line
|
||||
id="left-margin-Black-S"
|
||||
style="opacity:1;fill:none;stroke:#00aeef;stroke-width:0.5"
|
||||
x1="2882.24"
|
||||
x2="2882.24"
|
||||
y1="600.78497"
|
||||
y2="720.12097" />
|
||||
<line
|
||||
id="right-margin-Regular-S"
|
||||
style="opacity:1;fill:none;stroke:#00aeef;stroke-width:0.5"
|
||||
x1="1492"
|
||||
x2="1492"
|
||||
y1="600.78497"
|
||||
y2="720.12097" />
|
||||
<line
|
||||
id="left-margin-Regular-S"
|
||||
style="opacity:1;fill:none;stroke:#00aeef;stroke-width:0.5"
|
||||
x1="1395.6899"
|
||||
x2="1395.6899"
|
||||
y1="600.78497"
|
||||
y2="720.12097" />
|
||||
<line
|
||||
id="right-margin-Ultralight-S"
|
||||
style="opacity:1;fill:none;stroke:#00aeef;stroke-width:0.5"
|
||||
x1="605.70599"
|
||||
x2="605.70599"
|
||||
y1="600.78497"
|
||||
y2="720.12097" />
|
||||
<line
|
||||
id="left-margin-Ultralight-S"
|
||||
style="opacity:1;fill:none;stroke:#00aeef;stroke-width:0.5"
|
||||
x1="513.71698"
|
||||
x2="513.71698"
|
||||
y1="600.78497"
|
||||
y2="720.12097" />
|
||||
</g>
|
||||
<g
|
||||
id="Symbols">
|
||||
<g
|
||||
id="Black-S"
|
||||
transform="matrix(1 0 0 1 2898.24 696)"
|
||||
style="fill:#000000">
|
||||
<path
|
||||
id="path1811"
|
||||
style="fill:#000000;stroke:#000000;stroke-width:0.5;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 40.160224,10.512322 c 13.8183,0 24.9508,-10.88863047 24.9508,-24.560501 0,-7.1289 -3.32,-13.6719 -5.5172,-17.7734 l -8.545,-15.869199 c -2.1972,-4.1504 -6.2011,-6.5918 -10.8886,-6.5918 -4.6387,0 -8.6426,2.4414 -10.8399,6.543 l -8.4961,15.869099 c -2.2461,4.1504 -5.6152,10.6446 -5.6152,17.8223 0,13.67187053 11.1816,24.560501 24.9512,24.560501 z m -18.6035,-24.560501 c 0,-5.5176 2.6855,-10.8399 4.8339,-14.7949 l 8.4961,-15.869199 c 1.1231,-2.0508 2.9785,-3.2226 5.2735,-3.2226 2.2949,0 4.1504,1.123 5.2734,3.2226 l 8.5449,15.869199 c 2.0996,3.955 4.7852,9.2773 4.7852,14.7949 0,10.1074205 -8.252,18.2128805 -18.6035,18.2128805 -10.3028,0 -18.6035,-8.10546 -18.6035,-18.2128805 z m 18.6035,6.5917905 c -4.9805,0 -8.9356,-3.8085895 -8.9356,-8.5448905 0,-3.2227 1.6602,-6.2989 3.7598,-10.2539 l 4.7851,-8.935599 c 0.1954,-0.3906 0.6348,-0.3906 0.8301,0 l 4.7364,8.935599 c 2.0996,3.955 3.7597,7.0312 3.7597,10.2539 0,4.736301 -3.9062,8.5448905 -8.9355,8.5448905 z M 93.819567,-0.549 c 0,2.346 -1.91,4.25 -4.25,4.25 -2.35,0 -4.25,-1.904 -4.25,-4.25 0,-0.295 -0.01,-0.589 -0.03,-0.881 -0.15,-2.341 1.62,-4.366 3.96,-4.519 2.34,-0.154 4.37,1.621 4.52,3.962 0.03,0.477 0.05,0.956 0.05,1.438 z m -5.03,-14.361 c 1.4,1.882 1.01,4.547 -0.87,5.947 -1.88,1.401 -4.55,1.011 -5.95,-0.871 -0.38,-0.515 -0.79,-1.02 -1.23,-1.516 -1.56,-1.755 -1.4,-4.443 0.36,-6 1.75,-1.556 4.44,-1.395 6,0.36 0.6,0.68 1.16,1.374 1.69,2.08 z m -11.75,-10.145 c 2.02,1.186 2.7,3.792 1.52,5.815 -1.19,2.024 -3.79,2.704 -5.82,1.518 -0.6,-0.352 -1.22,-0.695 -1.85,-1.027 -2.08,-1.088 -2.88,-3.659 -1.79,-5.737 1.09,-2.077 3.66,-2.881 5.73,-1.792 0.76,0.395 1.5,0.803 2.21,1.223 z M 5.8395672,-26.278 c 2.08,-1.089 4.6499998,-0.285 5.7399998,1.792 1.08,2.078 0.28,4.649 -1.7999998,5.737 -0.63,0.332 -1.25,0.675 -1.85,1.027 -2.02,1.186 -4.63,0.506 -5.82,-1.518 -1.17999998,-2.023 -0.5,-4.629 1.52,-5.815 0.72,-0.42 1.45,-0.828 2.21,-1.223 z m -12.26,9.288 c 1.55,-1.755 4.24,-1.916 6.00000002,-0.36 1.74999998,1.557 1.90999998,4.245 0.36,6 -0.44,0.496 -0.86,1.001 -1.24000002,1.516 -1.4,1.882 -4.06,2.272 -5.95,0.871 -1.88,-1.4 -2.27,-4.065 -0.87,-5.946 0.53,-0.707 1.09,-1.401 1.7,-2.081 z m -6.6800002,15.003 c 0.15,-2.341 2.18,-4.116 4.5200002,-3.962 2.34,0.153 4.12,2.178 3.96,4.519 -0.02,0.292 -0.03,0.586 -0.03,0.881 0,2.346 -1.9,4.25 -4.25,4.25 -2.3400002,0 -4.2500002,-1.904 -4.2500002,-4.25 0,-0.482 0.02,-0.961 0.05,-1.438 z" />
|
||||
</g>
|
||||
<g
|
||||
id="Regular-S"
|
||||
transform="matrix(1 0 0 1 1419.69 696)"
|
||||
style="fill:#000000">
|
||||
<path
|
||||
id="path1814"
|
||||
style="fill:#000000;stroke:#000000;stroke-opacity:1;stroke-width:0.5;stroke-dasharray:none"
|
||||
d="m 40.829821,-13.588987 c 0,-4.9805 -2.4903,-9.8633 -4.5899,-13.7695 l -8.3984,-15.5762 c -0.9766,-1.8555 -1.8555,-2.6855 -3.711,-2.6855 -1.8554,0 -2.7343,0.83 -3.7109,2.6855 l -8.3496,15.5762 c -2.0508,3.9062 -4.5410003,8.789 -4.5410003,13.7695 0,8.9843901 7.4218003,16.2597781 16.6015003,16.2597781 9.2774,0 16.6993,-7.275388 16.6993,-16.2597781 z m -16.6993,21.9726701 c -12.3047,0 -22.3144003,-9.76563 -22.3144003,-21.9726701 0,-6.4453 3.125,-12.4023 5.2246,-16.4062 l 8.3496003,-15.6739 c 1.9043,-3.5156 4.6387,-5.664 8.7402,-5.664 4.1504,0 6.8848,2.1484 8.7891,5.664 l 8.3984,15.625 c 2.0996,4.0528 5.1758,10.0098 5.1758,16.4551 0,12.2070401 -10.0098,21.9726701 -22.3633,21.9726701 z m 46.31122,-10.4926827 c 0,1.518 -1.23,2.75 -2.75,2.75 -1.51,0 -2.75,-1.232 -2.75,-2.75 0,-0.328 -0.01,-0.654 -0.03,-0.979 -0.1,-1.515 1.05,-2.825 2.57,-2.924 1.51,-0.1 2.82,1.049 2.92,2.564 0.03,0.444 0.04,0.891 0.04,1.339 z m -4.73,-13.4640004 c 0.91,1.217 0.66,2.941 -0.56,3.848 -1.22,0.906 -2.94,0.653 -3.85,-0.564 -0.41,-0.549 -0.85,-1.088 -1.32,-1.616 -1,-1.136 -0.9,-2.875 0.24,-3.882 1.13,-1.007 2.87,-0.903 3.88,0.232 0.57,0.648 1.11,1.309 1.61,1.982 z m -11.3,-9.748 c 1.31,0.768 1.75,2.454 0.98,3.763 -0.77,1.31 -2.45,1.75 -3.76,0.982 -0.63,-0.364 -1.26,-0.718 -1.92,-1.061 -1.34,-0.704 -1.86,-2.368 -1.16,-3.712 0.7,-1.345 2.37,-1.864 3.71,-1.16 0.74,0.384 1.45,0.78 2.15,1.188 z m -58.7500004,-1.188 c 1.34,-0.704 3.01,-0.185 3.71,1.16 0.71,1.344 0.19,3.008 -1.16,3.712 -0.65,0.343 -1.29,0.697 -1.91,1.061 -1.31,0.768 -3,0.328 -3.77,-0.982 -0.76,-1.309 -0.32,-2.995 0.98,-3.763 0.7,-0.408 1.42,-0.804 2.15,-1.188 z m -11.8399996,8.954 c 1.01,-1.135 2.75,-1.239 3.89,-0.232 1.13,1.007 1.24,2.746 0.23,3.882 -0.47,0.528 -0.91,1.067 -1.32,1.616 -0.9,1.217 -2.63,1.47 -3.85,0.564 -1.21,-0.907 -1.47,-2.631 -0.56,-3.848 0.5,-0.673 1.04,-1.334 1.61,-1.982 z m -6.3,14.1070004 c 0.1,-1.515 1.41,-2.664 2.93,-2.564 1.51,0.099 2.66,1.409 2.56,2.924 -0.02,0.325 -0.03,0.651 -0.03,0.979 0,1.518 -1.23,2.75 -2.75,2.75 -1.52,0 -2.75,-1.232 -2.75,-2.75 0,-0.448 0.01,-0.895 0.04,-1.339 z m 46.60878,0.4059027 c -6.0547,0 -10.8886,-4.63867 -10.8886,-10.5468901 0,-3.6621 1.8554,-7.1777 3.9062,-11.084 l 6.6895,-12.4023 c 0.1953,-0.3418 0.4882,-0.3418 0.6836,0 l 6.5918,12.4023 c 2.0507,3.9063 4.0039,7.4219 4.0039,11.084 0,5.9082201 -4.8829,10.5468901 -10.9864,10.5468901 z" />
|
||||
</g>
|
||||
<g
|
||||
id="Ultralight-S"
|
||||
transform="matrix(1 0 0 1 531.717 696)"
|
||||
style="fill:#000000">
|
||||
<path
|
||||
id="path1817"
|
||||
style="fill:#000000;stroke:#000000;stroke-width:0.5;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 8.8343368,-13.746838 c 0,-5.673299 2.7617002,-11.221699 5.0883002,-15.497999 l 9.1216,-17.0816 c 1.0869,-1.9716 2.8223,-3.0303 4.9712,-3.0303 2.1524,0 3.8878,1.0587 4.9747,3.1212 l 9.0796,16.9873 c 2.372,4.2797 5.0849,9.8281 5.0849,15.501399 0,10.4814908 -8.5112,18.7940007 -19.1392,18.7940007 -10.6245,0 -19.1811002,-8.3125099 -19.1811002,-18.7940007 z M 28.015437,1.9680428 c 8.9595,0 16.1089,-6.9575205 16.1089,-15.7148808 0,-4.934999 -2.4448,-9.863299 -4.726,-14.087399 l -9.125,-16.9838 c -0.5679,-1.0381 -1.2652,-1.4595 -2.2579,-1.4595 -1.0381,0 -1.7353,0.4214 -2.2578,1.414 l -9.1216,17.0293 c -2.2778,4.2241 -4.7226,9.1524 -4.7226,14.087399 0,8.7573603 7.1494,15.7148808 16.102,15.7148808 z m 0,-2.12551995 c -7.7802,0 -13.9765,-6.00096055 -13.9765,-13.58936085 0,-4.433999 2.2642,-8.903299 4.4966,-13.081999 l 8.9599,-16.7163 c 0.2407,-0.478 0.8062,-0.478 1.0469,0 l 8.9531,16.7163 c 2.2324,4.1787 4.458,8.648 4.458,13.081999 0,7.5884003 -6.1543,13.58936085 -13.938,13.58936085 z M 70.343736,-2.1090003 c 0,-0.4319999 -0.014,-0.8619999 -0.042,-1.2899999 -0.073,-1.102 -1.026,-1.937 -2.127,-1.865 -1.101,0.072 -1.937,1.025 -1.865,2.127 0.023,0.341 0.034,0.684 0.034,1.0279999 0,1.104 0.896,2.0000001 2,2.0000001 1.104,0 2,-0.8960001 2,-2.0000001 z M 65.759736,-15.126 c -0.488,-0.655 -1.013,-1.3 -1.573,-1.931 -0.732,-0.826 -1.997,-0.902 -2.823,-0.169 -0.826,0.732 -0.902,1.997 -0.169,2.823 0.483,0.545 0.936,1.1 1.357,1.666 0.659,0.885 1.913,1.069 2.798,0.41 0.886,-0.659 1.07,-1.913 0.41,-2.799 z m -11.082,-9.548 c -0.686,-0.402 -1.39,-0.792 -2.113,-1.171 -0.978,-0.512 -2.187,-0.134 -2.7,0.844 -0.512,0.978 -0.134,2.187 0.844,2.7 0.666,0.348 1.315,0.708 1.946,1.078 0.953,0.558 2.179,0.238 2.737,-0.714 0.558,-0.952 0.238,-2.179 -0.714,-2.737 z M 1.6577364,-25.845 c -0.72300001,0.379 -1.42800001,0.769 -2.11300001,1.171 -0.95299999,0.558 -1.27299999,1.785 -0.71399999,2.737 0.55799999,0.952 1.78399999,1.272 2.736,0.714 0.632,-0.37 1.281,-0.73 1.947,-1.078 0.978,-0.513 1.356,-1.722 0.843,-2.7 -0.512,-0.978 -1.722,-1.356 -2.699,-0.844 z m -11.622,8.788 c -0.5600004,0.631 -1.0850004,1.276 -1.5740004,1.931 -0.659,0.886 -0.475,2.14 0.41,2.799 0.886,0.659 2.1400004,0.476 2.7990004,-0.41 0.421,-0.566 0.874,-1.121 1.357,-1.666 0.733,-0.826 0.657,-2.091 -0.169,-2.823 -0.826,-0.733 -2.091,-0.657 -2.823,0.169 z m -6.1150004,13.6579998 c -0.028,0.428 -0.042,0.858 -0.042,1.2899999 0,1.104 0.896,2.0000001 2,2.0000001 1.104,0 2,-0.8960001 2,-2.0000001 0,-0.3439999 0.011,-0.6869999 0.033,-1.0279999 0.073,-1.102 -0.763,-2.055 -1.864,-2.127 -1.102,-0.072 -2.055,0.763 -2.127,1.865 z" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 24 KiB |
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"symbols" : [
|
||||
{
|
||||
"filename" : "soilTemp.variable.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,363 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!--Generator: Apple Native CoreSVG 341-->
|
||||
|
||||
<svg
|
||||
version="1.1"
|
||||
viewBox="0 0 3300 2200"
|
||||
id="svg681"
|
||||
sodipodi:docname="groundtemp.variable.svg"
|
||||
inkscape:version="1.2.1 (9c6d41e, 2022-07-14)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs685" />
|
||||
<sodipodi:namedview
|
||||
id="namedview683"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="false"
|
||||
inkscape:zoom="2.3396954"
|
||||
inkscape:cx="2915.5504"
|
||||
inkscape:cy="705.00629"
|
||||
inkscape:window-width="1390"
|
||||
inkscape:window-height="1205"
|
||||
inkscape:window-x="65"
|
||||
inkscape:window-y="150"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="Guides"
|
||||
showguides="false" />
|
||||
<g
|
||||
id="Notes">
|
||||
<rect
|
||||
height="2200"
|
||||
id="artboard"
|
||||
style="fill:white;opacity:1"
|
||||
width="3300"
|
||||
x="0"
|
||||
y="0" />
|
||||
<line
|
||||
style="fill:none;stroke:black;opacity:1;stroke-width:0.5;"
|
||||
x1="263"
|
||||
x2="3036"
|
||||
y1="292"
|
||||
y2="292"
|
||||
id="line562" />
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;font-weight:bold;"
|
||||
transform="matrix(1 0 0 1 263 322)"
|
||||
id="text564">Weight/Scale Variations</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;"
|
||||
transform="matrix(1 0 0 1 559.711 322)"
|
||||
id="text566">Ultralight</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;"
|
||||
transform="matrix(1 0 0 1 856.422 322)"
|
||||
id="text568">Thin</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;"
|
||||
transform="matrix(1 0 0 1 1153.13 322)"
|
||||
id="text570">Light</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;"
|
||||
transform="matrix(1 0 0 1 1449.84 322)"
|
||||
id="text572">Regular</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;"
|
||||
transform="matrix(1 0 0 1 1746.56 322)"
|
||||
id="text574">Medium</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;"
|
||||
transform="matrix(1 0 0 1 2043.27 322)"
|
||||
id="text576">Semibold</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;"
|
||||
transform="matrix(1 0 0 1 2339.98 322)"
|
||||
id="text578">Bold</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;"
|
||||
transform="matrix(1 0 0 1 2636.69 322)"
|
||||
id="text580">Heavy</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;"
|
||||
transform="matrix(1 0 0 1 2933.4 322)"
|
||||
id="text582">Black</text>
|
||||
<line
|
||||
style="fill:none;stroke:black;opacity:1;stroke-width:0.5;"
|
||||
x1="263"
|
||||
x2="3036"
|
||||
y1="1903"
|
||||
y2="1903"
|
||||
id="line584" />
|
||||
<g
|
||||
transform="matrix(0.2 0 0 0.2 263 1933)"
|
||||
id="g588">
|
||||
<path
|
||||
d="m46.2402 4.15039c21.7773 0 39.4531-17.627 39.4531-39.4043s-17.6758-39.4043-39.4531-39.4043c-21.7285 0-39.4043 17.627-39.4043 39.4043s17.6758 39.4043 39.4043 39.4043Zm0-7.42188c-17.6758 0-31.9336-14.3066-31.9336-31.9824s14.2578-31.9824 31.9336-31.9824 31.9824 14.3066 31.9824 31.9824-14.3066 31.9824-31.9824 31.9824Zm-17.9688-31.9824c0 2.14844 1.51367 3.61328 3.75977 3.61328h10.498v10.5957c0 2.19727 1.46484 3.71094 3.61328 3.71094 2.24609 0 3.71094-1.51367 3.71094-3.71094v-10.5957h10.5957c2.19727 0 3.71094-1.46484 3.71094-3.61328 0-2.19727-1.51367-3.71094-3.71094-3.71094h-10.5957v-10.5469c0-2.24609-1.46484-3.75977-3.71094-3.75977-2.14844 0-3.61328 1.51367-3.61328 3.75977v10.5469h-10.498c-2.24609 0-3.75977 1.51367-3.75977 3.71094Z"
|
||||
id="path586" />
|
||||
</g>
|
||||
<g
|
||||
transform="matrix(0.2 0 0 0.2 281.506 1933)"
|
||||
id="g592">
|
||||
<path
|
||||
d="m58.5449 14.5508c27.4902 0 49.8047-22.3145 49.8047-49.8047s-22.3145-49.8047-49.8047-49.8047-49.8047 22.3145-49.8047 49.8047 22.3145 49.8047 49.8047 49.8047Zm0-8.30078c-22.9492 0-41.5039-18.5547-41.5039-41.5039s18.5547-41.5039 41.5039-41.5039 41.5039 18.5547 41.5039 41.5039-18.5547 41.5039-41.5039 41.5039Zm-22.6562-41.5039c0 2.39258 1.66016 4.00391 4.15039 4.00391h14.3555v14.4043c0 2.44141 1.66016 4.15039 4.05273 4.15039 2.44141 0 4.15039-1.66016 4.15039-4.15039v-14.4043h14.4043c2.44141 0 4.15039-1.61133 4.15039-4.00391 0-2.44141-1.70898-4.15039-4.15039-4.15039h-14.4043v-14.3555c0-2.49023-1.70898-4.19922-4.15039-4.19922-2.39258 0-4.05273 1.70898-4.05273 4.19922v14.3555h-14.3555c-2.49023 0-4.15039 1.70898-4.15039 4.15039Z"
|
||||
id="path590" />
|
||||
</g>
|
||||
<g
|
||||
transform="matrix(0.2 0 0 0.2 304.924 1933)"
|
||||
id="g596">
|
||||
<path
|
||||
d="m74.8535 28.3203c35.1074 0 63.623-28.4668 63.623-63.5742s-28.5156-63.623-63.623-63.623-63.5742 28.5156-63.5742 63.623 28.4668 63.5742 63.5742 63.5742Zm0-9.08203c-30.127 0-54.4922-24.3652-54.4922-54.4922s24.3652-54.4922 54.4922-54.4922 54.4922 24.3652 54.4922 54.4922-24.3652 54.4922-54.4922 54.4922Zm-28.8574-54.4922c0 2.58789 1.85547 4.39453 4.58984 4.39453h19.7266v19.7754c0 2.68555 1.85547 4.58984 4.44336 4.58984 2.68555 0 4.54102-1.85547 4.54102-4.58984v-19.7754h19.7754c2.68555 0 4.58984-1.80664 4.58984-4.39453 0-2.73438-1.85547-4.58984-4.58984-4.58984h-19.7754v-19.7266c0-2.73438-1.85547-4.63867-4.54102-4.63867-2.58789 0-4.44336 1.9043-4.44336 4.63867v19.7266h-19.7266c-2.73438 0-4.58984 1.85547-4.58984 4.58984Z"
|
||||
id="path594" />
|
||||
</g>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;font-weight:bold;"
|
||||
transform="matrix(1 0 0 1 263 1953)"
|
||||
id="text598">Design Variations</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;"
|
||||
transform="matrix(1 0 0 1 263 1971)"
|
||||
id="text600">Symbols are supported in up to nine weights and three scales.</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;"
|
||||
transform="matrix(1 0 0 1 263 1989)"
|
||||
id="text602">For optimal layout with text and other symbols, vertically align</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;"
|
||||
transform="matrix(1 0 0 1 263 2007)"
|
||||
id="text604">symbols with the adjacent text.</text>
|
||||
<line
|
||||
style="fill:none;stroke:#00AEEF;stroke-width:0.5;opacity:1.0;"
|
||||
x1="776"
|
||||
x2="776"
|
||||
y1="1919"
|
||||
y2="1933"
|
||||
id="line606" />
|
||||
<g
|
||||
transform="matrix(0.2 0 0 0.2 776 1933)"
|
||||
id="g610">
|
||||
<path
|
||||
d="m16.5527 0.78125c2.58789 0 3.85742-0.976562 4.78516-3.71094l6.29883-17.2363h28.8086l6.29883 17.2363c0.927734 2.73438 2.19727 3.71094 4.73633 3.71094 2.58789 0 4.24805-1.5625 4.24805-4.00391 0-0.830078-0.146484-1.61133-0.537109-2.63672l-22.9004-60.9863c-1.12305-2.97852-3.125-4.49219-6.25-4.49219-3.02734 0-5.07812 1.46484-6.15234 4.44336l-22.9004 61.084c-0.390625 1.02539-0.537109 1.80664-0.537109 2.63672 0 2.44141 1.5625 3.95508 4.10156 3.95508Zm13.4766-28.3691 11.8652-32.8613h0.244141l11.8652 32.8613Z"
|
||||
id="path608" />
|
||||
</g>
|
||||
<line
|
||||
style="fill:none;stroke:#00AEEF;stroke-width:0.5;opacity:1.0;"
|
||||
x1="792.836"
|
||||
x2="792.836"
|
||||
y1="1919"
|
||||
y2="1933"
|
||||
id="line612" />
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;font-weight:bold;"
|
||||
transform="matrix(1 0 0 1 776 1953)"
|
||||
id="text614">Margins</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;"
|
||||
transform="matrix(1 0 0 1 776 1971)"
|
||||
id="text616">Leading and trailing margins on the left and right side of each symbol</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;"
|
||||
transform="matrix(1 0 0 1 776 1989)"
|
||||
id="text618">can be adjusted by modifying the x-location of the margin guidelines.</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;"
|
||||
transform="matrix(1 0 0 1 776 2007)"
|
||||
id="text620">Modifications are automatically applied proportionally to all</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;"
|
||||
transform="matrix(1 0 0 1 776 2025)"
|
||||
id="text622">scales and weights.</text>
|
||||
<g
|
||||
transform="matrix(0.2 0 0 0.2 1289 1933)"
|
||||
id="g626">
|
||||
<path
|
||||
d="m14.209 9.32617 8.49609 8.54492c4.29688 4.3457 9.22852 4.05273 13.8672-1.07422l53.4668-58.9355-4.83398-4.88281-53.0762 58.3984c-1.75781 2.00195-3.41797 2.49023-5.76172 0.146484l-5.85938-5.81055c-2.34375-2.29492-1.80664-4.00391 0.195312-5.81055l57.373-54.0039-4.88281-4.83398-57.959 54.4434c-4.93164 4.58984-5.32227 9.47266-1.02539 13.8184Zm32.0801-90.9668c-2.09961 2.05078-2.24609 4.93164-1.07422 6.88477 1.17188 1.80664 3.4668 2.97852 6.68945 2.14844 7.32422-1.70898 14.9414-2.00195 22.0703 2.68555l-2.92969 7.27539c-1.70898 4.15039-0.830078 7.08008 1.85547 9.81445l11.4746 11.5723c2.44141 2.44141 4.49219 2.53906 7.32422 2.05078l5.32227-0.976562 3.32031 3.36914-0.195312 2.7832c-0.195312 2.49023 0.439453 4.39453 2.88086 6.78711l3.80859 3.71094c2.39258 2.39258 5.46875 2.53906 7.8125 0.195312l14.5508-14.5996c2.34375-2.34375 2.24609-5.32227-0.146484-7.71484l-3.85742-3.80859c-2.39258-2.39258-4.24805-3.17383-6.64062-2.97852l-2.88086 0.244141-3.22266-3.17383 1.2207-5.61523c0.634766-2.83203-0.146484-5.0293-3.07617-7.95898l-10.9863-10.9375c-16.6992-16.6016-38.8672-16.2109-53.3203-1.75781Zm7.4707 1.85547c12.1582-8.88672 28.6133-7.37305 39.7461 3.75977l12.1582 12.0605c1.17188 1.17188 1.36719 2.09961 1.02539 3.80859l-1.61133 7.42188 7.51953 7.42188 4.93164-0.292969c1.26953-0.0488281 1.66016 0.0488281 2.63672 1.02539l2.88086 2.88086-12.207 12.207-2.88086-2.88086c-0.976562-0.976562-1.12305-1.36719-1.07422-2.68555l0.341797-4.88281-7.4707-7.42188-7.61719 1.26953c-1.61133 0.341797-2.34375 0.195312-3.56445-0.976562l-10.0098-10.0098c-1.26953-1.17188-1.41602-2.00195-0.634766-3.85742l4.39453-10.4492c-7.8125-7.27539-17.9688-10.4004-28.125-7.42188-0.78125 0.195312-1.07422-0.439453-0.439453-0.976562Z"
|
||||
id="path624" />
|
||||
</g>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;font-weight:bold;"
|
||||
transform="matrix(1 0 0 1 1289 1953)"
|
||||
id="text628">Exporting</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;"
|
||||
transform="matrix(1 0 0 1 1289 1971)"
|
||||
id="text630">Symbols should be outlined when exporting to ensure the</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;"
|
||||
transform="matrix(1 0 0 1 1289 1989)"
|
||||
id="text632">design is preserved when submitting to Xcode.</text>
|
||||
<text
|
||||
id="template-version"
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:end;"
|
||||
transform="matrix(1 0 0 1 3036 1933)">Template v.6.0</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:end;"
|
||||
transform="matrix(1 0 0 1 3036 1951)"
|
||||
id="text635">Requires Xcode 16 or greater</text>
|
||||
<text
|
||||
id="descriptive-name"
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:end;"
|
||||
transform="matrix(1 0 0 1 3036 1969)">Generated from thermometer.variable</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:end;"
|
||||
transform="matrix(1 0 0 1 3036 1987)"
|
||||
id="text638">Typeset at 100.0 points</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;"
|
||||
transform="matrix(1 0 0 1 263 726)"
|
||||
id="text640">Small</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;"
|
||||
transform="matrix(1 0 0 1 263 1156)"
|
||||
id="text642">Medium</text>
|
||||
<text
|
||||
style="stroke:none;fill:black;font-family:sans-serif;font-size:13;"
|
||||
transform="matrix(1 0 0 1 263 1586)"
|
||||
id="text644">Large</text>
|
||||
</g>
|
||||
<g
|
||||
id="Guides">
|
||||
<g
|
||||
id="H-reference"
|
||||
style="fill:#27AAE1;stroke:none;"
|
||||
transform="matrix(1 0 0 1 339 696)">
|
||||
<path
|
||||
d="M0.993654 0L3.63775 0L29.3281-67.1323L30.0303-67.1323L30.0303-70.459L28.1226-70.459ZM11.6885-24.4799L46.9815-24.4799L46.2315-26.7285L12.4385-26.7285ZM55.1196 0L57.7637 0L30.6382-70.459L29.4326-70.459L29.4326-67.1323Z"
|
||||
id="path647" />
|
||||
</g>
|
||||
<line
|
||||
id="Baseline-S"
|
||||
style="fill:none;stroke:#27AAE1;opacity:1;stroke-width:0.5;"
|
||||
x1="263"
|
||||
x2="3036"
|
||||
y1="696"
|
||||
y2="696" />
|
||||
<line
|
||||
id="Capline-S"
|
||||
style="fill:none;stroke:#27AAE1;opacity:1;stroke-width:0.5;"
|
||||
x1="263"
|
||||
x2="3036"
|
||||
y1="625.541"
|
||||
y2="625.541" />
|
||||
<g
|
||||
id="g654"
|
||||
style="fill:#27AAE1;stroke:none;"
|
||||
transform="matrix(1 0 0 1 339 1126)">
|
||||
<path
|
||||
d="M0.993654 0L3.63775 0L29.3281-67.1323L30.0303-67.1323L30.0303-70.459L28.1226-70.459ZM11.6885-24.4799L46.9815-24.4799L46.2315-26.7285L12.4385-26.7285ZM55.1196 0L57.7637 0L30.6382-70.459L29.4326-70.459L29.4326-67.1323Z"
|
||||
id="path652" />
|
||||
</g>
|
||||
<line
|
||||
id="Baseline-M"
|
||||
style="fill:none;stroke:#27AAE1;opacity:1;stroke-width:0.5;"
|
||||
x1="263"
|
||||
x2="3036"
|
||||
y1="1126"
|
||||
y2="1126" />
|
||||
<line
|
||||
id="Capline-M"
|
||||
style="fill:none;stroke:#27AAE1;opacity:1;stroke-width:0.5;"
|
||||
x1="263"
|
||||
x2="3036"
|
||||
y1="1055.54"
|
||||
y2="1055.54" />
|
||||
<g
|
||||
id="g660"
|
||||
style="fill:#27AAE1;stroke:none;"
|
||||
transform="matrix(1 0 0 1 339 1556)">
|
||||
<path
|
||||
d="M0.993654 0L3.63775 0L29.3281-67.1323L30.0303-67.1323L30.0303-70.459L28.1226-70.459ZM11.6885-24.4799L46.9815-24.4799L46.2315-26.7285L12.4385-26.7285ZM55.1196 0L57.7637 0L30.6382-70.459L29.4326-70.459L29.4326-67.1323Z"
|
||||
id="path658" />
|
||||
</g>
|
||||
<line
|
||||
id="Baseline-L"
|
||||
style="fill:none;stroke:#27AAE1;opacity:1;stroke-width:0.5;"
|
||||
x1="263"
|
||||
x2="3036"
|
||||
y1="1556"
|
||||
y2="1556" />
|
||||
<line
|
||||
id="Capline-L"
|
||||
style="fill:none;stroke:#27AAE1;opacity:1;stroke-width:0.5;"
|
||||
x1="263"
|
||||
x2="3036"
|
||||
y1="1485.54"
|
||||
y2="1485.54" />
|
||||
<line
|
||||
id="right-margin-Black-S"
|
||||
style="opacity:1;fill:none;stroke:#00aeef;stroke-width:0.5"
|
||||
x1="2994.5601"
|
||||
x2="2994.5601"
|
||||
y1="600.78497"
|
||||
y2="720.12097" />
|
||||
<line
|
||||
id="left-margin-Black-S"
|
||||
style="opacity:1;fill:none;stroke:#00aeef;stroke-width:0.5"
|
||||
x1="2882.24"
|
||||
x2="2882.24"
|
||||
y1="600.78497"
|
||||
y2="720.12097" />
|
||||
<line
|
||||
id="right-margin-Regular-S"
|
||||
style="opacity:1;fill:none;stroke:#00aeef;stroke-width:0.5"
|
||||
x1="1492"
|
||||
x2="1492"
|
||||
y1="600.78497"
|
||||
y2="720.12097" />
|
||||
<line
|
||||
id="left-margin-Regular-S"
|
||||
style="opacity:1;fill:none;stroke:#00aeef;stroke-width:0.5"
|
||||
x1="1395.6899"
|
||||
x2="1395.6899"
|
||||
y1="600.78497"
|
||||
y2="720.12097" />
|
||||
<line
|
||||
id="right-margin-Ultralight-S"
|
||||
style="opacity:1;fill:none;stroke:#00aeef;stroke-width:0.5"
|
||||
x1="605.70599"
|
||||
x2="605.70599"
|
||||
y1="600.78497"
|
||||
y2="720.12097" />
|
||||
<line
|
||||
id="left-margin-Ultralight-S"
|
||||
style="opacity:1;fill:none;stroke:#00aeef;stroke-width:0.5"
|
||||
x1="513.71698"
|
||||
x2="513.71698"
|
||||
y1="600.78497"
|
||||
y2="720.12097" />
|
||||
</g>
|
||||
<g
|
||||
id="Symbols">
|
||||
<g
|
||||
id="Black-S"
|
||||
transform="matrix(1 0 0 1 2898.24 696)">
|
||||
<path
|
||||
d="m 93.819567,-0.549 c 0,2.346 -1.91,4.25 -4.25,4.25 -2.35,0 -4.25,-1.904 -4.25,-4.25 0,-0.295 -0.01,-0.589 -0.03,-0.881 -0.15,-2.341 1.62,-4.366 3.96,-4.519 2.34,-0.154 4.37,1.621 4.52,3.962 0.03,0.477 0.05,0.956 0.05,1.438 z m -5.03,-14.361 c 1.4,1.882 1.01,4.547 -0.87,5.947 -1.88,1.401 -4.55,1.011 -5.95,-0.871 -0.38,-0.515 -0.79,-1.02 -1.23,-1.516 -1.56,-1.755 -1.4,-4.443 0.36,-6 1.75,-1.556 4.44,-1.395 6,0.36 0.6,0.68 1.16,1.374 1.69,2.08 z m -11.75,-10.145 c 2.02,1.186 2.7,3.792 1.52,5.815 -1.19,2.024 -3.79,2.704 -5.82,1.518 -0.6,-0.352 -1.22,-0.695 -1.85,-1.027 -2.08,-1.088 -2.88,-3.659 -1.79,-5.737 1.09,-2.077 3.66,-2.881 5.73,-1.792 0.76,0.395 1.5,0.803 2.21,1.223 z M 5.8395672,-26.278 c 2.08,-1.089 4.6499998,-0.285 5.7399998,1.792 1.08,2.078 0.28,4.649 -1.7999998,5.737 -0.63,0.332 -1.25,0.675 -1.85,1.027 -2.02,1.186 -4.63,0.506 -5.82,-1.518 -1.17999998,-2.023 -0.5,-4.629 1.52,-5.815 0.72,-0.42 1.45,-0.828 2.21,-1.223 z m -12.26,9.288 c 1.55,-1.755 4.24,-1.916 6.00000002,-0.36 1.74999998,1.557 1.90999998,4.245 0.36,6 -0.44,0.496 -0.86,1.001 -1.24000002,1.516 -1.4,1.882 -4.06,2.272 -5.95,0.871 -1.88,-1.4 -2.27,-4.065 -0.87,-5.946 0.53,-0.707 1.09,-1.401 1.7,-2.081 z m -6.6800002,15.003 c 0.15,-2.341 2.18,-4.116 4.5200002,-3.962 2.34,0.153 4.12,2.178 3.96,4.519 -0.02,0.292 -0.03,0.586 -0.03,0.881 0,2.346 -1.9,4.25 -4.25,4.25 -2.3400002,0 -4.2500002,-1.904 -4.2500002,-4.25 0,-0.482 0.02,-0.961 0.05,-1.438 z m 53.49,13.315 c 13.96,0 25.34,-10.937 25.34,-24.951 0,-6.592 -2.44,-12.256 -6.99,-16.797 -0.73,-0.732 -0.83,-1.074 -0.83,-2.051 v -29.638 c 0,-11.182 -7.12,-18.702 -17.52,-18.702 -10.55,0 -17.63,7.52 -17.63,18.702 v 29.638 c 0,0.977 -0.1,1.367 -0.83,2.051 -4.69,4.346 -6.98,10.205 -6.98,16.797 0,14.014 11.37,24.951 25.44,24.951 z m 0,-11.474 c -7.72,0 -13.92,-6.25 -13.92,-13.965 0,-5.176 2.34,-8.985 6.3,-11.573 1.12,-0.732 1.56,-1.318 1.56,-2.832 v -33.3 c 0,-4.493 2.44,-7.471 6.06,-7.471 3.51,0 5.95,2.978 5.95,7.471 v 33.3 c 0,1.514 0.44,2.1 1.57,2.832 3.95,2.588 6.29,6.397 6.29,11.573 0,7.715 -6.2,13.965 -13.81,13.965 z m -0.05,-5.176 c 4.83,0 8.74,-3.907 8.74,-8.789 0,-3.369 -1.91,-6.153 -4.69,-7.666 -1.17,-0.635 -1.56,-1.075 -1.56,-2.832 v -10.254 c 0,-1.367 -1.13,-2.49 -2.49,-2.49 -1.37,0 -2.49,1.123 -2.49,2.49 v 10.254 c 0,1.757 -0.4,2.197 -1.57,2.832 -2.78,1.513 -4.68,4.297 -4.68,7.666 0,4.882 3.9,8.789 8.74,8.789 z m 0,-36.035 c 1.36,0 2.49,-1.123 2.49,-2.491 0,-1.367 -1.13,-2.49 -2.49,-2.49 -1.37,0 -2.49,1.123 -2.49,2.49 0,1.368 1.12,2.491 2.49,2.491 z m 0,-8.985 c 1.36,0 2.49,-1.123 2.49,-2.49 0,-1.367 -1.13,-2.49 -2.49,-2.49 -1.37,0 -2.49,1.123 -2.49,2.49 0,1.367 1.12,2.49 2.49,2.49 z m 0,-8.984 c 1.36,0 2.49,-1.123 2.49,-2.49 0,-1.368 -1.13,-2.491 -2.49,-2.491 -1.37,0 -2.49,1.123 -2.49,2.491 0,1.367 1.12,2.49 2.49,2.49 z"
|
||||
style="clip-rule:evenodd;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.5px;stroke-linejoin:round;stroke-miterlimit:2"
|
||||
id="path4426" />
|
||||
</g>
|
||||
<g
|
||||
id="Regular-S"
|
||||
transform="matrix(1 0 0 1 1419.69 696)">
|
||||
<path
|
||||
d="m 70.441741,-2.1089996 c 0,1.518 -1.23,2.75 -2.75,2.75 -1.51,0 -2.75,-1.232 -2.75,-2.75 0,-0.328 -0.01,-0.654 -0.03,-0.979 -0.1,-1.515 1.05,-2.825 2.57,-2.924 1.51,-0.1 2.82,1.049 2.92,2.564 0.03,0.444 0.04,0.891 0.04,1.339 z m -4.73,-13.4640004 c 0.91,1.217 0.66,2.941 -0.56,3.848 -1.22,0.906 -2.94,0.653 -3.85,-0.564 -0.41,-0.549 -0.85,-1.088 -1.32,-1.616 -1,-1.136 -0.9,-2.875 0.24,-3.882 1.13,-1.007 2.87,-0.903 3.88,0.232 0.57,0.648 1.11,1.309 1.61,1.982 z m -11.3,-9.748 c 1.31,0.768 1.75,2.454 0.98,3.763 -0.77,1.31 -2.45,1.75 -3.76,0.982 -0.63,-0.364 -1.26,-0.718 -1.92,-1.061 -1.34,-0.704 -1.86,-2.368 -1.16,-3.712 0.7,-1.345 2.37,-1.864 3.71,-1.16 0.74,0.384 1.45,0.78 2.15,1.188 z m -58.7500004,-1.188 c 1.34,-0.704 3.01,-0.185 3.71,1.16 0.71,1.344 0.19,3.008 -1.16,3.712 -0.65,0.343 -1.29,0.697 -1.91,1.061 -1.31,0.768 -3,0.328 -3.77,-0.982 -0.76,-1.309 -0.32,-2.995 0.98,-3.763 0.7,-0.408 1.42,-0.804 2.15,-1.188 z m -11.8399996,8.954 c 1.01,-1.135 2.75,-1.239 3.89,-0.232 1.13,1.007 1.24,2.746 0.23,3.882 -0.47,0.528 -0.91,1.067 -1.32,1.616 -0.9,1.217 -2.63,1.47 -3.85,0.564 -1.21,-0.907 -1.47,-2.631 -0.56,-3.848 0.5,-0.673 1.04,-1.334 1.61,-1.982 z m -6.3,14.1070004 c 0.1,-1.515 1.41,-2.664 2.93,-2.564 1.51,0.099 2.66,1.409 2.56,2.924 -0.02,0.325 -0.03,0.651 -0.03,0.979 0,1.518 -1.23,2.75 -2.75,2.75 -1.52,0 -2.75,-1.232 -2.75,-2.75 0,-0.448 0.01,-0.895 0.04,-1.339 z m 46.47,10.772 c 11.23,0 20.36,-9.131 20.36,-20.3610004 0,-5.908 -2.44,-11.084 -6.98,-15.283 -0.79,-0.733 -0.93,-1.123 -0.93,-2.149 v -33.789 c 0,-8.154 -5.03,-13.623 -12.45,-13.623 -7.48,0 -12.55,5.469 -12.55,13.623 v 33.789 c 0,1.026 -0.15,1.416 -0.88,2.149 -4.5400004,4.199 -6.9800004,9.375 -6.9800004,15.283 0,11.2300004 9.1300004,20.3610004 20.4100004,20.3610004 z m 0,-6.347 c -7.77,0 -14.0200004,-6.299 -14.0200004,-14.0140004 0,-4.736 2.3000004,-9.033 6.3000004,-11.67 1.12,-0.732 1.56,-1.367 1.56,-2.881 v -36.377 c 0,-4.541 2.49,-7.519 6.16,-7.519 3.61,0 6.05,2.978 6.05,7.519 v 36.377 c 0,1.514 0.49,2.149 1.61,2.881 3.96,2.637 6.3,6.934 6.3,11.67 0,7.7150004 -6.25,14.0140004 -13.96,14.0140004 z m -0.05,-5.176 c 4.93,0 8.88,-3.955 8.88,-8.8870004 0,-3.418 -1.95,-6.25 -4.73,-7.764 -1.22,-0.683 -1.61,-1.123 -1.61,-2.88 v -13.819 c 0,-1.416 -1.13,-2.539 -2.54,-2.539 -1.37,0 -2.49,1.123 -2.49,2.539 v 13.819 c 0,1.757 -0.39,2.197 -1.62,2.88 -2.83,1.514 -4.73,4.346 -4.73,7.764 0,4.9320004 3.95,8.8870004 8.84,8.8870004 z m 0,-39.6000004 c 1.41,0 2.54,-1.172 2.54,-2.539 0,-1.416 -1.13,-2.539 -2.54,-2.539 -1.37,0 -2.49,1.123 -2.49,2.539 0,1.367 1.12,2.539 2.49,2.539 z m 0,-8.838 c 1.41,0 2.54,-1.172 2.54,-2.539 0,-1.416 -1.13,-2.539 -2.54,-2.539 -1.37,0 -2.49,1.123 -2.49,2.539 0,1.367 1.12,2.539 2.49,2.539 z m 0,-8.789 c 1.41,0 2.54,-1.172 2.54,-2.539 0,-1.416 -1.13,-2.539 -2.54,-2.539 -1.37,0 -2.49,1.123 -2.49,2.539 0,1.367 1.12,2.539 2.49,2.539 z"
|
||||
style="clip-rule:evenodd;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.5px;stroke-linejoin:round;stroke-miterlimit:2"
|
||||
id="path4429" />
|
||||
</g>
|
||||
<g
|
||||
id="Ultralight-S"
|
||||
transform="matrix(1 0 0 1 531.717 696)">
|
||||
<path
|
||||
d="m 70.343736,-2.1090003 c 0,-0.4319999 -0.014,-0.8619999 -0.042,-1.2899999 -0.073,-1.102 -1.026,-1.937 -2.127,-1.865 -1.101,0.072 -1.937,1.025 -1.865,2.127 0.023,0.341 0.034,0.684 0.034,1.0279999 0,1.104 0.896,2.0000001 2,2.0000001 1.104,0 2,-0.8960001 2,-2.0000001 z M 65.759736,-15.126 c -0.488,-0.655 -1.013,-1.3 -1.573,-1.931 -0.732,-0.826 -1.997,-0.902 -2.823,-0.169 -0.826,0.732 -0.902,1.997 -0.169,2.823 0.483,0.545 0.936,1.1 1.357,1.666 0.659,0.885 1.913,1.069 2.798,0.41 0.886,-0.659 1.07,-1.913 0.41,-2.799 z m -11.082,-9.548 c -0.686,-0.402 -1.39,-0.792 -2.113,-1.171 -0.978,-0.512 -2.187,-0.134 -2.7,0.844 -0.512,0.978 -0.134,2.187 0.844,2.7 0.666,0.348 1.315,0.708 1.946,1.078 0.953,0.558 2.179,0.238 2.737,-0.714 0.558,-0.952 0.238,-2.179 -0.714,-2.737 z M 1.6577364,-25.845 c -0.72300001,0.379 -1.42800001,0.769 -2.11300001,1.171 -0.95299999,0.558 -1.27299999,1.785 -0.71399999,2.737 0.55799999,0.952 1.78399999,1.272 2.736,0.714 0.632,-0.37 1.281,-0.73 1.947,-1.078 0.978,-0.513 1.356,-1.722 0.843,-2.7 -0.512,-0.978 -1.722,-1.356 -2.699,-0.844 z m -11.622,8.788 c -0.5600004,0.631 -1.0850004,1.276 -1.5740004,1.931 -0.659,0.886 -0.475,2.14 0.41,2.799 0.886,0.659 2.1400004,0.476 2.7990004,-0.41 0.421,-0.566 0.874,-1.121 1.357,-1.666 0.733,-0.826 0.657,-2.091 -0.169,-2.823 -0.826,-0.733 -2.091,-0.657 -2.823,0.169 z m -6.1150004,13.6579998 c -0.028,0.428 -0.042,0.858 -0.042,1.2899999 0,1.104 0.896,2.0000001 2,2.0000001 1.104,0 2,-0.8960001 2,-2.0000001 0,-0.3439999 0.011,-0.6869999 0.033,-1.0279999 0.073,-1.102 -0.763,-2.055 -1.864,-2.127 -1.102,-0.072 -2.055,0.763 -2.127,1.865 z m 43.192,10.087 c 10.095,0 18.227,-8.1770001 18.227,-18.1809998 0,-5.409 -2.214,-10.131 -6.891,-13.876 -1.145,-0.914 -1.382,-1.577 -1.382,-3.192 v -38.103 c 0,-6.202 -4.121,-10.581 -9.954,-10.581 -5.836,0 -9.96,4.379 -9.96,10.581 v 38.103 c 0,1.615 -0.238,2.278 -1.379,3.192 -4.677,3.745 -6.8909996,8.467 -6.8909996,13.876 0,10.0039997 8.1319996,18.1809998 18.2299996,18.1809998 z m 0,-2.033 c -8.899,0 -16.148,-7.298 -16.148,-16.1479998 0,-4.873 2.023,-9.306 6.39,-12.487 1.395,-1.005 1.926,-2.23 1.926,-3.971 v -38.693 c 0,-4.95 3.262,-8.473 7.832,-8.473 4.567,0 7.826,3.523 7.826,8.473 v 38.693 c 0,1.741 0.534,2.966 1.929,3.971 4.364,3.181 6.39,7.614 6.39,12.487 0,8.8499998 -7.249,16.1479998 -16.145,16.1479998 z m -0.003,-5.13100005 c 6.112,0 10.975,-4.90799995 10.975,-11.02099975 0,-4.28 -2.362,-7.793 -5.917,-9.67 -1.447,-0.775 -1.929,-1.351 -1.929,-3.517 v -10.503 c 0,-1.734 -1.395,-3.13 -3.129,-3.13 -1.731,0 -3.126,1.396 -3.126,3.13 v 10.503 c 0,2.166 -0.482,2.742 -1.929,3.517 -3.559,1.877 -5.917,5.39 -5.917,9.67 0,6.1129998 4.863,11.02099975 10.972,11.02099975 z m 0,-42.05099975 c 1.734,0 3.129,-1.445 3.129,-3.175 0,-1.734 -1.395,-3.129 -3.129,-3.129 -1.731,0 -3.126,1.395 -3.126,3.129 0,1.73 1.395,3.175 3.126,3.175 z m 0,-10.473 c 1.734,0 3.129,-1.399 3.129,-3.129 0,-1.78 -1.395,-3.175 -3.129,-3.175 -1.731,0 -3.126,1.395 -3.126,3.175 0,1.73 1.395,3.129 3.126,3.129 z m 0,-10.515 c 1.734,0 3.129,-1.399 3.129,-3.129 0,-1.734 -1.395,-3.175 -3.129,-3.175 -1.731,0 -3.126,1.441 -3.126,3.175 0,1.73 1.395,3.129 3.126,3.129 z"
|
||||
style="clip-rule:evenodd;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.5px;stroke-linejoin:round;stroke-miterlimit:2"
|
||||
id="path4432" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 25 KiB |
|
|
@ -29,6 +29,8 @@ public struct ManagedAttribute<Value: Numeric> {
|
|||
converter = { $0.int32Value as? Value }
|
||||
} else if Value.self == Int64.self {
|
||||
converter = { $0.int64Value as? Value }
|
||||
} else if Value.self == UInt32.self {
|
||||
converter = { $0.uint32Value as? Value }
|
||||
} else {
|
||||
fatalError("Unsupported type: \(Value.self)")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,17 +10,17 @@ import Foundation
|
|||
extension Date {
|
||||
|
||||
var lastHeard: String {
|
||||
if timeIntervalSince1970 > 0 {
|
||||
if self.timeIntervalSince1970 > 0 && self < Calendar.current.date(byAdding: .year, value: 1, to: Date())! {
|
||||
formatted()
|
||||
} else {
|
||||
"unknown"
|
||||
"unknown.age".localized
|
||||
}
|
||||
}
|
||||
|
||||
func formattedDate(format: String) -> String {
|
||||
let dateformat = DateFormatter()
|
||||
dateformat.dateFormat = format
|
||||
if self > Calendar.current.date(byAdding: .year, value: -5, to: Date())! {
|
||||
if self.timeIntervalSince1970 > 0 && self < Calendar.current.date(byAdding: .year, value: 1, to: Date())! {
|
||||
return dateformat.string(from: self)
|
||||
} else {
|
||||
return "unknown.age".localized
|
||||
|
|
|
|||
|
|
@ -93,12 +93,51 @@ extension String {
|
|||
|
||||
// Filter out variation selectors from the string
|
||||
var withoutVariationSelectors: String {
|
||||
return self.unicodeScalars
|
||||
.filter { scalar in
|
||||
return !scalar.properties.isVariationSelector
|
||||
var scalars: [UnicodeScalar] = []
|
||||
var previousWasASCII = false
|
||||
|
||||
for scalar in self.unicodeScalars {
|
||||
if scalar.properties.isVariationSelector {
|
||||
// Only keep variation selector if the previous character was ASCII
|
||||
if previousWasASCII {
|
||||
scalars.append(scalar)
|
||||
}
|
||||
// No need to update previousWasASCII since variation selectors aren't characters
|
||||
// Shouldn't have 2 in a row
|
||||
} else {
|
||||
scalars.append(scalar)
|
||||
previousWasASCII = scalar.isASCII
|
||||
}
|
||||
.compactMap { UnicodeScalar($0) }
|
||||
}
|
||||
|
||||
return scalars.compactMap { UnicodeScalar($0) }
|
||||
.map { String($0) }
|
||||
.joined()
|
||||
}
|
||||
|
||||
// Adds variation selectors to prefer the graphical form of emoji.
|
||||
// Looks ahead to make sure that the variation selector is not already applied.
|
||||
var addingVariationSelectors: String {
|
||||
var result = ""
|
||||
let scalars = self.unicodeScalars
|
||||
var index = scalars.startIndex
|
||||
while index < scalars.endIndex {
|
||||
let currentScalar = scalars[index]
|
||||
result += String(currentScalar)
|
||||
if currentScalar.properties.isEmoji && !currentScalar.properties.isEmojiPresentation && !currentScalar.isASCII {
|
||||
// Check if the next scalar is U+FE0F
|
||||
let nextIndex = scalars.index(after: index)
|
||||
if nextIndex < scalars.endIndex && scalars[nextIndex].value == 0xFE0F {
|
||||
// Already has variation selector; skip the next scalar
|
||||
index = nextIndex
|
||||
} else {
|
||||
// Append variation selector
|
||||
result += String(UnicodeScalar(0xFE0F)!)
|
||||
}
|
||||
}
|
||||
// Move to the next scalar
|
||||
index = scalars.index(after: index)
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
self.isConnected = false
|
||||
self.isConnecting = false
|
||||
self.lastConnectionError = "🚨 " + String.localizedStringWithFormat("Connection failed after %d attempts to connect to %@. You may need to forget your device under Settings > Bluetooth.".localized, timeoutTimerCount, name)
|
||||
Logger.services.error("\(self.lastConnectionError)")
|
||||
Logger.services.error("\(self.lastConnectionError, privacy: .public)")
|
||||
self.timeoutTimerCount = 0
|
||||
self.startScanning()
|
||||
} else {
|
||||
|
|
@ -295,7 +295,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
} else {
|
||||
// Disconnected without error which indicates user intent to disconnect
|
||||
// Happens when swiping to disconnect
|
||||
Logger.services.info("ℹ️ [BLE] Disconnected: \(peripheral.name ?? "Unknown".localized, privacy: .public): \(String(describing: "User Initiated Disconnect".localized))")
|
||||
Logger.services.info("ℹ️ [BLE] Disconnected: \(peripheral.name ?? "Unknown".localized, privacy: .public): \(String(describing: "User Initiated Disconnect".localized), privacy: .public)")
|
||||
}
|
||||
// Start a scan so the disconnected peripheral is moved to the peripherals[] if it is awake
|
||||
self.startScanning()
|
||||
|
|
@ -485,7 +485,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
}
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.traceroute.sent %@".localized, destNum.toHex())
|
||||
Logger.mesh.info("🪧 \(logString)")
|
||||
Logger.mesh.info("🪧 \(logString, privacy: .public)")
|
||||
|
||||
} catch {
|
||||
|
||||
|
|
@ -498,14 +498,14 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
guard connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected else { return }
|
||||
|
||||
if FROMRADIO_characteristic == nil {
|
||||
Logger.mesh.error("🚨 \("firmware.version.unsupported".localized)")
|
||||
Logger.mesh.error("🚨 \("firmware.version.unsupported".localized, privacy: .public)")
|
||||
invalidVersion = true
|
||||
return
|
||||
} else {
|
||||
|
||||
let nodeName = connectedPeripheral?.peripheral.name ?? "unknown".localized
|
||||
let logString = String.localizedStringWithFormat("mesh.log.wantconfig %@".localized, nodeName)
|
||||
Logger.mesh.info("🛎️ \(logString)")
|
||||
Logger.mesh.info("🛎️ \(logString, privacy: .public)")
|
||||
// BLE Characteristics discovered, issue wantConfig
|
||||
var toRadio: ToRadio = ToRadio()
|
||||
configNonce += 1
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ class LocalNotificationManager {
|
|||
|
||||
UNUserNotificationCenter.current().add(request) { error in
|
||||
if let error {
|
||||
Logger.services.error("Error Scheduling Notification: \(error.localizedDescription)")
|
||||
Logger.services.error("Error Scheduling Notification: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ import OSLog
|
|||
}
|
||||
}
|
||||
} catch {
|
||||
Logger.services.error("💥 [App] Could not start location updates: \(error.localizedDescription)")
|
||||
Logger.services.error("💥 [App] Could not start location updates: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,6 @@
|
|||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>_XCCurrentVersionName</key>
|
||||
<string>MeshtasticDataModelV 49.xcdatamodel</string>
|
||||
<string>MeshtasticDataModelV 50.xcdatamodel</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="23605" systemVersion="24D70" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="23231" systemVersion="23G80" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
||||
<entity name="AmbientLightingConfigEntity" representedClassName="AmbientLightingConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="blue" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="current" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
|
|
@ -373,7 +373,8 @@
|
|||
<attribute name="heartbeat" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<attribute name="historyReturnMax" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="historyReturnWindow" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="isRouter" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="isRouter" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="isServer" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="lastHeartbeat" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="lastRequest" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="records" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,504 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="23605" systemVersion="24D81" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
||||
<entity name="AmbientLightingConfigEntity" representedClassName="AmbientLightingConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="blue" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="current" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="green" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="ledState" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="red" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="ambientLightingConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="ambientLightingConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="BluetoothConfigEntity" representedClassName="BluetoothConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="deviceLoggingEnabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="fixedPin" optional="YES" attributeType="Integer 32" defaultValueString="123456" usesScalarValueType="YES"/>
|
||||
<attribute name="mode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="bluetoothConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="bluetoothConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="CannedMessageConfigEntity" representedClassName="CannedMessageConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerEventCcw" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerEventCw" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerEventPress" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerPinA" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerPinB" optional="YES" attributeType="Integer 32" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerPinPress" optional="YES" attributeType="Integer 32" usesScalarValueType="YES"/>
|
||||
<attribute name="messages" optional="YES" attributeType="String" minValueString="0" maxValueString="198"/>
|
||||
<attribute name="rotary1Enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="sendBell" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="updown1Enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<relationship name="cannedMessagesConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="cannedMessageConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="ChannelEntity" representedClassName="ChannelEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="downlinkEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="id" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="index" attributeType="Integer 32" minValueString="0" maxValueString="13" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="mute" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="name" optional="YES" attributeType="String"/>
|
||||
<attribute name="positionPrecision" optional="YES" attributeType="Integer 32" defaultValueString="32" usesScalarValueType="YES"/>
|
||||
<attribute name="psk" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="role" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="uplinkEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<relationship name="myInfoChannel" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MyInfoEntity" inverseName="channels" inverseEntity="MyInfoEntity"/>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="index"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="DetectionSensorConfigEntity" representedClassName="DetectionSensorConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="minimumBroadcastSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="monitorPin" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="name" optional="YES" attributeType="String"/>
|
||||
<attribute name="sendBell" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="stateBroadcastSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="triggerType" optional="YES" attributeType="Integer 32" usesScalarValueType="YES"/>
|
||||
<attribute name="usePullup" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<relationship name="detectionSensorConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="detectionSensorConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="DeviceConfigEntity" representedClassName="DeviceConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="buttonGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="buzzerGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="debugLogEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="disableTripleClick" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="doubleTapAsButtonPress" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="isManaged" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="ledHeartbeatEnabled" optional="YES" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<attribute name="nodeInfoBroadcastSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="rebroadcastMode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="role" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="serialEnabled" optional="YES" attributeType="Boolean" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="tripleClickAsAdHocPing" optional="YES" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<attribute name="tzdef" optional="YES" attributeType="String"/>
|
||||
<relationship name="deviceConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="deviceConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="DeviceMetadataEntity" representedClassName="DeviceMetadataEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="canShutdown" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="deviceStateVersion" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="excludedModules" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="firmwareVersion" optional="YES" attributeType="String"/>
|
||||
<attribute name="hasBluetooth" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="hasEthernet" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="hasWifi" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="hwModel" optional="YES" attributeType="String"/>
|
||||
<attribute name="positionFlags" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="role" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="time" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<relationship name="metadataNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="metadata" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="DisplayConfigEntity" representedClassName="DisplayConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="compassNorthTop" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="displayMode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="flipScreen" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="gpsFormat" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="headingBold" optional="YES" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<attribute name="oledType" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="screenCarouselInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="screenOnSeconds" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="units" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="wakeOnTapOrMotion" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<relationship name="displayConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="displayConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="ExternalNotificationConfigEntity" representedClassName="ExternalNotificationConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="active" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="alertBell" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="alertBellBuzzer" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="alertBellVibra" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="alertMessage" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="alertMessageBuzzer" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="alertMessageVibra" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="nagTimeout" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="output" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="outputBuzzer" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="outputMilliseconds" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="outputVibra" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="useI2SAsBuzzer" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="usePWM" optional="YES" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<relationship name="externalNotificationConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="externalNotificationConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="LocationEntity" representedClassName="LocationEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="altitude" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="heading" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="id" optional="YES" attributeType="Integer 32" usesScalarValueType="YES"/>
|
||||
<attribute name="latitudeI" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="longitudeI" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="speed" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="routeLocation" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="RouteEntity" inverseName="locations" inverseEntity="RouteEntity"/>
|
||||
</entity>
|
||||
<entity name="LoRaConfigEntity" representedClassName="LoRaConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="bandwidth" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="channelNum" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="codingRate" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="frequencyOffset" optional="YES" attributeType="Float" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="hopLimit" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="ignoreMqtt" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="modemPreset" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="okToMqtt" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="overrideDutyCycle" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="overrideFrequency" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="regionCode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="spreadFactor" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="sx126xRxBoostedGain" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="txEnabled" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<attribute name="txPower" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="usePreset" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<relationship name="loRaConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="loRaConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="MessageEntity" representedClassName="MessageEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="ackError" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="ackSNR" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="ackTimestamp" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="admin" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="adminDescription" optional="YES" attributeType="String"/>
|
||||
<attribute name="channel" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="isEmoji" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="messageId" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="messagePayload" optional="YES" attributeType="String" defaultValueString=""/>
|
||||
<attribute name="messagePayloadMarkdown" optional="YES" attributeType="String"/>
|
||||
<attribute name="messageTimestamp" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="pkiEncrypted" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="portNum" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="publicKey" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="read" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="realACK" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="receivedACK" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="replyID" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="rssi" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<relationship name="fromUser" optional="YES" maxCount="1" deletionRule="Nullify" ordered="YES" destinationEntity="UserEntity" inverseName="sentMessages" inverseEntity="UserEntity"/>
|
||||
<relationship name="toUser" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="UserEntity" inverseName="receivedMessages" inverseEntity="UserEntity"/>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="messageId"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="MQTTConfigEntity" representedClassName="MQTTConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="address" optional="YES" attributeType="String"/>
|
||||
<attribute name="enabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="encryptionEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="jsonEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="mapPositionPrecision" optional="YES" attributeType="Integer 32" defaultValueString="13" usesScalarValueType="YES"/>
|
||||
<attribute name="mapPublishIntervalSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="mapReportingEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="password" optional="YES" attributeType="String" maxValueString="30"/>
|
||||
<attribute name="proxyToClientEnabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="root" optional="YES" attributeType="String" defaultValueString="msh"/>
|
||||
<attribute name="tlsEnabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="username" optional="YES" attributeType="String" maxValueString="30"/>
|
||||
<relationship name="mqttConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="mqttConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="MyInfoEntity" representedClassName="MyInfoEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="adminIndex" optional="YES" attributeType="Integer 32" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="bleName" optional="YES" attributeType="String"/>
|
||||
<attribute name="deviceId" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="minAppVersion" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="myNodeNum" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="peripheralId" optional="YES" attributeType="String"/>
|
||||
<attribute name="rebootCount" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="registered" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<relationship name="channels" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="ChannelEntity" inverseName="myInfoChannel" inverseEntity="ChannelEntity"/>
|
||||
<relationship name="myInfoNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="myInfo" inverseEntity="NodeInfoEntity"/>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="myNodeNum"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="NetworkConfigEntity" representedClassName="NetworkConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="dns" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="enabledProtocols" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="ethEnabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="gateway" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="ip" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="ntpServer" optional="YES" attributeType="String"/>
|
||||
<attribute name="subnet" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="wifiEnabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="wifiMode" optional="YES" attributeType="Integer 32" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="wifiPsk" optional="YES" attributeType="String" minValueString="0" maxValueString="60"/>
|
||||
<attribute name="wifiSsid" optional="YES" attributeType="String" minValueString="0" maxValueString="30"/>
|
||||
<relationship name="networkConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="networkConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="NodeInfoEntity" representedClassName="NodeInfoEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="bleName" optional="YES" attributeType="String"/>
|
||||
<attribute name="channel" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="favorite" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="firstHeard" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="hopsAway" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="id" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="ignored" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="lastHeard" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="num" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="peripheralId" optional="YES" attributeType="String"/>
|
||||
<attribute name="rssi" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="sessionExpiration" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="sessionPasskey" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="viaMqtt" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<relationship name="ambientLightingConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="AmbientLightingConfigEntity" inverseName="ambientLightingConfigNode" inverseEntity="AmbientLightingConfigEntity"/>
|
||||
<relationship name="bluetoothConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="BluetoothConfigEntity" inverseName="bluetoothConfigNode" inverseEntity="BluetoothConfigEntity"/>
|
||||
<relationship name="cannedMessageConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="CannedMessageConfigEntity" inverseName="cannedMessagesConfigNode" inverseEntity="CannedMessageConfigEntity"/>
|
||||
<relationship name="detectionSensorConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="DetectionSensorConfigEntity" inverseName="detectionSensorConfigNode" inverseEntity="DetectionSensorConfigEntity"/>
|
||||
<relationship name="deviceConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="DeviceConfigEntity" inverseName="deviceConfigNode" inverseEntity="DeviceConfigEntity"/>
|
||||
<relationship name="displayConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="DisplayConfigEntity" inverseName="displayConfigNode" inverseEntity="DisplayConfigEntity"/>
|
||||
<relationship name="externalNotificationConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="ExternalNotificationConfigEntity" inverseName="externalNotificationConfigNode" inverseEntity="ExternalNotificationConfigEntity"/>
|
||||
<relationship name="loRaConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="LoRaConfigEntity" inverseName="loRaConfigNode" inverseEntity="LoRaConfigEntity"/>
|
||||
<relationship name="metadata" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="DeviceMetadataEntity" inverseName="metadataNode" inverseEntity="DeviceMetadataEntity"/>
|
||||
<relationship name="mqttConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MQTTConfigEntity" inverseName="mqttConfigNode" inverseEntity="MQTTConfigEntity"/>
|
||||
<relationship name="myInfo" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MyInfoEntity" inverseName="myInfoNode" inverseEntity="MyInfoEntity"/>
|
||||
<relationship name="networkConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NetworkConfigEntity" inverseName="networkConfigNode" inverseEntity="NetworkConfigEntity"/>
|
||||
<relationship name="pax" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="PaxCounterEntity" inverseName="paxNode" inverseEntity="PaxCounterEntity"/>
|
||||
<relationship name="paxCounterConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="PaxCounterConfigEntity" inverseName="paxCounterConfigNode" inverseEntity="PaxCounterConfigEntity"/>
|
||||
<relationship name="positionConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="PositionConfigEntity" inverseName="positionConfigNode" inverseEntity="PositionConfigEntity"/>
|
||||
<relationship name="positions" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="PositionEntity" inverseName="nodePosition" inverseEntity="PositionEntity"/>
|
||||
<relationship name="powerConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="PowerConfigEntity" inverseName="powerConfigNode" inverseEntity="PowerConfigEntity"/>
|
||||
<relationship name="rangeTestConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="RangeTestConfigEntity" inverseName="rangeTestConfigNode" inverseEntity="RangeTestConfigEntity"/>
|
||||
<relationship name="rtttlConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="RTTTLConfigEntity" inverseName="rtttlConfigNode" inverseEntity="RTTTLConfigEntity"/>
|
||||
<relationship name="securityConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="SecurityConfigEntity" inverseName="securityConfigNode" inverseEntity="SecurityConfigEntity"/>
|
||||
<relationship name="serialConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="SerialConfigEntity" inverseName="serialConfigNode" inverseEntity="SerialConfigEntity"/>
|
||||
<relationship name="storeForwardConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="StoreForwardConfigEntity" inverseName="storeForwardConfigNode" inverseEntity="StoreForwardConfigEntity"/>
|
||||
<relationship name="telemetries" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="TelemetryEntity" inverseName="nodeTelemetry" inverseEntity="TelemetryEntity"/>
|
||||
<relationship name="telemetryConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="TelemetryConfigEntity" inverseName="telemetryConfigNode" inverseEntity="TelemetryConfigEntity"/>
|
||||
<relationship name="traceRoutes" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="TraceRouteEntity" inverseName="node" inverseEntity="TraceRouteEntity"/>
|
||||
<relationship name="user" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="UserEntity" inverseName="userNode" inverseEntity="UserEntity"/>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="num"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="PaxCounterConfigEntity" representedClassName="PaxCounterConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="bleThreshold" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="updateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="wifiThreshold" optional="YES" attributeType="Integer 32" defaultValueString="-80" usesScalarValueType="YES"/>
|
||||
<relationship name="paxCounterConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="paxCounterConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="PaxCounterEntity" representedClassName="PaxCounterEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="ble" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="time" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="uptime" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="wifi" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="paxNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="pax" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="PositionConfigEntity" representedClassName="PositionConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="broadcastSmartMinimumDistance" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="broadcastSmartMinimumIntervalSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="deviceGpsEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="fixedPosition" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="gpsAttemptTime" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="gpsEnGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="gpsMode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="gpsUpdateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="positionBroadcastSeconds" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="positionFlags" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="rxGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="smartPositionEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="txGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="positionConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="positionConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="PositionEntity" representedClassName="PositionEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="altitude" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="heading" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="latest" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="latitudeI" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="longitudeI" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="precisionBits" optional="YES" attributeType="Integer 32" defaultValueString="32" usesScalarValueType="YES"/>
|
||||
<attribute name="rssi" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="satsInView" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="seqNo" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="speed" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="time" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<relationship name="nodePosition" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="positions" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="PowerConfigEntity" representedClassName="PowerConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="adcMultiplierOverride" optional="YES" attributeType="Float" usesScalarValueType="YES"/>
|
||||
<attribute name="deviceBatteryInaAddress" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="isPowerSaving" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="lsSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="minWakeSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="onBatteryShutdownAfterSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="waitBluetoothSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="powerConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="powerConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="RangeTestConfigEntity" representedClassName="RangeTestConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="save" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="sender" optional="YES" attributeType="Integer 32" usesScalarValueType="YES"/>
|
||||
<relationship name="rangeTestConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="rangeTestConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="RouteEntity" representedClassName="RouteEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="color" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="date" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="distance" optional="YES" attributeType="Double" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="elevationGain" optional="YES" attributeType="Double" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="endDate" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="id" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="name" optional="YES" attributeType="String"/>
|
||||
<attribute name="notes" optional="YES" attributeType="String"/>
|
||||
<relationship name="locations" optional="YES" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="LocationEntity" inverseName="routeLocation" inverseEntity="LocationEntity"/>
|
||||
</entity>
|
||||
<entity name="RTTTLConfigEntity" representedClassName="RTTTLConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="ringtone" optional="YES" attributeType="String" maxValueString="228" defaultValueString=""/>
|
||||
<relationship name="rtttlConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="rtttlConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="SecurityConfigEntity" representedClassName="SecurityConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="adminChannelEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="adminKey" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="adminKey2" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="adminKey3" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="bluetoothLoggingEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="debugLogApiEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="isManaged" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="privateKey" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="publicKey" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="serialEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<relationship name="securityConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="securityConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="SerialConfigEntity" representedClassName="SerialConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="baudRate" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="echo" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="mode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="overrideConsoleSerialPort" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="rxd" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="timeout" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="txd" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="serialConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="serialConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="StoreForwardConfigEntity" representedClassName="StoreForwardConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="enabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="heartbeat" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<attribute name="historyReturnMax" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="historyReturnWindow" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="isRouter" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="lastHeartbeat" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="lastRequest" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="records" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="storeForwardConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="storeForwardConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="TelemetryConfigEntity" representedClassName="TelemetryConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="deviceUpdateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="environmentDisplayFahrenheit" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="environmentMeasurementEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="environmentScreenEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="environmentUpdateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="powerMeasurementEnabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="powerScreenEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="powerUpdateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="telemetryConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="telemetryConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="TelemetryEntity" representedClassName="TelemetryEntity" syncable="YES">
|
||||
<attribute name="airUtilTx" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="barometricPressure" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="batteryLevel" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="channelUtilization" optional="YES" attributeType="Float" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="current" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="gasResistance" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="iaq" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="irLux" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="lux" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="metricsType" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="numOnlineNodes" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="numPacketsRx" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="numPacketsRxBad" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="numPacketsTx" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="numRxDupe" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="numTotalNodes" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="numTxRelay" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="numTxRelayCanceled" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="powerCh1Current" optional="YES" attributeType="Float" usesScalarValueType="YES"/>
|
||||
<attribute name="powerCh1Voltage" optional="YES" attributeType="Float" usesScalarValueType="YES"/>
|
||||
<attribute name="powerCh2Current" optional="YES" attributeType="Float" usesScalarValueType="YES"/>
|
||||
<attribute name="powerCh2Voltage" optional="YES" attributeType="Float" usesScalarValueType="YES"/>
|
||||
<attribute name="powerCh3Current" optional="YES" attributeType="Float" usesScalarValueType="YES"/>
|
||||
<attribute name="powerCh3Voltage" optional="YES" attributeType="Float" usesScalarValueType="YES"/>
|
||||
<attribute name="radiation" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="rainfall1H" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="rainfall24H" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="relativeHumidity" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="rssi" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="soilMoisture" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="soilTemperature" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="temperature" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="time" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="uptimeSeconds" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="uvLux" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="voltage" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="weight" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="whiteLux" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="windDirection" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="windGust" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="windLull" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="windSpeed" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<relationship name="nodeTelemetry" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="telemetries" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="TraceRouteEntity" representedClassName="TraceRouteEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="hasPositions" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="hopsBack" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="hopsTowards" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="id" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="response" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="routeBackText" optional="YES" attributeType="String"/>
|
||||
<attribute name="routeText" optional="YES" attributeType="String"/>
|
||||
<attribute name="sent" optional="YES" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="time" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<relationship name="hops" optional="YES" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="TraceRouteHopEntity" inverseName="traceRoute" inverseEntity="TraceRouteHopEntity"/>
|
||||
<relationship name="node" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="traceRoutes" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="TraceRouteHopEntity" representedClassName="TraceRouteHopEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="altitude" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="back" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="latitudeI" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="longitudeI" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="name" optional="YES" attributeType="String"/>
|
||||
<attribute name="num" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="time" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<relationship name="traceRoute" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="TraceRouteEntity" inverseName="hops" inverseEntity="TraceRouteEntity"/>
|
||||
</entity>
|
||||
<entity name="UserEntity" representedClassName="UserEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="hwDisplayName" optional="YES" attributeType="String"/>
|
||||
<attribute name="hwModel" attributeType="String"/>
|
||||
<attribute name="hwModelId" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="isLicensed" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="keyMatch" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<attribute name="lastMessage" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="longName" attributeType="String"/>
|
||||
<attribute name="mute" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="newPublicKey" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="num" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="numString" optional="YES" attributeType="String"/>
|
||||
<attribute name="pkiEncrypted" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="publicKey" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="role" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="shortName" attributeType="String"/>
|
||||
<attribute name="userId" attributeType="String"/>
|
||||
<relationship name="receivedMessages" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="MessageEntity" inverseName="toUser" inverseEntity="MessageEntity"/>
|
||||
<relationship name="sentMessages" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="MessageEntity" inverseName="fromUser" inverseEntity="MessageEntity"/>
|
||||
<relationship name="userNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="user" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="WaypointEntity" representedClassName="WaypointEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="created" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="expire" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="icon" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="id" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="lastUpdated" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="latitudeI" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="locked" attributeType="Integer 64" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="longDescription" optional="YES" attributeType="String" maxValueString="100"/>
|
||||
<attribute name="longitudeI" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="name" attributeType="String" minValueString="1" maxValueString="30"/>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="id"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
</model>
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -97,10 +97,10 @@ class MeshtasticAppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificat
|
|||
if let targetValue = userInfo["target"] as? String,
|
||||
let deepLink = userInfo["path"] as? String,
|
||||
let url = URL(string: deepLink) {
|
||||
Logger.services.info("userNotificationCenter didReceiveResponse \(targetValue) \(deepLink)")
|
||||
Logger.services.info("userNotificationCenter didReceiveResponse \(targetValue, privacy: .public) \(deepLink, privacy: .public)")
|
||||
router?.route(url: url)
|
||||
} else {
|
||||
Logger.services.error("Failed to handle notification response: \(userInfo)")
|
||||
Logger.services.error("Failed to handle notification response: \(userInfo, privacy: .public)")
|
||||
}
|
||||
completionHandler()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,5 +42,14 @@ public class TelemetryEntity: NSManagedObject, Identifiable {
|
|||
@ManagedAttribute<Float>(attributeName: "windGust") public var windGust: Float?
|
||||
@ManagedAttribute<Float>(attributeName: "windLull") public var windLull: Float?
|
||||
@ManagedAttribute<Float>(attributeName: "windSpeed") public var windSpeed: Float?
|
||||
@ManagedAttribute<Float>(attributeName: "irLux") public var irLux: Float?
|
||||
@ManagedAttribute<Float>(attributeName: "lux") public var lux: Float?
|
||||
@ManagedAttribute<Float>(attributeName: "uvLux") public var uvLux: Float?
|
||||
@ManagedAttribute<Float>(attributeName: "whiteLux") public var whiteLux: Float?
|
||||
@ManagedAttribute<Float>(attributeName: "radiation") public var radiation: Float?
|
||||
@ManagedAttribute<Float>(attributeName: "rainfall1H") public var rainfall1H: Float?
|
||||
@ManagedAttribute<Float>(attributeName: "rainfall24H") public var rainfall24H: Float?
|
||||
@ManagedAttribute<Float>(attributeName: "soilTemperature") public var soilTemperature: Float?
|
||||
@ManagedAttribute<UInt32>(attributeName: "soilMoisture") public var soilMoisture: UInt32?
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,12 +38,18 @@ class MetricsChartSeries: ObservableObject {
|
|||
// Possibly converted to the proper units
|
||||
let valueClosure: (TelemetryEntity) -> Float?
|
||||
|
||||
// Used for scaling the Y-axis
|
||||
let initialYAxisRange: ClosedRange<Float>?
|
||||
let minumumYAxisSpan: Float?
|
||||
|
||||
// Main initializer
|
||||
init<Value, ChartBody: ChartContent, ForegroundStyle: ShapeStyle>(
|
||||
id: String,
|
||||
keyPath: KeyPath<TelemetryEntity, Value>,
|
||||
name: String,
|
||||
abbreviatedName: String,
|
||||
initialYAxisRange: ClosedRange<Float>? = nil,
|
||||
minumumYAxisSpan: Float? = nil,
|
||||
conversion: ((Value) -> Value)? = nil,
|
||||
visible: Bool = true,
|
||||
foregroundStyle: @escaping ((ClosedRange<Float>?) -> ForegroundStyle?) = { _ in nil },
|
||||
|
|
@ -54,6 +60,8 @@ class MetricsChartSeries: ObservableObject {
|
|||
self.id = id
|
||||
self.name = name
|
||||
self.abbreviatedName = abbreviatedName
|
||||
self.initialYAxisRange = initialYAxisRange
|
||||
self.minumumYAxisSpan = minumumYAxisSpan
|
||||
self.visible = visible
|
||||
|
||||
// By saving these closures, MetricsChartSeries can be type agnostic
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
class MetricsSeriesList: ObservableObject, RandomAccessCollection, RangeReplaceableCollection {
|
||||
|
||||
@Published var series: [MetricsChartSeries]
|
||||
|
|
@ -38,23 +39,82 @@ class MetricsSeriesList: ObservableObject, RandomAccessCollection, RangeReplacea
|
|||
return nil
|
||||
}
|
||||
|
||||
// Calculates the chartRange based on the series configuration and data provided
|
||||
// Besides checkign the range of the data, this function also obeys some series-level
|
||||
// configuraiton, such as:
|
||||
// 1. starting with a desired fixed range
|
||||
// 2. obeying a minimum span
|
||||
func chartRange(forData data: [TelemetryEntity]) -> ClosedRange<Float> {
|
||||
var lower: Float?
|
||||
var upper: Float?
|
||||
var globalLower: Float = .infinity
|
||||
var globalUpper: Float = -.infinity
|
||||
|
||||
// Keep track of the range of each series
|
||||
var range: [MetricsChartSeries: ClosedRange<Float>] = [:]
|
||||
|
||||
// Determine if there is an initial fixed range.
|
||||
// The range might exapand past this initial range if the data goes beyond.
|
||||
for aSeries in self.visible {
|
||||
if let thisRange = aSeries.initialYAxisRange {
|
||||
range[aSeries] = thisRange
|
||||
if thisRange.upperBound > globalUpper {globalUpper = thisRange.upperBound}
|
||||
if thisRange.lowerBound < globalLower {globalLower = thisRange.lowerBound}
|
||||
}
|
||||
}
|
||||
|
||||
// Iterate through all the data. It would be easier to iterate
|
||||
// the series then the data, but this way we only iterate the data once
|
||||
for te in data {
|
||||
for aSeries in self.visible {
|
||||
var seriesUpper = range[aSeries]?.upperBound ?? -.infinity
|
||||
var seriesLower = range[aSeries]?.lowerBound ?? .infinity
|
||||
|
||||
if let value = aSeries.valueFor(te) {
|
||||
if value > (upper ?? -.infinity) {upper = value}
|
||||
if value < (lower ?? .infinity) {lower = value}
|
||||
// Update the global bounds
|
||||
if value > globalUpper {globalUpper = value}
|
||||
if value < globalLower {globalLower = value}
|
||||
|
||||
// Update the series bounds if necessary
|
||||
if value > seriesUpper || value < seriesLower {
|
||||
if value > seriesUpper {
|
||||
seriesUpper = value
|
||||
}
|
||||
if value < seriesLower {
|
||||
seriesLower = value
|
||||
}
|
||||
if seriesUpper.isFinite && seriesLower.isFinite {
|
||||
range[aSeries] = seriesLower...seriesUpper
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return default range if no data or nil
|
||||
guard let lower, let upper else {
|
||||
|
||||
// Go through each series one last time to obey the minimum span
|
||||
for aSeries in self.visible {
|
||||
if let minimumSpan = aSeries.minumumYAxisSpan,
|
||||
let currentRange = range[aSeries] {
|
||||
let currentSpan = currentRange.upperBound - currentRange.lowerBound
|
||||
if currentSpan < minimumSpan {
|
||||
// Calculate the center of the range
|
||||
let centerOfRange = currentRange.lowerBound + (currentSpan / 2)
|
||||
let newLower = centerOfRange - (minimumSpan / 2.0)
|
||||
let newUpper = centerOfRange + (minimumSpan / 2.0)
|
||||
|
||||
if newUpper > globalUpper {
|
||||
globalUpper = newUpper
|
||||
}
|
||||
if newLower < globalLower {
|
||||
globalLower = newLower
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return default range if no data
|
||||
if !globalLower.isFinite || !globalUpper.isFinite {
|
||||
return 0.0...100.0
|
||||
}
|
||||
return lower...upper
|
||||
return globalLower...globalUpper
|
||||
}
|
||||
|
||||
// Collection conformance
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ class PersistenceController {
|
|||
|
||||
if let error = error as NSError? {
|
||||
|
||||
Logger.data.error("CoreData Error: \(error.localizedDescription). Now attempting to truncate CoreData database. All app data will be lost.")
|
||||
Logger.data.error("CoreData Error: \(error.localizedDescription, privacy: .public). Now attempting to truncate CoreData database. All app data will be lost.")
|
||||
self.clearDatabase()
|
||||
}
|
||||
})
|
||||
|
|
@ -65,11 +65,11 @@ class PersistenceController {
|
|||
do {
|
||||
try persistentStoreCoordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: url, options: nil)
|
||||
} catch let error {
|
||||
Logger.data.error("Failed to re-create CoreData database: \(error.localizedDescription)")
|
||||
Logger.data.error("Failed to re-create CoreData database: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
|
||||
} catch let error {
|
||||
Logger.data.error("Failed to destroy CoreData database, delete the app and re-install to clear data. Attempted to clear persistent store: \(error.localizedDescription)")
|
||||
Logger.data.error("Failed to destroy CoreData database, delete the app and re-install to clear data. Attempted to clear persistent store: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -103,123 +103,4 @@ extension NSPersistentContainer {
|
|||
case invalidSource(String)
|
||||
}
|
||||
|
||||
/// Restore backup persistent stores located in the directory referenced by `backupURL`.
|
||||
/// **Be very careful with this**. To restore a persistent store, the current persistent store must be removed from the container. When that happens, **all currently loaded Core Data objects** will become invalid. Using them after restoring will cause your app to crash.
|
||||
/// When calling this method you **must** ensure that you do not continue to use any previously fetched managed objects or existing fetched results controllers. **If this method does not throw, that does not mean your app is safe.** You need to take extra steps to prevent crashes. The details vary depending on the nature of your app.
|
||||
/// - Parameter backupURL: A file URL containing backup copies of all currently loaded persistent stores.
|
||||
/// - Throws: `CopyPersistentStoreError` in various situations.
|
||||
/// - Returns: Nothing. If no errors are thrown, the restore is complete.
|
||||
func restorePersistentStore(from backupURL: URL) throws {
|
||||
guard backupURL.isFileURL else {
|
||||
throw CopyPersistentStoreErrors.invalidSource("Backup URL must be a file URL")
|
||||
}
|
||||
|
||||
var isDirectory: ObjCBool = false
|
||||
if FileManager.default.fileExists(atPath: backupURL.path, isDirectory: &isDirectory) {
|
||||
if !isDirectory.boolValue {
|
||||
throw CopyPersistentStoreErrors.invalidSource("Source URL must be a directory")
|
||||
}
|
||||
} else {
|
||||
throw CopyPersistentStoreErrors.invalidSource("Source URL must exist")
|
||||
}
|
||||
|
||||
for persistentStoreDescription in persistentStoreDescriptions {
|
||||
guard let loadedStoreURL = persistentStoreDescription.url else {
|
||||
continue
|
||||
}
|
||||
let backupStoreURL = backupURL.appendingPathComponent(loadedStoreURL.lastPathComponent)
|
||||
guard FileManager.default.fileExists(atPath: backupStoreURL.path) else {
|
||||
throw CopyPersistentStoreErrors.invalidSource("Missing backup store for \(backupStoreURL)")
|
||||
}
|
||||
do {
|
||||
let storeOptions = persistentStoreDescription.options
|
||||
let configurationName = persistentStoreDescription.configuration
|
||||
let storeType = persistentStoreDescription.type
|
||||
// Replace the current store with the backup copy. This has a side effect of removing the current store from the Core Data stack.
|
||||
// When restoring, it's necessary to use the current persistent store coordinator.
|
||||
try persistentStoreCoordinator.replacePersistentStore(at: loadedStoreURL, destinationOptions: storeOptions, withPersistentStoreFrom: backupStoreURL, sourceOptions: storeOptions, ofType: storeType)
|
||||
// Add the persistent store at the same location we've been using, because it was removed in the previous step.
|
||||
try persistentStoreCoordinator.addPersistentStore(ofType: storeType, configurationName: configurationName, at: loadedStoreURL, options: storeOptions)
|
||||
} catch {
|
||||
throw CopyPersistentStoreErrors.copyStoreError("Could not restore: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Copy all loaded persistent stores to a new directory. Each currently loaded file-based persistent store will be copied (including journal files, external binary storage, and anything else Core Data needs) into the destination directory to a persistent store with the same name and type as the existing store. In-memory stores, if any, are skipped.
|
||||
/// - Parameters:
|
||||
/// - destinationURL: Destination for new persistent store files. Must be a file URL. If `overwriting` is `false` and `destinationURL` exists, it must be a directory.
|
||||
/// - overwriting: If `true`, any existing copies of the persistent store will be replaced or updated. If `false`, existing copies will not be changed or remoted. When this is `false`, the destination persistent store file must not already exist.
|
||||
/// - Throws: `CopyPersistentStoreError`
|
||||
/// - Returns: Nothing. If no errors are thrown, all loaded persistent stores will be copied to the destination directory.
|
||||
func copyPersistentStores(to destinationURL: URL, overwriting: Bool = false) throws {
|
||||
|
||||
guard !destinationURL.relativeString.contains("/0/") else {
|
||||
throw CopyPersistentStoreErrors.invalidDestination("Invalid 0 Node Id")
|
||||
}
|
||||
|
||||
guard destinationURL.isFileURL else {
|
||||
throw CopyPersistentStoreErrors.invalidDestination("Destination URL must be a file URL")
|
||||
}
|
||||
// If the destination exists and we aren't overwriting it, then it must be a directory. (If we are overwriting, we'll remove it anyway, so it doesn't matter whether it's a directory).
|
||||
var isDirectory: ObjCBool = false
|
||||
if !overwriting && FileManager.default.fileExists(atPath: destinationURL.path, isDirectory: &isDirectory) {
|
||||
if !isDirectory.boolValue {
|
||||
throw CopyPersistentStoreErrors.invalidDestination("Destination URL must be a directory")
|
||||
}
|
||||
// Don't check if destination stores exist in the destination dir, that comes later on a per-store basis.
|
||||
}
|
||||
// If we're overwriting, remove the destination.
|
||||
if overwriting && FileManager.default.fileExists(atPath: destinationURL.path) {
|
||||
do {
|
||||
try FileManager.default.removeItem(at: destinationURL)
|
||||
} catch {
|
||||
throw CopyPersistentStoreErrors.destinationNotRemoved("Can't overwrite destination at \(destinationURL)")
|
||||
}
|
||||
}
|
||||
// Create the destination directory
|
||||
do {
|
||||
try FileManager.default.createDirectory(at: destinationURL, withIntermediateDirectories: true, attributes: nil)
|
||||
} catch {
|
||||
throw CopyPersistentStoreErrors.destinationError("Could not create destination directory at \(destinationURL)")
|
||||
}
|
||||
|
||||
for persistentStoreDescription in persistentStoreDescriptions {
|
||||
guard let storeURL = persistentStoreDescription.url else {
|
||||
continue
|
||||
}
|
||||
guard persistentStoreDescription.type != NSInMemoryStoreType else {
|
||||
continue
|
||||
}
|
||||
let destinationStoreURL = destinationURL.appendingPathComponent(storeURL.lastPathComponent)
|
||||
|
||||
if !overwriting && FileManager.default.fileExists(atPath: destinationStoreURL.path) {
|
||||
// If the destination exists, the replacePersistentStore call will update it in place. That's fine unless we're not overwriting.
|
||||
throw CopyPersistentStoreErrors.destinationError("Destination already exists at \(destinationStoreURL)")
|
||||
}
|
||||
do {
|
||||
// Replace an existing backup, if any, with a new one with the same options and type. This doesn't affect the current Core Data stack.
|
||||
// The function name says "replace", but it works if there's nothing at the destination yet. In that case it creates a new persistent store.
|
||||
// Note that for backup, it doesn't matter if the persistent store coordinator is the one currently in use or a different one. It could be a class function, for this use.
|
||||
try persistentStoreCoordinator.replacePersistentStore(at: destinationStoreURL, destinationOptions: persistentStoreDescription.options, withPersistentStoreFrom: storeURL, sourceOptions: persistentStoreDescription.options, ofType: persistentStoreDescription.type)
|
||||
/// Cleanup extra files
|
||||
let directory = destinationStoreURL.deletingLastPathComponent()
|
||||
/// Delete -wal file
|
||||
do {
|
||||
try FileManager.default.removeItem(at: directory.appendingPathComponent("Meshtastic.sqlite-wal"))
|
||||
/// Delete -shm file
|
||||
do {
|
||||
try FileManager.default.removeItem(at: directory.appendingPathComponent("Meshtastic.sqlite-shm"))
|
||||
} catch {
|
||||
Logger.services.error("🗄 Error Deleting Meshtastic.sqlite-shm file \(error, privacy: .public)")
|
||||
}
|
||||
} catch {
|
||||
Logger.services.error("🗄 Error Deleting Meshtastic.sqlite-wal file \(error, privacy: .public)")
|
||||
}
|
||||
} catch {
|
||||
Logger.services.error("🗄 Error Deleting Meshtastic.sqlite file \(error, privacy: .public)")
|
||||
throw CopyPersistentStoreErrors.copyStoreError("\(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ public func deleteChannelMessages(channel: ChannelEntity, context: NSManagedObje
|
|||
}
|
||||
try context.save()
|
||||
} catch let error as NSError {
|
||||
Logger.data.error("\(error.localizedDescription)")
|
||||
Logger.data.error("\(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -98,7 +98,7 @@ public func deleteUserMessages(user: UserEntity, context: NSManagedObjectContext
|
|||
}
|
||||
try context.save()
|
||||
} catch let error as NSError {
|
||||
Logger.data.error("\(error.localizedDescription)")
|
||||
Logger.data.error("\(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -122,7 +122,7 @@ public func clearCoreDataDatabase(context: NSManagedObjectContext, includeRoutes
|
|||
do {
|
||||
try context.executeAndMergeChanges(using: deleteRequest)
|
||||
} catch {
|
||||
Logger.data.error("\(error.localizedDescription)")
|
||||
Logger.data.error("\(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -130,7 +130,7 @@ public func clearCoreDataDatabase(context: NSManagedObjectContext, includeRoutes
|
|||
func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.nodeinfo.received %@".localized, packet.from.toHex())
|
||||
Logger.mesh.info("📟 \(logString)")
|
||||
Logger.mesh.info("📟 \(logString, privacy: .public)")
|
||||
|
||||
guard packet.from > 0 else { return }
|
||||
|
||||
|
|
@ -313,7 +313,7 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext)
|
|||
func upsertPositionPacket (packet: MeshPacket, context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.position.received %@".localized, String(packet.from))
|
||||
Logger.mesh.info("📍 \(logString)")
|
||||
Logger.mesh.info("📍 \(logString, privacy: .public)")
|
||||
|
||||
let fetchNodePositionRequest = NodeInfoEntity.fetchRequest()
|
||||
fetchNodePositionRequest.predicate = NSPredicate(format: "num == %lld", Int64(packet.from))
|
||||
|
|
@ -407,7 +407,7 @@ func upsertPositionPacket (packet: MeshPacket, context: NSManagedObjectContext)
|
|||
func upsertBluetoothConfigPacket(config: Config.BluetoothConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.bluetooth.config %@".localized, String(nodeNum))
|
||||
Logger.mesh.info("📶 \(logString)")
|
||||
Logger.mesh.info("📶 \(logString, privacy: .public)")
|
||||
|
||||
let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest()
|
||||
fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum))
|
||||
|
|
@ -451,7 +451,7 @@ func upsertBluetoothConfigPacket(config: Config.BluetoothConfig, nodeNum: Int64,
|
|||
func upsertDeviceConfigPacket(config: Config.DeviceConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.device.config %@".localized, String(nodeNum))
|
||||
Logger.mesh.info("📟 \(logString)")
|
||||
Logger.mesh.info("📟 \(logString, privacy: .public)")
|
||||
let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest()
|
||||
fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum))
|
||||
|
||||
|
|
@ -506,7 +506,7 @@ func upsertDeviceConfigPacket(config: Config.DeviceConfig, nodeNum: Int64, sessi
|
|||
func upsertDisplayConfigPacket(config: Config.DisplayConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.display.config %@".localized, nodeNum.toHex())
|
||||
Logger.data.info("🖥️ \(logString)")
|
||||
Logger.data.info("🖥️ \(logString, privacy: .public)")
|
||||
|
||||
let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest()
|
||||
fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum))
|
||||
|
|
@ -551,19 +551,15 @@ func upsertDisplayConfigPacket(config: Config.DisplayConfig, nodeNum: Int64, ses
|
|||
Logger.data.info("💾 [DisplayConfigEntity] Updated for node: \(nodeNum.toHex(), privacy: .public)")
|
||||
|
||||
} catch {
|
||||
|
||||
context.rollback()
|
||||
|
||||
let nsError = error as NSError
|
||||
Logger.data.error("💥 [DisplayConfigEntity] Error Updating Core Data: \(nsError, privacy: .public)")
|
||||
}
|
||||
} else {
|
||||
|
||||
Logger.data.error("💥 [DisplayConfigEntity] No Nodes found in local database matching node \(nodeNum.toHex()) unable to save Display Config")
|
||||
Logger.data.error("💥 [DisplayConfigEntity] No Nodes found in local database matching node \(nodeNum.toHex(), privacy: .public) unable to save Display Config")
|
||||
}
|
||||
|
||||
} catch {
|
||||
|
||||
let nsError = error as NSError
|
||||
Logger.data.error("💥 [DisplayConfigEntity] Fetching node for core data failed: \(nsError, privacy: .public)")
|
||||
}
|
||||
|
|
@ -572,7 +568,7 @@ func upsertDisplayConfigPacket(config: Config.DisplayConfig, nodeNum: Int64, ses
|
|||
func upsertLoRaConfigPacket(config: Config.LoRaConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.lora.config %@".localized, nodeNum.toHex())
|
||||
Logger.data.info("📻 \(logString)")
|
||||
Logger.data.info("📻 \(logString, privacy: .public)")
|
||||
|
||||
let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest()
|
||||
fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", nodeNum)
|
||||
|
|
@ -643,7 +639,7 @@ func upsertLoRaConfigPacket(config: Config.LoRaConfig, nodeNum: Int64, sessionPa
|
|||
func upsertNetworkConfigPacket(config: Config.NetworkConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.network.config %@".localized, String(nodeNum))
|
||||
Logger.data.info("🌐 \(logString)")
|
||||
Logger.data.info("🌐 \(logString, privacy: .public)")
|
||||
|
||||
let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest()
|
||||
fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum))
|
||||
|
|
@ -658,12 +654,14 @@ func upsertNetworkConfigPacket(config: Config.NetworkConfig, nodeNum: Int64, ses
|
|||
newNetworkConfig.wifiSsid = config.wifiSsid
|
||||
newNetworkConfig.wifiPsk = config.wifiPsk
|
||||
newNetworkConfig.ethEnabled = config.ethEnabled
|
||||
newNetworkConfig.enabledProtocols = Int32(config.enabledProtocols)
|
||||
fetchedNode[0].networkConfig = newNetworkConfig
|
||||
} else {
|
||||
fetchedNode[0].networkConfig?.ethEnabled = config.ethEnabled
|
||||
fetchedNode[0].networkConfig?.wifiEnabled = config.wifiEnabled
|
||||
fetchedNode[0].networkConfig?.wifiSsid = config.wifiSsid
|
||||
fetchedNode[0].networkConfig?.wifiPsk = config.wifiPsk
|
||||
fetchedNode[0].networkConfig?.enabledProtocols = Int32(config.enabledProtocols)
|
||||
}
|
||||
if sessionPasskey != nil {
|
||||
fetchedNode[0].sessionPasskey = sessionPasskey
|
||||
|
|
@ -690,7 +688,7 @@ func upsertNetworkConfigPacket(config: Config.NetworkConfig, nodeNum: Int64, ses
|
|||
func upsertPositionConfigPacket(config: Config.PositionConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.position.config %@".localized, String(nodeNum))
|
||||
Logger.data.info("🗺️ \(logString)")
|
||||
Logger.data.info("🗺️ \(logString, privacy: .public)")
|
||||
|
||||
let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest()
|
||||
fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum))
|
||||
|
|
@ -753,7 +751,7 @@ func upsertPositionConfigPacket(config: Config.PositionConfig, nodeNum: Int64, s
|
|||
|
||||
func upsertPowerConfigPacket(config: Config.PowerConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) {
|
||||
let logString = String.localizedStringWithFormat("mesh.log.power.config %@".localized, String(nodeNum))
|
||||
Logger.data.info("🗺️ \(logString)")
|
||||
Logger.data.info("🗺️ \(logString, privacy: .public)")
|
||||
|
||||
let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest()
|
||||
fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum))
|
||||
|
|
@ -805,7 +803,7 @@ func upsertPowerConfigPacket(config: Config.PowerConfig, nodeNum: Int64, session
|
|||
func upsertSecurityConfigPacket(config: Config.SecurityConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.security.config %@".localized, String(nodeNum))
|
||||
Logger.data.info("🛡️ \(logString)")
|
||||
Logger.data.info("🛡️ \(logString, privacy: .public)")
|
||||
|
||||
let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest()
|
||||
fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum))
|
||||
|
|
@ -866,7 +864,7 @@ func upsertSecurityConfigPacket(config: Config.SecurityConfig, nodeNum: Int64, s
|
|||
func upsertAmbientLightingModuleConfigPacket(config: ModuleConfig.AmbientLightingConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.ambientlighting.config %@".localized, String(nodeNum))
|
||||
Logger.data.info("🏮 \(logString)")
|
||||
Logger.data.info("🏮 \(logString, privacy: .public)")
|
||||
|
||||
let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest()
|
||||
fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum))
|
||||
|
|
@ -919,7 +917,7 @@ func upsertAmbientLightingModuleConfigPacket(config: ModuleConfig.AmbientLightin
|
|||
func upsertCannedMessagesModuleConfigPacket(config: ModuleConfig.CannedMessageConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.cannedmessage.config %@".localized, String(nodeNum))
|
||||
Logger.data.info("🥫 \(logString)")
|
||||
Logger.data.info("🥫 \(logString, privacy: .public)")
|
||||
|
||||
let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest()
|
||||
fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum))
|
||||
|
|
@ -978,7 +976,7 @@ func upsertCannedMessagesModuleConfigPacket(config: ModuleConfig.CannedMessageCo
|
|||
func upsertDetectionSensorModuleConfigPacket(config: ModuleConfig.DetectionSensorConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.detectionsensor.config %@".localized, String(nodeNum))
|
||||
Logger.data.info("🕵️ \(logString)")
|
||||
Logger.data.info("🕵️ \(logString, privacy: .public)")
|
||||
|
||||
let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest()
|
||||
fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum))
|
||||
|
|
@ -1035,7 +1033,7 @@ func upsertDetectionSensorModuleConfigPacket(config: ModuleConfig.DetectionSenso
|
|||
func upsertExternalNotificationModuleConfigPacket(config: ModuleConfig.ExternalNotificationConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.externalnotification.config %@".localized, String(nodeNum))
|
||||
Logger.data.info("📣 \(logString)")
|
||||
Logger.data.info("📣 \(logString, privacy: .public)")
|
||||
|
||||
let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest()
|
||||
fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum))
|
||||
|
|
@ -1104,7 +1102,7 @@ func upsertExternalNotificationModuleConfigPacket(config: ModuleConfig.ExternalN
|
|||
func upsertPaxCounterModuleConfigPacket(config: ModuleConfig.PaxcounterConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.paxcounter.config %@".localized, String(nodeNum))
|
||||
Logger.data.info("🧑🤝🧑 \(logString)")
|
||||
Logger.data.info("🧑🤝🧑 \(logString, privacy: .public)")
|
||||
|
||||
let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest()
|
||||
fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum))
|
||||
|
|
@ -1146,7 +1144,7 @@ func upsertPaxCounterModuleConfigPacket(config: ModuleConfig.PaxcounterConfig, n
|
|||
func upsertRtttlConfigPacket(ringtone: String, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.ringtone.config %@".localized, String(nodeNum))
|
||||
Logger.data.info("⛰️ \(logString)")
|
||||
Logger.data.info("⛰️ \(logString, privacy: .public)")
|
||||
|
||||
let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest()
|
||||
fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum))
|
||||
|
|
@ -1186,7 +1184,7 @@ func upsertRtttlConfigPacket(ringtone: String, nodeNum: Int64, sessionPasskey: D
|
|||
func upsertMqttModuleConfigPacket(config: ModuleConfig.MQTTConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.mqtt.config %@".localized, String(nodeNum))
|
||||
Logger.data.info("🌉 \(logString)")
|
||||
Logger.data.info("🌉 \(logString, privacy: .public)")
|
||||
|
||||
let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest()
|
||||
fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum))
|
||||
|
|
@ -1248,7 +1246,7 @@ func upsertMqttModuleConfigPacket(config: ModuleConfig.MQTTConfig, nodeNum: Int6
|
|||
func upsertRangeTestModuleConfigPacket(config: ModuleConfig.RangeTestConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.rangetest.config %@".localized, String(nodeNum))
|
||||
Logger.data.info("⛰️ \(logString)")
|
||||
Logger.data.info("⛰️ \(logString, privacy: .public)")
|
||||
|
||||
let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest()
|
||||
fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum))
|
||||
|
|
@ -1292,7 +1290,7 @@ func upsertRangeTestModuleConfigPacket(config: ModuleConfig.RangeTestConfig, nod
|
|||
func upsertSerialModuleConfigPacket(config: ModuleConfig.SerialConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.serial.config %@".localized, String(nodeNum))
|
||||
Logger.data.info("🤖 \(logString)")
|
||||
Logger.data.info("🤖 \(logString, privacy: .public)")
|
||||
|
||||
let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest()
|
||||
fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum))
|
||||
|
|
@ -1347,7 +1345,7 @@ func upsertSerialModuleConfigPacket(config: ModuleConfig.SerialConfig, nodeNum:
|
|||
func upsertStoreForwardModuleConfigPacket(config: ModuleConfig.StoreForwardConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) {
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.storeforward.config %@".localized, String(nodeNum))
|
||||
Logger.data.info("📬 \(logString)")
|
||||
Logger.data.info("📬 \(logString, privacy: .public)")
|
||||
|
||||
let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest()
|
||||
fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum))
|
||||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -87,7 +87,8 @@
|
|||
"images": [
|
||||
"t-echo.svg"
|
||||
],
|
||||
"requiresDfu": true
|
||||
"requiresDfu": true,
|
||||
"hasInkHud": true
|
||||
},
|
||||
{
|
||||
"hwModel": 8,
|
||||
|
|
@ -153,17 +154,21 @@
|
|||
"images": [
|
||||
"tbeam-s3-core.svg"
|
||||
],
|
||||
"requiresDfu": true
|
||||
"requiresDfu": true,
|
||||
"partitionScheme": "8MB"
|
||||
},
|
||||
{
|
||||
"hwModel": 13,
|
||||
"hwModelSlug": "RAK11200",
|
||||
"platformioTarget": "rak11200",
|
||||
"architecture": "esp32",
|
||||
"activelySupported": false,
|
||||
"activelySupported": true,
|
||||
"displayName": "RAK WisBlock 11200",
|
||||
"tags": [
|
||||
"RAK"
|
||||
],
|
||||
"images": [
|
||||
"rak11200.svg"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -188,7 +193,7 @@
|
|||
"displayName": "LILYGO T-LoRa V2.1-1.8",
|
||||
"tags": [
|
||||
"LilyGo",
|
||||
"2.4G LoRA"
|
||||
"2.4GHz"
|
||||
],
|
||||
"images": [
|
||||
"tlora-v2-1-1_8.svg"
|
||||
|
|
@ -338,7 +343,8 @@
|
|||
"requiresDfu": true,
|
||||
"images": [
|
||||
"station-g2.svg"
|
||||
]
|
||||
],
|
||||
"partitionScheme": "16MB"
|
||||
},
|
||||
{
|
||||
"hwModel": 39,
|
||||
|
|
@ -404,7 +410,8 @@
|
|||
"images": [
|
||||
"heltec-v3.svg",
|
||||
"heltec-v3-case.svg"
|
||||
]
|
||||
],
|
||||
"partitionScheme": "8MB"
|
||||
},
|
||||
{
|
||||
"hwModel": 44,
|
||||
|
|
@ -419,7 +426,8 @@
|
|||
],
|
||||
"images": [
|
||||
"heltec-wsl-v3.svg"
|
||||
]
|
||||
],
|
||||
"partitionScheme": "8MB"
|
||||
},
|
||||
{
|
||||
"hwModel": 47,
|
||||
|
|
@ -469,7 +477,8 @@
|
|||
"images": [
|
||||
"heltec-wireless-tracker.svg"
|
||||
],
|
||||
"requiresDfu": true
|
||||
"requiresDfu": true,
|
||||
"partitionScheme": "8MB"
|
||||
},
|
||||
{
|
||||
"hwModel": 58,
|
||||
|
|
@ -482,7 +491,8 @@
|
|||
"images": [
|
||||
"heltec-wireless-tracker.svg"
|
||||
],
|
||||
"requiresDfu": true
|
||||
"requiresDfu": true,
|
||||
"partitionScheme": "8MB"
|
||||
},
|
||||
{
|
||||
"hwModel": 49,
|
||||
|
|
@ -497,7 +507,9 @@
|
|||
],
|
||||
"images": [
|
||||
"heltec-wireless-paper.svg"
|
||||
]
|
||||
],
|
||||
"hasInkHud": true,
|
||||
"partitionScheme": "8MB"
|
||||
},
|
||||
{
|
||||
"hwModel": 50,
|
||||
|
|
@ -513,7 +525,9 @@
|
|||
"images": [
|
||||
"t-deck.svg"
|
||||
],
|
||||
"requiresDfu": true
|
||||
"requiresDfu": true,
|
||||
"hasMui": true,
|
||||
"partitionScheme": "16MB"
|
||||
},
|
||||
{
|
||||
"hwModel": 51,
|
||||
|
|
@ -528,7 +542,8 @@
|
|||
],
|
||||
"images": [
|
||||
"t-watch-s3.svg"
|
||||
]
|
||||
],
|
||||
"partitionScheme": "16MB"
|
||||
},
|
||||
{
|
||||
"hwModel": 52,
|
||||
|
|
@ -537,7 +552,9 @@
|
|||
"architecture": "esp32-s3",
|
||||
"activelySupported": true,
|
||||
"supportLevel": 3,
|
||||
"displayName": "Pi Computer S3"
|
||||
"displayName": "Pi Computer S3",
|
||||
"hasMui": true,
|
||||
"partitionScheme": "8MB"
|
||||
},
|
||||
{
|
||||
"hwModel": 53,
|
||||
|
|
@ -567,7 +584,8 @@
|
|||
"displayName": "Heltec Wireless Paper V1.0",
|
||||
"images": [
|
||||
"heltec-wireless-paper-v1_0.svg"
|
||||
]
|
||||
],
|
||||
"partitionScheme": "8MB"
|
||||
},
|
||||
{
|
||||
"hwModel": 59,
|
||||
|
|
@ -577,36 +595,41 @@
|
|||
"activelySupported": true,
|
||||
"supportLevel": 3,
|
||||
"displayName": "unPhone",
|
||||
"requiresDfu": true
|
||||
"requiresDfu": true,
|
||||
"hasMui": true,
|
||||
"partitionScheme": "8MB"
|
||||
},
|
||||
{
|
||||
"hwModel": 48,
|
||||
"hwModelSlug": "HELTEC_WIRELESS_TRACKER",
|
||||
"platformioTarget": "tracksenger",
|
||||
"architecture": "esp32-s3",
|
||||
"activelySupported": true,
|
||||
"activelySupported": false,
|
||||
"supportLevel": 3,
|
||||
"displayName": "TrackSenger (small TFT)",
|
||||
"requiresDfu": true
|
||||
"requiresDfu": true,
|
||||
"partitionScheme": "8MB"
|
||||
},
|
||||
{
|
||||
"hwModel": 48,
|
||||
"hwModelSlug": "HELTEC_WIRELESS_TRACKER",
|
||||
"platformioTarget": "tracksenger-lcd",
|
||||
"architecture": "esp32-s3",
|
||||
"activelySupported": true,
|
||||
"activelySupported": false,
|
||||
"supportLevel": 3,
|
||||
"displayName": "TrackSenger (big TFT)",
|
||||
"requiresDfu": true
|
||||
"requiresDfu": true,
|
||||
"partitionScheme": "8MB"
|
||||
},
|
||||
{
|
||||
"hwModel": 48,
|
||||
"hwModelSlug": "HELTEC_WIRELESS_TRACKER",
|
||||
"platformioTarget": "tracksenger-oled",
|
||||
"architecture": "esp32-s3",
|
||||
"activelySupported": true,
|
||||
"activelySupported": false,
|
||||
"supportLevel": 3,
|
||||
"displayName": "TrackSenger (big OLED)"
|
||||
"displayName": "TrackSenger (big OLED)",
|
||||
"partitionScheme": "8MB"
|
||||
},
|
||||
{
|
||||
"hwModel": 61,
|
||||
|
|
@ -647,7 +670,8 @@
|
|||
"images": [
|
||||
"heltec-vision-master-t190.svg"
|
||||
],
|
||||
"requiresDfu": true
|
||||
"requiresDfu": true,
|
||||
"partitionScheme": "8MB"
|
||||
},
|
||||
{
|
||||
"hwModel": 67,
|
||||
|
|
@ -663,7 +687,9 @@
|
|||
"images": [
|
||||
"heltec-vision-master-e213.svg"
|
||||
],
|
||||
"requiresDfu": true
|
||||
"requiresDfu": true,
|
||||
"hasInkHud": true,
|
||||
"partitionScheme": "8MB"
|
||||
},
|
||||
{
|
||||
"hwModel": 68,
|
||||
|
|
@ -679,7 +705,9 @@
|
|||
"images": [
|
||||
"heltec-vision-master-e290.svg"
|
||||
],
|
||||
"requiresDfu": true
|
||||
"requiresDfu": true,
|
||||
"hasInkHud": true,
|
||||
"partitionScheme": "8MB"
|
||||
},
|
||||
{
|
||||
"hwModel": 69,
|
||||
|
|
@ -711,7 +739,9 @@
|
|||
],
|
||||
"images": [
|
||||
"seeed-sensecap-indicator.svg"
|
||||
]
|
||||
],
|
||||
"hasMui": true,
|
||||
"partitionScheme": "8MB"
|
||||
},
|
||||
{
|
||||
"hwModel": 71,
|
||||
|
|
@ -730,8 +760,8 @@
|
|||
"requiresDfu": true
|
||||
},
|
||||
{
|
||||
"hwModel": 72,
|
||||
"hwModelSlug": "Seeed_XIAO_S3",
|
||||
"hwModel": 81,
|
||||
"hwModelSlug": "SEEED_XIAO_S3",
|
||||
"platformioTarget": "seeed-xiao-s3",
|
||||
"architecture": "esp32-s3",
|
||||
"activelySupported": true,
|
||||
|
|
@ -743,14 +773,15 @@
|
|||
"images": [
|
||||
"seeed-xiao-s3.svg"
|
||||
],
|
||||
"requiresDfu": true
|
||||
"requiresDfu": true,
|
||||
"partitionScheme": "8MB"
|
||||
},
|
||||
{
|
||||
"hwModel": 84,
|
||||
"hwModelSlug": "WISMESH_TAP",
|
||||
"platformioTarget": "rak_wismeshtap",
|
||||
"architecture": "nrf52840",
|
||||
"activelySupported": false,
|
||||
"activelySupported": true,
|
||||
"supportLevel": 1,
|
||||
"displayName": "RAK WisMesh Tap",
|
||||
"tags": [
|
||||
|
|
@ -760,5 +791,79 @@
|
|||
"rak-wismeshtap.svg"
|
||||
],
|
||||
"requiresDfu": true
|
||||
},
|
||||
{
|
||||
"hwModel": 22,
|
||||
"hwModelSlug": "WISMESH_HUB",
|
||||
"platformioTarget": "rak2560",
|
||||
"architecture": "nrf52840",
|
||||
"activelySupported": true,
|
||||
"supportLevel": 1,
|
||||
"displayName": "RAK WisMesh Repeater",
|
||||
"tags": [
|
||||
"RAK"
|
||||
],
|
||||
"images": [
|
||||
"rak2560.svg"
|
||||
],
|
||||
"requiresDfu": true
|
||||
},
|
||||
{
|
||||
"hwModel": 63,
|
||||
"hwModelSlug": "NRF52_PROMICRO_DIY",
|
||||
"platformioTarget": "nrf52_promicro_diy_tcxo",
|
||||
"architecture": "nrf52840",
|
||||
"activelySupported": true,
|
||||
"supportLevel": 3,
|
||||
"displayName": "NRF52 Pro-micro DIY",
|
||||
"tags": [
|
||||
"DIY"
|
||||
],
|
||||
"images": [
|
||||
"promicro.svg"
|
||||
],
|
||||
"requiresDfu": true
|
||||
},
|
||||
{
|
||||
"hwModel": 88,
|
||||
"hwModelSlug": "XIAO_NRF52_KIT",
|
||||
"platformioTarget": "seeed_xiao_nrf52840_kit",
|
||||
"architecture": "nrf52840",
|
||||
"activelySupported": true,
|
||||
"supportLevel": 1,
|
||||
"displayName": "Seeed Xiao NRF52840 Kit",
|
||||
"tags": [
|
||||
"Seeed"
|
||||
],
|
||||
"requiresDfu": true,
|
||||
"images": [
|
||||
"seeed_xiao_nrf52_kit.svg"
|
||||
]
|
||||
},
|
||||
{
|
||||
"hwModel": 89,
|
||||
"hwModelSlug": "THINKNODE_M1",
|
||||
"platformioTarget": "thinknode_m1",
|
||||
"architecture": "nrf52840",
|
||||
"activelySupported": false,
|
||||
"supportLevel": 1,
|
||||
"displayName": "ThinkNode M1",
|
||||
"tags": [
|
||||
"Elecrow"
|
||||
],
|
||||
"requiresDfu": true
|
||||
},
|
||||
{
|
||||
"hwModel": 90,
|
||||
"hwModelSlug": "THINKNODE_M2",
|
||||
"platformioTarget": "thinknode_m2",
|
||||
"architecture": "esp32-s3",
|
||||
"activelySupported": false,
|
||||
"supportLevel": 1,
|
||||
"displayName": "ThinkNode M2",
|
||||
"tags": [
|
||||
"Elecrow"
|
||||
],
|
||||
"requiresDfu": false
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ class Router: ObservableObject {
|
|||
} else if components.path.hasPrefix("/settings") {
|
||||
routeSettings(components)
|
||||
} else {
|
||||
Logger.services.warning("Failed to route url: \(url)")
|
||||
Logger.services.warning("Failed to route url: \(url, privacy: .public)")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ struct Connect: View {
|
|||
@State var isUnsetRegion = false
|
||||
@State var invalidFirmwareVersion = false
|
||||
@State var liveActivityStarted = false
|
||||
@State var presentingSwitchPreferredPeripheral = false
|
||||
@State var selectedPeripherialId = ""
|
||||
|
||||
init () {
|
||||
|
|
@ -34,7 +35,7 @@ struct Connect: View {
|
|||
if success {
|
||||
Logger.services.info("Notifications are all set!")
|
||||
} else if let error = error {
|
||||
Logger.services.error("\(error.localizedDescription)")
|
||||
Logger.services.error("\(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -61,9 +62,9 @@ struct Connect: View {
|
|||
.padding(.trailing)
|
||||
VStack(alignment: .leading) {
|
||||
if node != nil {
|
||||
Text(connectedPeripheral.longName).font(.title2)
|
||||
Text(connectedPeripheral.longName.addingVariationSelectors).font(.title2)
|
||||
}
|
||||
Text("BLE Name").font(.callout)+Text(": \(bleManager.connectedPeripheral?.peripheral.name ?? "unknown".localized)")
|
||||
Text("BLE Name").font(.callout)+Text(": \(bleManager.connectedPeripheral?.peripheral.name?.addingVariationSelectors ?? "unknown".localized)")
|
||||
.font(.callout).foregroundColor(Color.gray)
|
||||
if node != nil {
|
||||
Text("firmware.version").font(.callout)+Text(": \(node?.metadata?.firmwareVersion ?? "unknown".localized)")
|
||||
|
|
@ -120,7 +121,7 @@ struct Connect: View {
|
|||
#endif
|
||||
Text("Num: \(String(node!.num))")
|
||||
Text("Short Name: \(node?.user?.shortName ?? "?")")
|
||||
Text("Long Name: \(node?.user?.longName ?? "unknown".localized)")
|
||||
Text("Long Name: \(node?.user?.longName?.addingVariationSelectors ?? "unknown".localized)")
|
||||
Text("BLE RSSI: \(connectedPeripheral.rssi)")
|
||||
|
||||
Button {
|
||||
|
|
@ -214,22 +215,11 @@ struct Connect: View {
|
|||
if let connectedPeripheral = bleManager.connectedPeripheral, connectedPeripheral.peripheral.state == CBPeripheralState.connected {
|
||||
bleManager.disconnectPeripheral()
|
||||
}
|
||||
let container = NSPersistentContainer(name: "Meshtastic")
|
||||
guard let url = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else {
|
||||
Logger.data.error("nil File path for back")
|
||||
return
|
||||
}
|
||||
do {
|
||||
try container.copyPersistentStores(to: url.appendingPathComponent("backup").appendingPathComponent("\(UserDefaults.preferredPeripheralNum)"), overwriting: true)
|
||||
Logger.data.notice("🗂️ Made a core data backup to backup/\(UserDefaults.preferredPeripheralNum)")
|
||||
|
||||
} catch {
|
||||
Logger.data.error("🗂️ Core data backup copy error: \(error, privacy: .public)")
|
||||
}
|
||||
clearCoreDataDatabase(context: context, includeRoutes: false)
|
||||
presentingSwitchPreferredPeripheral = true
|
||||
selectedPeripherialId = peripheral.peripheral.identifier.uuidString
|
||||
} else {
|
||||
self.bleManager.connectTo(peripheral: peripheral.peripheral)
|
||||
}
|
||||
UserDefaults.preferredPeripheralId = selectedPeripherialId
|
||||
self.bleManager.connectTo(peripheral: peripheral.peripheral)
|
||||
}) {
|
||||
Text(peripheral.name).font(.callout)
|
||||
}
|
||||
|
|
@ -240,6 +230,21 @@ struct Connect: View {
|
|||
}.padding([.bottom, .top])
|
||||
}
|
||||
}
|
||||
.confirmationDialog("Connecting to a new radio will clear all app data on the phone.", isPresented: $presentingSwitchPreferredPeripheral, titleVisibility: .visible) {
|
||||
Button("Connect to new radio?", role: .destructive) {
|
||||
UserDefaults.preferredPeripheralId = selectedPeripherialId
|
||||
UserDefaults.preferredPeripheralNum = 0
|
||||
if bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral.peripheral.state == CBPeripheralState.connected {
|
||||
bleManager.disconnectPeripheral()
|
||||
}
|
||||
clearCoreDataDatabase(context: context, includeRoutes: false)
|
||||
let radio = bleManager.peripherals.first(where: { $0.peripheral.identifier.uuidString == selectedPeripherialId })
|
||||
if radio != nil {
|
||||
bleManager.connectTo(peripheral: radio!.peripheral)
|
||||
}
|
||||
}
|
||||
}
|
||||
.textCase(nil)
|
||||
}
|
||||
|
||||
} else {
|
||||
|
|
@ -319,7 +324,7 @@ struct Connect: View {
|
|||
isUnsetRegion = false
|
||||
}
|
||||
} catch {
|
||||
Logger.data.error("💥 Error fetching node info: \(error.localizedDescription)")
|
||||
Logger.data.error("💥 Error fetching node info: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -333,7 +338,7 @@ struct Connect: View {
|
|||
let localStats = node?.telemetries?.filtered(using: NSPredicate(format: "metricsType == 4"))
|
||||
let mostRecent = localStats?.lastObject as? TelemetryEntity
|
||||
|
||||
let activityAttributes = MeshActivityAttributes(nodeNum: Int(node?.num ?? 0), name: node?.user?.longName ?? "unknown")
|
||||
let activityAttributes = MeshActivityAttributes(nodeNum: Int(node?.num ?? 0), name: node?.user?.longName?.addingVariationSelectors ?? "unknown")
|
||||
|
||||
let future = Date(timeIntervalSinceNow: Double(timerSeconds))
|
||||
let initialContentState = MeshActivityAttributes.ContentState(uptimeSeconds: UInt32(mostRecent?.uptimeSeconds ?? 0),
|
||||
|
|
@ -356,7 +361,7 @@ struct Connect: View {
|
|||
pushType: nil)
|
||||
Logger.services.info("Requested MyActivity live activity. ID: \(myActivity.id)")
|
||||
} catch {
|
||||
Logger.services.error("Error requesting live activity: \(error.localizedDescription)")
|
||||
Logger.services.error("Error requesting live activity: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -53,6 +53,5 @@ struct ContentView: View {
|
|||
}
|
||||
.tag(NavigationState.Tab.settings)
|
||||
}
|
||||
.toolbarBackground(.visible, for: .tabBar)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ struct CircleText: View {
|
|||
Circle()
|
||||
.fill(color)
|
||||
.frame(width: circleSize, height: circleSize)
|
||||
Text(text)
|
||||
Text(text.addingVariationSelectors)
|
||||
.frame(width: circleSize * 0.9, height: circleSize * 0.9, alignment: .center)
|
||||
.foregroundColor(color.isLight() ? .black : .white)
|
||||
.minimumScaleFactor(0.001)
|
||||
|
|
|
|||
47
Meshtastic/Views/Helpers/Compact Widgets/CompactWidget.swift
Normal file
47
Meshtastic/Views/Helpers/Compact Widgets/CompactWidget.swift
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
//
|
||||
// CompactWidget.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Created by Jake Bordens on 3/14/25.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
// This file was created for the purpose of previewing
|
||||
// all of the Compact Widgets in one place.
|
||||
|
||||
// In the future, it could be used for a CompactWidget superclass, if desired.
|
||||
|
||||
#Preview {
|
||||
|
||||
let gridItemLayout = Array(repeating: GridItem(.flexible(), spacing: 10), count: 2)
|
||||
Form {
|
||||
LazyVGrid(columns: gridItemLayout) {
|
||||
HumidityCompactWidget(humidity: 27, dewPoint: "32°")
|
||||
HumidityCompactWidget(humidity: 27, dewPoint: nil)
|
||||
WeatherConditionsCompactWidget(temperature: "24°F", symbolName: "sun.rain.fill", description: "Raining")
|
||||
PressureCompactWidget(pressure: "1004.76", unit: "hPA", low: true)
|
||||
PressureCompactWidget(pressure: "1004.76", unit: "hPA", low: false)
|
||||
WindCompactWidget(speed: "12 mph", gust: "15 mph", direction: "SW")
|
||||
WindCompactWidget(speed: "12 mph", gust: nil, direction: "SW")
|
||||
WindCompactWidget(speed: "12 mph", gust: "15 mph", direction: nil)
|
||||
WindCompactWidget(speed: "12 mph", gust: nil, direction: nil)
|
||||
RadiationCompactWidget(radiation: "15", unit: "µR/hr")
|
||||
DistanceCompactWidget(distance: "123", unit: "mm")
|
||||
WeightCompactWidget(weight: "123", unit: "kg")
|
||||
SoilTemperatureCompactWidget(temperature: "23", unit: "°C")
|
||||
SoilMoistureCompactWidget(moisture: "23", unit: "%")
|
||||
|
||||
let rain: Float = 10.1
|
||||
let locale = NSLocale.current as NSLocale
|
||||
let usesMetricSystem = locale.usesMetricSystem // Returns true for metric (mm), false for imperial (inches)
|
||||
let unit = usesMetricSystem ? UnitLength.millimeters : UnitLength.inches
|
||||
let unitLabel = usesMetricSystem ? "mm" : "in"
|
||||
let measurement = Measurement(value: Double(rain), unit: UnitLength.millimeters)
|
||||
let decimals = usesMetricSystem ? 0 : 1
|
||||
let formattedRain = measurement.converted(to: unit).value.formatted(.number.precision(.fractionLength(decimals)))
|
||||
RainfallCompactWidget(timespan: .rainfall1H, rainfall: formattedRain, unit: unitLabel)
|
||||
RainfallCompactWidget(timespan: .rainfall24H, rainfall: formattedRain, unit: unitLabel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -17,6 +17,7 @@ struct CurrentConditionsCompact: View {
|
|||
.symbolRenderingMode(.multicolor)
|
||||
}
|
||||
}
|
||||
|
||||
struct CurrentConditionsCompact_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
//
|
||||
// DistanceCompactWidget.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Created by Jake Bordens on 3/14/25.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct DistanceCompactWidget: View {
|
||||
let distance: String
|
||||
let unit: String
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
HStack(alignment: .firstTextBaseline) {
|
||||
Image(systemName: "ruler")
|
||||
.imageScale(.small)
|
||||
.foregroundColor(.accentColor)
|
||||
Text("Distance")
|
||||
.textCase(.uppercase)
|
||||
.font(.callout)
|
||||
}
|
||||
HStack {
|
||||
Text("\(distance)")
|
||||
.font(distance.length < 4 ? .system(size: 50) : .system(size: 40) )
|
||||
Text(unit)
|
||||
.font(.system(size: 14))
|
||||
}
|
||||
}
|
||||
.frame(minWidth: 100, idealWidth: 125, maxWidth: 150, minHeight: 120, idealHeight: 130, maxHeight: 140)
|
||||
.padding()
|
||||
.background(.tertiary, in: RoundedRectangle(cornerRadius: 20, style: .continuous))
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
DistanceCompactWidget(distance: "123", unit: "mm")
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
//
|
||||
// HumidityCompactWidget.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Created by Jake Bordens on 3/14/25.
|
||||
//
|
||||
import SwiftUI
|
||||
|
||||
struct HumidityCompactWidget: View {
|
||||
let humidity: Int
|
||||
let dewPoint: String?
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
HStack(spacing: 5.0) {
|
||||
Image(systemName: "humidity")
|
||||
.foregroundColor(.accentColor)
|
||||
.font(.callout)
|
||||
Text("Humidity")
|
||||
.textCase(.uppercase)
|
||||
.font(.caption)
|
||||
}
|
||||
Text("\(humidity)%")
|
||||
.font(.largeTitle)
|
||||
.padding(.bottom, 5)
|
||||
if let dewPoint {
|
||||
Text("The dew point is \(dewPoint) right now.")
|
||||
.lineLimit(3)
|
||||
.allowsTightening(true)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.font(.caption2)
|
||||
}
|
||||
}
|
||||
.frame(minWidth: 100, idealWidth: 125, maxWidth: 150, minHeight: 120, idealHeight: 130, maxHeight: 140)
|
||||
.padding()
|
||||
.background(.tertiary, in: RoundedRectangle(cornerRadius: 20, style: .continuous))
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
let gridItemLayout = Array(repeating: GridItem(.flexible(), spacing: 10), count: 2)
|
||||
Form {
|
||||
LazyVGrid(columns: gridItemLayout) {
|
||||
HumidityCompactWidget(humidity: 27, dewPoint: "32°")
|
||||
HumidityCompactWidget(humidity: 27, dewPoint: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
//
|
||||
// PressureCompactWidget.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Created by Jake Bordens on 3/14/25.
|
||||
//
|
||||
import SwiftUI
|
||||
|
||||
struct PressureCompactWidget: View {
|
||||
let pressure: String
|
||||
let unit: String
|
||||
let low: Bool
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
HStack(spacing: 5.0) {
|
||||
Image(systemName: "gauge")
|
||||
.foregroundColor(.accentColor)
|
||||
.font(.callout)
|
||||
Text("Pressure")
|
||||
.textCase(.uppercase)
|
||||
.font(.caption)
|
||||
}
|
||||
Text(pressure)
|
||||
.font(pressure.length < 7 ? .system(size: 35) : .system(size: 30) )
|
||||
Text(low ? "LOW" : "HIGH")
|
||||
.padding(.bottom, 10)
|
||||
Text(unit)
|
||||
}
|
||||
.frame(minWidth: 100, idealWidth: 125, maxWidth: 150, minHeight: 120, idealHeight: 130, maxHeight: 140)
|
||||
.padding()
|
||||
.background(.tertiary, in: RoundedRectangle(cornerRadius: 20, style: .continuous))
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
let gridItemLayout = Array(repeating: GridItem(.flexible(), spacing: 10), count: 2)
|
||||
Form {
|
||||
LazyVGrid(columns: gridItemLayout) {
|
||||
PressureCompactWidget(pressure: "1004.76", unit: "hPA", low: true)
|
||||
PressureCompactWidget(pressure: "1004.76", unit: "hPA", low: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
//
|
||||
// RadiationCompactWidget.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Created by Jake Bordens on 3/14/25.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct RadiationCompactWidget: View {
|
||||
let radiation: String
|
||||
let unit: String
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
HStack(alignment: .firstTextBaseline) {
|
||||
Text(verbatim: "☢")
|
||||
.font(.system(size: 30, design: .monospaced))
|
||||
.foregroundColor(.accentColor)
|
||||
Text("Radiation")
|
||||
.textCase(.uppercase)
|
||||
.font(.callout)
|
||||
}
|
||||
HStack {
|
||||
Text("\(radiation)")
|
||||
.font(radiation.length < 4 ? .system(size: 50) : .system(size: 34) )
|
||||
Text(unit)
|
||||
.font(.system(size: 14))
|
||||
}
|
||||
}
|
||||
.frame(minWidth: 100, idealWidth: 125, maxWidth: 150, minHeight: 120, idealHeight: 130, maxHeight: 140)
|
||||
.padding()
|
||||
.background(.tertiary, in: RoundedRectangle(cornerRadius: 20, style: .continuous))
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
RadiationCompactWidget(radiation: "15", unit: "µR/hr")
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
//
|
||||
// RainfallCompactWidgets.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Created by Jake Bordens on 3/15/25.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct RainfallCompactWidget: View {
|
||||
enum RainfallTimeSpan: String {
|
||||
case rainfall1H = "Rainfall 1H"
|
||||
case rainfall24H = "Rainfall 24H"
|
||||
}
|
||||
|
||||
let timespan: RainfallTimeSpan
|
||||
let rainfall: String
|
||||
let unit: String
|
||||
|
||||
private var icon: Image {
|
||||
if timespan == .rainfall1H {
|
||||
return Image(systemName: "cloud.rain.fill")
|
||||
}
|
||||
return Image(systemName: "cloud.heavyrain.fill")
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
HStack(alignment: .firstTextBaseline) {
|
||||
icon.imageScale(.small)
|
||||
.foregroundColor(.accentColor)
|
||||
Text(timespan.rawValue)
|
||||
.textCase(.uppercase)
|
||||
.font(.callout)
|
||||
}
|
||||
HStack {
|
||||
Text("\(rainfall)")
|
||||
.font(rainfall.length < 4 ? .system(size: 50) : .system(size: 40) )
|
||||
Text(unit)
|
||||
.font(.system(size: 14))
|
||||
}
|
||||
}
|
||||
.frame(minWidth: 100, idealWidth: 125, maxWidth: 150, minHeight: 120, idealHeight: 130, maxHeight: 140)
|
||||
.padding()
|
||||
.background(.tertiary, in: RoundedRectangle(cornerRadius: 20, style: .continuous))
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
let gridItemLayout = Array(repeating: GridItem(.flexible(), spacing: 10), count: 2)
|
||||
Form {
|
||||
LazyVGrid(columns: gridItemLayout) {
|
||||
let rain: Float = 10.1
|
||||
let locale = NSLocale.current as NSLocale
|
||||
let usesMetricSystem = locale.usesMetricSystem // Returns true for metric (mm), false for imperial (inches)
|
||||
let unit = usesMetricSystem ? UnitLength.millimeters : UnitLength.inches
|
||||
let unitLabel = usesMetricSystem ? "mm" : "in"
|
||||
let measurement = Measurement(value: Double(rain), unit: UnitLength.millimeters)
|
||||
let decimals = usesMetricSystem ? 0 : 1
|
||||
let formattedRain = measurement.converted(to: unit).value.formatted(.number.precision(.fractionLength(decimals)))
|
||||
|
||||
RainfallCompactWidget(timespan: .rainfall1H, rainfall: formattedRain, unit: unitLabel)
|
||||
RainfallCompactWidget(timespan: .rainfall24H, rainfall: formattedRain, unit: unitLabel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
//
|
||||
// SoilCompactWidgets.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Created by Jake Bordens on 3/14/25.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct SoilTemperatureCompactWidget: View {
|
||||
let temperature: String
|
||||
let unit: String
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
HStack(alignment: .firstTextBaseline) {
|
||||
Image("soil.temperature")
|
||||
.imageScale(.small)
|
||||
.foregroundColor(.accentColor)
|
||||
Text("Soil Temp")
|
||||
.textCase(.uppercase)
|
||||
.font(.callout)
|
||||
}
|
||||
HStack {
|
||||
Text("\(temperature)")
|
||||
.font(temperature.length < 4 ? .system(size: 50) : .system(size: 40) )
|
||||
Text(unit)
|
||||
.font(.system(size: 14))
|
||||
}
|
||||
}
|
||||
.frame(minWidth: 100, idealWidth: 125, maxWidth: 150, minHeight: 120, idealHeight: 130, maxHeight: 140)
|
||||
.padding()
|
||||
.background(.tertiary, in: RoundedRectangle(cornerRadius: 20, style: .continuous))
|
||||
}
|
||||
}
|
||||
|
||||
struct SoilMoistureCompactWidget: View {
|
||||
let moisture: String
|
||||
let unit: String
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
HStack(alignment: .firstTextBaseline) {
|
||||
Image("soil.moisture")
|
||||
.imageScale(.small)
|
||||
.foregroundColor(.accentColor)
|
||||
Text("Soil Moisture")
|
||||
.textCase(.uppercase)
|
||||
.font(.callout)
|
||||
}
|
||||
HStack {
|
||||
Text("\(moisture)")
|
||||
.font(moisture.length < 4 ? .system(size: 50) : .system(size: 40) )
|
||||
Text(unit)
|
||||
.font(.system(size: 14))
|
||||
}
|
||||
}
|
||||
.frame(minWidth: 100, idealWidth: 125, maxWidth: 150, minHeight: 120, idealHeight: 130, maxHeight: 140)
|
||||
.padding()
|
||||
.background(.tertiary, in: RoundedRectangle(cornerRadius: 20, style: .continuous))
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
let gridItemLayout = Array(repeating: GridItem(.flexible(), spacing: 10), count: 2)
|
||||
Form {
|
||||
LazyVGrid(columns: gridItemLayout) {
|
||||
SoilTemperatureCompactWidget(temperature: "23", unit: "°C")
|
||||
SoilMoistureCompactWidget(moisture: "23", unit: "%")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
//
|
||||
// WeatherConditionsCompactWidget.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Created by Jake Bordens on 3/14/25.
|
||||
//
|
||||
import SwiftUI
|
||||
|
||||
struct WeatherConditionsCompactWidget: View {
|
||||
let temperature: String
|
||||
let symbolName: String
|
||||
let description: String
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
HStack(spacing: 5.0) {
|
||||
Image(systemName: symbolName)
|
||||
.foregroundColor(.accentColor)
|
||||
.font(.callout)
|
||||
Text(description)
|
||||
.lineLimit(2)
|
||||
.allowsTightening(/*@START_MENU_TOKEN@*/true/*@END_MENU_TOKEN@*/)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.font(.caption)
|
||||
}
|
||||
Text(temperature)
|
||||
.font(temperature.length < 4 ? .system(size: 72) : .system(size: 54) )
|
||||
}
|
||||
.frame(minWidth: 100, idealWidth: 125, maxWidth: 150, minHeight: 120, idealHeight: 130, maxHeight: 140)
|
||||
.padding()
|
||||
.background(.tertiary, in: RoundedRectangle(cornerRadius: 20, style: .continuous))
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
let gridItemLayout = Array(repeating: GridItem(.flexible(), spacing: 10), count: 2)
|
||||
Form {
|
||||
LazyVGrid(columns: gridItemLayout) {
|
||||
WeatherConditionsCompactWidget(temperature: "24°F", symbolName: "sun.rain.fill", description: "Raining")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
//
|
||||
// WeightCompactWidget.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Created by Jake Bordens on 3/14/25.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct WeightCompactWidget: View {
|
||||
let weight: String
|
||||
let unit: String
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
HStack(alignment: .firstTextBaseline) {
|
||||
Image(systemName: "scalemass")
|
||||
.imageScale(.small)
|
||||
.foregroundColor(.accentColor)
|
||||
Text("Weight")
|
||||
.textCase(.uppercase)
|
||||
.font(.callout)
|
||||
}
|
||||
HStack {
|
||||
Text("\(weight)")
|
||||
.font(weight.length < 4 ? .system(size: 50) : .system(size: 40) )
|
||||
Text(unit)
|
||||
.font(.system(size: 14))
|
||||
}
|
||||
}
|
||||
.frame(minWidth: 100, idealWidth: 125, maxWidth: 150, minHeight: 120, idealHeight: 130, maxHeight: 140)
|
||||
.padding()
|
||||
.background(.tertiary, in: RoundedRectangle(cornerRadius: 20, style: .continuous))
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
WeightCompactWidget(weight: "123", unit: "kg")
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
//
|
||||
// WindCompactWidget.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Created by Jake Bordens on 3/14/25.
|
||||
//
|
||||
import SwiftUI
|
||||
|
||||
struct WindCompactWidget: View {
|
||||
let speed: String
|
||||
let gust: String?
|
||||
let direction: String?
|
||||
|
||||
var body: some View {
|
||||
let hasGust = ((gust ?? "").isEmpty == false)
|
||||
VStack(alignment: .leading) {
|
||||
Label { Text("Wind").textCase(.uppercase) } icon: { Image(systemName: "wind").foregroundColor(.accentColor) }
|
||||
if let direction {
|
||||
Text("\(direction)")
|
||||
.font(!hasGust ? .callout : .caption)
|
||||
.padding(.bottom, 10)
|
||||
}
|
||||
Text(speed)
|
||||
.font(.system(size: 35))
|
||||
if let gust, !gust.isEmpty {
|
||||
Text("Gusts \(gust)")
|
||||
}
|
||||
}
|
||||
.frame(minWidth: 100, idealWidth: 125, maxWidth: 150, minHeight: 120, idealHeight: 130, maxHeight: 140)
|
||||
.padding()
|
||||
.background(.tertiary, in: RoundedRectangle(cornerRadius: 20, style: .continuous))
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
let gridItemLayout = Array(repeating: GridItem(.flexible(), spacing: 10), count: 2)
|
||||
Form {
|
||||
LazyVGrid(columns: gridItemLayout) {
|
||||
WindCompactWidget(speed: "12 mph", gust: "15 mph", direction: "SW")
|
||||
WindCompactWidget(speed: "12 mph", gust: nil, direction: "SW")
|
||||
WindCompactWidget(speed: "12 mph", gust: "15 mph", direction: nil)
|
||||
WindCompactWidget(speed: "12 mph", gust: nil, direction: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -28,7 +28,7 @@ struct ConnectedDevice: View {
|
|||
.imageScale(.large)
|
||||
.foregroundColor(.green)
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text(name).font(name.isEmoji() ? .title : .callout).foregroundColor(.gray)
|
||||
Text(name.addingVariationSelectors).font(name.isEmoji() ? .title : .callout).foregroundColor(.gray)
|
||||
} else {
|
||||
Image(systemName: "antenna.radiowaves.left.and.right.slash")
|
||||
.imageScale(.medium)
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ struct SecureInput: View {
|
|||
}) {
|
||||
Image(systemName: self.isSecure ? "eye.slash" : "eye")
|
||||
.accentColor(.secondary)
|
||||
}
|
||||
}.buttonStyle(BorderlessButtonStyle())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ struct LocalWeatherConditions: View {
|
|||
attributionLogo = colorScheme == .light ? attribution.combinedMarkLightURL : attribution.combinedMarkDarkURL
|
||||
}
|
||||
} catch {
|
||||
Logger.services.error("Could not gather weather information: \(error.localizedDescription)")
|
||||
Logger.services.error("Could not gather weather information: \(error.localizedDescription, privacy: .public)")
|
||||
condition = .clear
|
||||
symbolName = "cloud.fill"
|
||||
}
|
||||
|
|
@ -89,113 +89,6 @@ struct LocalWeatherConditions: View {
|
|||
}
|
||||
}
|
||||
|
||||
struct WeatherConditionsCompactWidget: View {
|
||||
let temperature: String
|
||||
let symbolName: String
|
||||
let description: String
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
HStack(spacing: 5.0) {
|
||||
Image(systemName: symbolName)
|
||||
.foregroundColor(.accentColor)
|
||||
.font(.callout)
|
||||
Text(description)
|
||||
.lineLimit(2)
|
||||
.allowsTightening(/*@START_MENU_TOKEN@*/true/*@END_MENU_TOKEN@*/)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.font(.caption)
|
||||
}
|
||||
Text(temperature)
|
||||
.font(temperature.length < 4 ? .system(size: 72) : .system(size: 54) )
|
||||
}
|
||||
.frame(minWidth: 100, idealWidth: 125, maxWidth: 150, minHeight: 120, idealHeight: 130, maxHeight: 140)
|
||||
.padding()
|
||||
.background(.tertiary, in: RoundedRectangle(cornerRadius: 20, style: .continuous))
|
||||
}
|
||||
}
|
||||
|
||||
struct HumidityCompactWidget: View {
|
||||
let humidity: Int
|
||||
let dewPoint: String?
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
HStack(spacing: 5.0) {
|
||||
Image(systemName: "humidity")
|
||||
.foregroundColor(.accentColor)
|
||||
.font(.callout)
|
||||
Text("Humidity")
|
||||
.textCase(.uppercase)
|
||||
.font(.caption)
|
||||
}
|
||||
Text("\(humidity)%")
|
||||
.font(.largeTitle)
|
||||
.padding(.bottom, 5)
|
||||
if let dewPoint {
|
||||
Text("The dew point is \(dewPoint) right now.")
|
||||
.lineLimit(3)
|
||||
.allowsTightening(true)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.font(.caption2)
|
||||
}
|
||||
}
|
||||
.frame(minWidth: 100, idealWidth: 125, maxWidth: 150, minHeight: 120, idealHeight: 130, maxHeight: 140)
|
||||
.padding()
|
||||
.background(.tertiary, in: RoundedRectangle(cornerRadius: 20, style: .continuous))
|
||||
}
|
||||
}
|
||||
|
||||
struct PressureCompactWidget: View {
|
||||
let pressure: String
|
||||
let unit: String
|
||||
let low: Bool
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
HStack(spacing: 5.0) {
|
||||
Image(systemName: "gauge")
|
||||
.foregroundColor(.accentColor)
|
||||
.font(.callout)
|
||||
Text("Pressure")
|
||||
.textCase(.uppercase)
|
||||
.font(.caption)
|
||||
}
|
||||
Text(pressure)
|
||||
.font(pressure.length < 7 ? .system(size: 35) : .system(size: 30) )
|
||||
Text(low ? "LOW" : "HIGH")
|
||||
.padding(.bottom, 10)
|
||||
Text(unit)
|
||||
}
|
||||
.frame(minWidth: 100, idealWidth: 125, maxWidth: 150, minHeight: 120, idealHeight: 130, maxHeight: 140)
|
||||
.padding()
|
||||
.background(.tertiary, in: RoundedRectangle(cornerRadius: 20, style: .continuous))
|
||||
}
|
||||
}
|
||||
|
||||
struct WindCompactWidget: View {
|
||||
let speed: String
|
||||
let gust: String?
|
||||
let direction: String?
|
||||
|
||||
var body: some View {
|
||||
let hasGust = ((gust ?? "").isEmpty == false)
|
||||
VStack(alignment: .leading) {
|
||||
Label { Text("Wind").textCase(.uppercase) } icon: { Image(systemName: "wind").foregroundColor(.accentColor) }
|
||||
if let direction {
|
||||
Text("\(direction)")
|
||||
.font(!hasGust ? .callout : .caption)
|
||||
.padding(.bottom, 10)
|
||||
}
|
||||
Text(speed)
|
||||
.font(!hasGust ? .system(size: 45) : .system(size: 35))
|
||||
if let gust, !gust.isEmpty {
|
||||
Text("Gusts \(gust)")
|
||||
}
|
||||
}
|
||||
.frame(minWidth: 100, idealWidth: 125, maxWidth: 150, minHeight: 120, idealHeight: 130, maxHeight: 140)
|
||||
.padding()
|
||||
.background(.tertiary, in: RoundedRectangle(cornerRadius: 20, style: .continuous))
|
||||
}
|
||||
}
|
||||
|
||||
/// Magnus Formula
|
||||
func calculateDewPoint(temp: Float, relativeHumidity: Float) -> Double {
|
||||
let a: Float = 17.27
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ struct NodeWeatherForecastView: View {
|
|||
)
|
||||
})
|
||||
} catch {
|
||||
Logger.services.error("Could not load weather: \(error.localizedDescription)")
|
||||
Logger.services.error("Could not load weather: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,64 +0,0 @@
|
|||
//
|
||||
//// MapButtons.swift
|
||||
//// Meshtastic
|
||||
////
|
||||
//// Copyright © Garth Vander Houwen 4/23/23.
|
||||
////
|
||||
//
|
||||
//import SwiftUI
|
||||
//
|
||||
//struct MapButtons: View {
|
||||
// let buttonWidth: CGFloat = 22
|
||||
// let width: CGFloat = 45
|
||||
// @Binding var tracking: UserTrackingModes
|
||||
// @Binding var isPresentingInfoSheet: Bool
|
||||
// var body: some View {
|
||||
// VStack {
|
||||
// let impactLight = UIImpactFeedbackGenerator(style: .light)
|
||||
// Button(action: {
|
||||
// self.isPresentingInfoSheet.toggle()
|
||||
// }) {
|
||||
// Image(systemName: isPresentingInfoSheet ? "info.circle.fill" : "info.circle")
|
||||
// .resizable()
|
||||
// .frame(width: buttonWidth, height: buttonWidth, alignment: .center)
|
||||
// .offset(y: -2)
|
||||
// }
|
||||
// Divider()
|
||||
// Button(action: {
|
||||
// switch self.tracking {
|
||||
// case .none:
|
||||
// self.tracking = .follow
|
||||
// case .follow:
|
||||
// self.tracking = .followWithHeading
|
||||
// case .followWithHeading:
|
||||
// self.tracking = .none
|
||||
// }
|
||||
// impactLight.impactOccurred()
|
||||
// }) {
|
||||
// Image(systemName: tracking.icon)
|
||||
// .frame(width: buttonWidth, height: buttonWidth, alignment: .center)
|
||||
// .offset(y: 3)
|
||||
// }
|
||||
// }
|
||||
// .frame(width: width, height: width*2, alignment: .center)
|
||||
// .background(Color(UIColor.systemBackground))
|
||||
// .cornerRadius(8)
|
||||
// .shadow(radius: 1)
|
||||
// .offset(x: 3, y: 25)
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//// MARK: Previews
|
||||
//// struct MapControl_Previews: PreviewProvider {
|
||||
//// @State static var tracking: UserTrackingModes = .none
|
||||
//// @State static var isPresentingInfoSheet = false
|
||||
//// static var previews: some View {
|
||||
//// Group {
|
||||
//// MapButtons(tracking: $tracking, isPresentingInfoSheet: $isPresentingInfoSheet)
|
||||
//// .environment(\.colorScheme, .light)
|
||||
//// MapButtons(tracking: $tracking, isPresentingInfoSheet: $isPresentingInfoSheet)
|
||||
//// .environment(\.colorScheme, .dark)
|
||||
//// }
|
||||
//// .previewLayout(.fixed(width: 60, height: 100))
|
||||
//// }
|
||||
//// }
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
//
|
||||
// MapViewFitExtension.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Created by Garth Vander Houwen on 1/15/23.
|
||||
//
|
||||
|
||||
import MapKit
|
||||
|
||||
extension MKMapView {
|
||||
|
||||
func fitAllAnnotations(with padding: UIEdgeInsets = UIEdgeInsets(top: 100, left: 100, bottom: 100, right: 100)) {
|
||||
var zoomRect: MKMapRect = .null
|
||||
annotations.forEach({
|
||||
let annotationPoint = MKMapPoint($0.coordinate)
|
||||
let pointRect = MKMapRect(x: annotationPoint.x, y: annotationPoint.y, width: 0.01, height: 0.01)
|
||||
zoomRect = zoomRect.union(pointRect)
|
||||
})
|
||||
|
||||
setVisibleMapRect(zoomRect, edgePadding: padding, animated: true)
|
||||
}
|
||||
|
||||
func fit(annotations: [MKAnnotation], andShow show: Bool, with padding: UIEdgeInsets = UIEdgeInsets(top: 100, left: 100, bottom: 100, right: 100)) {
|
||||
var zoomRect: MKMapRect = .null
|
||||
annotations.forEach({
|
||||
let aPoint = MKMapPoint($0.coordinate)
|
||||
let rect = MKMapRect(x: aPoint.x, y: aPoint.y, width: 0.1, height: 0.1)
|
||||
zoomRect = zoomRect.isNull ? rect : zoomRect.union(rect)
|
||||
})
|
||||
|
||||
if show {
|
||||
addAnnotations(annotations)
|
||||
}
|
||||
|
||||
setVisibleMapRect(zoomRect, edgePadding: padding, animated: true)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,434 +0,0 @@
|
|||
////
|
||||
//// MapViewSwitUI.swift
|
||||
//// Meshtastic
|
||||
////
|
||||
//// Copyright(c) Josh Pirihi & Garth Vander Houwen 1/16/22.
|
||||
//
|
||||
//import Foundation
|
||||
//import SwiftUI
|
||||
//import MapKit
|
||||
//import OSLog
|
||||
//
|
||||
//struct PolygonInfo: Codable {
|
||||
// let stroke: String?
|
||||
// let strokeWidth, strokeOpacity: Int?
|
||||
// let fill: String?
|
||||
// let fillOpacity: Double?
|
||||
// let title, subtitle: String?
|
||||
//}
|
||||
//
|
||||
//func degreesToRadians(_ number: Double) -> Double {
|
||||
// return number * .pi / 180
|
||||
//}
|
||||
//var currentMapLayer: MapLayer?
|
||||
//
|
||||
//struct MapViewSwiftUI: UIViewRepresentable {
|
||||
// var onLongPress: (_ waypointCoordinate: CLLocationCoordinate2D) -> Void
|
||||
// var onWaypointEdit: (_ waypointId: Int ) -> Void
|
||||
// let mapView = MKMapView()
|
||||
// // Parameters
|
||||
// let selectedMapLayer: MapLayer
|
||||
// let selectedWeatherLayer: MapOverlayServer = UserDefaults.mapOverlayServer
|
||||
// let positions: [PositionEntity]
|
||||
// let waypoints: [WaypointEntity]
|
||||
// let userTrackingMode: MKUserTrackingMode
|
||||
// let showNodeHistory: Bool
|
||||
// let showRouteLines: Bool
|
||||
// let mapViewType: MKMapType = MKMapType.standard
|
||||
// // Offline Map Tiles
|
||||
// @AppStorage("lastUpdatedLocalMapFile") private var lastUpdatedLocalMapFile = 0
|
||||
// @State private var loadedLastUpdatedLocalMapFile = 0
|
||||
// var customMapOverlay: CustomMapOverlay?
|
||||
// @State private var presentCustomMapOverlayHash: CustomMapOverlay?
|
||||
// // MARK: Private methods
|
||||
// private func configureMap(mapView: MKMapView) {
|
||||
// // Map View Parameters
|
||||
// mapView.mapType = mapViewType
|
||||
// mapView.addAnnotations(waypoints)
|
||||
// // Do the initial map centering
|
||||
// let latest = positions
|
||||
// .filter { $0.latest == true }
|
||||
// .sorted { $0.nodePosition?.num ?? 0 > $1.nodePosition?.num ?? -1 }
|
||||
// let span = MKCoordinateSpan(latitudeDelta: 0.003, longitudeDelta: 0.003)
|
||||
// let center = (latest.count > 0 && userTrackingMode == MKUserTrackingMode.none) ? latest[0].coordinate : LocationHelper.currentLocation
|
||||
// let region = MKCoordinateRegion(center: center, span: span)
|
||||
// mapView.addAnnotations(showNodeHistory ? positions : latest)
|
||||
// mapView.setRegion(region, animated: true)
|
||||
// // Set user (phone gps) tracking options
|
||||
// mapView.setUserTrackingMode(userTrackingMode, animated: true)
|
||||
// if userTrackingMode == MKUserTrackingMode.none {
|
||||
// if latest.count == 1 {
|
||||
// mapView.fit(annotations: showNodeHistory ? positions: latest, andShow: false)
|
||||
// } else {
|
||||
// mapView.fitAllAnnotations()
|
||||
// }
|
||||
// mapView.showsUserLocation = false
|
||||
// } else {
|
||||
// mapView.showsUserLocation = true
|
||||
// }
|
||||
// // Other MKMapView Settings
|
||||
// mapView.preferredConfiguration.elevationStyle = .realistic// .flat
|
||||
// mapView.pointOfInterestFilter = MKPointOfInterestFilter.excludingAll
|
||||
// mapView.isPitchEnabled = true
|
||||
// mapView.isRotateEnabled = true
|
||||
// mapView.isScrollEnabled = true
|
||||
// mapView.isZoomEnabled = true
|
||||
// mapView.showsBuildings = true
|
||||
// mapView.showsScale = true
|
||||
// mapView.showsTraffic = true
|
||||
//
|
||||
// mapView.showsCompass = false
|
||||
// let compass = MKCompassButton(mapView: mapView)
|
||||
// compass.translatesAutoresizingMaskIntoConstraints = false
|
||||
// #if targetEnvironment(macCatalyst)
|
||||
// // Show the default always visible compass and the mac only controls
|
||||
// compass.compassVisibility = .visible
|
||||
// mapView.addSubview(compass)
|
||||
// mapView.showsZoomControls = true
|
||||
// mapView.showsPitchControl = true
|
||||
// compass.trailingAnchor.constraint(equalTo: mapView.trailingAnchor, constant: -115).isActive = true
|
||||
// compass.bottomAnchor.constraint(equalTo: mapView.bottomAnchor, constant: -5).isActive = true
|
||||
// #else
|
||||
// compass.compassVisibility = .adaptive
|
||||
// mapView.addSubview(compass)
|
||||
// compass.trailingAnchor.constraint(equalTo: mapView.trailingAnchor, constant: -5).isActive = true
|
||||
// compass.topAnchor.constraint(equalTo: mapView.topAnchor, constant: 145).isActive = true
|
||||
// #endif
|
||||
// }
|
||||
// private func setMapBaseLayer(mapView: MKMapView) {
|
||||
// // Avoid refreshing UI if selectedLayer has not changed
|
||||
// guard currentMapLayer != selectedMapLayer else { return }
|
||||
// currentMapLayer = selectedMapLayer
|
||||
// for overlay in mapView.overlays where overlay is MKTileOverlay {
|
||||
// mapView.removeOverlay(overlay)
|
||||
// }
|
||||
// switch selectedMapLayer {
|
||||
// case .offline:
|
||||
// mapView.mapType = .standard
|
||||
// let overlay = TileOverlay()
|
||||
// overlay.canReplaceMapContent = false
|
||||
// overlay.minimumZ = UserDefaults.mapTileServer.zoomRange.startIndex
|
||||
// overlay.maximumZ = UserDefaults.mapTileServer.zoomRange.endIndex
|
||||
// mapView.addOverlay(overlay, level: UserDefaults.mapTilesAboveLabels ? .aboveLabels : .aboveRoads)
|
||||
// case .satellite:
|
||||
// mapView.mapType = .satellite
|
||||
// case .hybrid:
|
||||
// mapView.mapType = .hybrid
|
||||
// default:
|
||||
// mapView.mapType = .standard
|
||||
// }
|
||||
// }
|
||||
// private func setMapOverlays(mapView: MKMapView) {
|
||||
// // Weather radar
|
||||
// if UserDefaults.enableOverlayServer {
|
||||
// let locale = Locale.current
|
||||
// if locale.region?.identifier ?? "no locale" == "US" {
|
||||
// let overlay = MKTileOverlay(urlTemplate: selectedWeatherLayer.tileUrl)
|
||||
// overlay.canReplaceMapContent = false
|
||||
// overlay.minimumZ = selectedWeatherLayer.zoomRange.startIndex
|
||||
// overlay.maximumZ = selectedWeatherLayer.zoomRange.endIndex
|
||||
// mapView.addOverlay(overlay, level: .aboveLabels)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// func makeUIView(context: Context) -> MKMapView {
|
||||
// currentMapLayer = nil
|
||||
// mapView.delegate = context.coordinator
|
||||
// self.configureMap(mapView: mapView)
|
||||
// return mapView
|
||||
// }
|
||||
// func updateUIView(_ mapView: MKMapView, context: Context) {
|
||||
// // Set selected map base layer
|
||||
// setMapBaseLayer(mapView: mapView)
|
||||
// // Set map tile server and weather overlay layers
|
||||
// setMapOverlays(mapView: mapView)
|
||||
// let latest = positions
|
||||
// .filter { $0.latest == true }
|
||||
// .sorted { $0.nodePosition?.num ?? 0 > $1.nodePosition?.num ?? -1 }
|
||||
// // Node Route Lines
|
||||
// if showRouteLines {
|
||||
// // Remove all existing PolyLine Overlays
|
||||
// for overlay in mapView.overlays where overlay is MKPolyline {
|
||||
// mapView.removeOverlay(overlay)
|
||||
// }
|
||||
// var lineIndex = 0
|
||||
// for position in latest {
|
||||
// let nodePositions = positions.filter { $0.nodeCoordinate != nil && $0.nodePosition?.num ?? 0 == position.nodePosition?.num ?? -1 }
|
||||
// let lineCoords = nodePositions.compactMap({(position) -> CLLocationCoordinate2D in
|
||||
// return position.nodeCoordinate ?? LocationHelper.DefaultLocation
|
||||
// })
|
||||
// let polyline = MKPolyline(coordinates: lineCoords, count: nodePositions.count)
|
||||
// polyline.title = "\(String(position.nodePosition?.num ?? 0))"
|
||||
// mapView.addOverlay(polyline, level: .aboveLabels)
|
||||
// lineIndex += 1
|
||||
// // There are 18 colors for lines, start over if we are at index 17
|
||||
// if lineIndex > 17 {
|
||||
// lineIndex = 0
|
||||
// }
|
||||
// }
|
||||
// } else {
|
||||
// // Remove all existing PolyLine Overlays
|
||||
// for overlay in mapView.overlays where overlay is MKPolyline {
|
||||
// mapView.removeOverlay(overlay)
|
||||
// }
|
||||
// }
|
||||
// let annotationCount = waypoints.count + (showNodeHistory ? positions.count : latest.count)
|
||||
// if annotationCount != mapView.annotations.count {
|
||||
// Logger.services.info("Annotation Count: \(annotationCount) Map Annotations: \(mapView.annotations.count)")
|
||||
// mapView.removeAnnotations(mapView.annotations)
|
||||
// mapView.addAnnotations(waypoints)
|
||||
// }
|
||||
// mapView.addAnnotations(showNodeHistory ? positions : latest)
|
||||
// if userTrackingMode == MKUserTrackingMode.none {
|
||||
// mapView.showsUserLocation = false
|
||||
// if UserDefaults.enableMapRecentering {
|
||||
// if latest.count == 1 {
|
||||
// mapView.fit(annotations: showNodeHistory ? positions : latest, andShow: true)
|
||||
// } else {
|
||||
// mapView.fitAllAnnotations()
|
||||
// }
|
||||
// }
|
||||
// } else {
|
||||
// mapView.showsUserLocation = true
|
||||
// }
|
||||
// mapView.setUserTrackingMode(userTrackingMode, animated: true)
|
||||
// }
|
||||
// func makeCoordinator() -> MapCoordinator {
|
||||
// return Coordinator(self)
|
||||
// }
|
||||
// final class MapCoordinator: NSObject, MKMapViewDelegate, UIGestureRecognizerDelegate {
|
||||
// var parent: MapViewSwiftUI
|
||||
// var longPressRecognizer = UILongPressGestureRecognizer()
|
||||
// init(_ parent: MapViewSwiftUI) {
|
||||
// self.parent = parent
|
||||
// super.init()
|
||||
// self.longPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(longPressHandler))
|
||||
// self.longPressRecognizer.minimumPressDuration = 0.5
|
||||
// self.longPressRecognizer.cancelsTouchesInView = true
|
||||
// self.longPressRecognizer.delegate = self
|
||||
// self.parent.mapView.addGestureRecognizer(longPressRecognizer)
|
||||
// }
|
||||
// func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
|
||||
// switch annotation {
|
||||
// case let positionAnnotation as PositionEntity:
|
||||
// let reuseID = String(positionAnnotation.nodePosition?.num ?? 0) + "-" + String(positionAnnotation.time?.timeIntervalSince1970 ?? 0)
|
||||
// let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "node") as? MKMarkerAnnotationView ?? MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: reuseID )
|
||||
// annotationView.tag = -1
|
||||
// annotationView.canShowCallout = true
|
||||
// if positionAnnotation.latest {
|
||||
// annotationView.markerTintColor = UIColor(hex: UInt32(positionAnnotation.nodePosition?.num ?? 0)).darker()
|
||||
// annotationView.displayPriority = .required
|
||||
// annotationView.titleVisibility = .visible
|
||||
// } else {
|
||||
// annotationView.markerTintColor = UIColor(hex: UInt32(positionAnnotation.nodePosition?.num ?? 0)).lighter()
|
||||
// annotationView.displayPriority = .defaultHigh
|
||||
// annotationView.titleVisibility = .adaptive
|
||||
// }
|
||||
// annotationView.tag = -1
|
||||
// annotationView.canShowCallout = true
|
||||
// annotationView.titleVisibility = .adaptive
|
||||
// let leftIcon = UIImageView(image: annotationView.glyphText?.image())
|
||||
// leftIcon.backgroundColor = UIColor(.indigo)
|
||||
// annotationView.leftCalloutAccessoryView = leftIcon
|
||||
// let subtitle = UILabel()
|
||||
// subtitle.text = "Long Name: \(positionAnnotation.nodePosition?.user?.longName ?? "Unknown") \n"
|
||||
// subtitle.text? += "Latitude: \(String(format: "%.5f", positionAnnotation.coordinate.latitude)) \n"
|
||||
// subtitle.text! += "Longitude: \(String(format: "%.5f", positionAnnotation.coordinate.longitude)) \n"
|
||||
// let distanceFormatter = MKDistanceFormatter()
|
||||
// subtitle.text! += "Altitude: \(distanceFormatter.string(fromDistance: Double(positionAnnotation.altitude))) \n"
|
||||
// if positionAnnotation.nodePosition?.metadata != nil {
|
||||
// if DeviceRoles(rawValue: Int(positionAnnotation.nodePosition!.metadata?.role ?? 0)) == DeviceRoles.client ||
|
||||
// DeviceRoles(rawValue: Int(positionAnnotation.nodePosition!.metadata?.role ?? 0)) == DeviceRoles.clientMute ||
|
||||
// DeviceRoles(rawValue: Int(positionAnnotation.nodePosition!.metadata?.role ?? 0)) == DeviceRoles.routerClient {
|
||||
// annotationView.glyphImage = UIImage(systemName: "flipphone")
|
||||
// } else if DeviceRoles(rawValue: Int(positionAnnotation.nodePosition!.metadata?.role ?? 0)) == DeviceRoles.repeater {
|
||||
// annotationView.glyphImage = UIImage(systemName: "repeat")
|
||||
// } else if DeviceRoles(rawValue: Int(positionAnnotation.nodePosition!.metadata?.role ?? 0)) == DeviceRoles.router {
|
||||
// annotationView.glyphImage = UIImage(systemName: "wifi.router.fill")
|
||||
// } else if DeviceRoles(rawValue: Int(positionAnnotation.nodePosition!.metadata?.role ?? 0)) == DeviceRoles.tracker {
|
||||
// annotationView.glyphImage = UIImage(systemName: "location.viewfinder")
|
||||
// } else if DeviceRoles(rawValue: Int(positionAnnotation.nodePosition!.metadata?.role ?? 0)) == DeviceRoles.sensor {
|
||||
// annotationView.glyphImage = UIImage(systemName: "sensor")
|
||||
// }
|
||||
// let pf = PositionFlags(rawValue: Int(positionAnnotation.nodePosition?.metadata?.positionFlags ?? 3))
|
||||
// if pf.contains(.Satsinview) {
|
||||
// subtitle.text! += "Sats in view: \(String(positionAnnotation.satsInView)) \n"
|
||||
// }
|
||||
// if pf.contains(.SeqNo) {
|
||||
// subtitle.text! += "Sequence: \(String(positionAnnotation.seqNo)) \n"
|
||||
// }
|
||||
// if pf.contains(.Heading) {
|
||||
// if parent.userTrackingMode != MKUserTrackingMode.followWithHeading {
|
||||
// annotationView.glyphImage = UIImage(systemName: "location.north.fill")?.rotate(radians: Float(degreesToRadians(Double(positionAnnotation.heading))))
|
||||
// subtitle.text! += "Heading: \(String(positionAnnotation.heading)) \n"
|
||||
// } else {
|
||||
// annotationView.glyphImage = UIImage(systemName: "flipphone")
|
||||
// }
|
||||
// }
|
||||
// if pf.contains(.Speed) {
|
||||
// let formatter = MeasurementFormatter()
|
||||
// formatter.locale = Locale.current
|
||||
// if positionAnnotation.speed <= 1 {
|
||||
// annotationView.glyphImage = UIImage(systemName: "hexagon")
|
||||
// }
|
||||
// subtitle.text! += "Speed: \(formatter.string(from: Measurement(value: Double(positionAnnotation.speed), unit: UnitSpeed.kilometersPerHour))) \n"
|
||||
// }
|
||||
// } else {
|
||||
// // node metadata is nil
|
||||
// annotationView.glyphImage = UIImage(systemName: "flipphone")
|
||||
// }
|
||||
// if LocationHelper.currentLocation.distance(from: LocationHelper.DefaultLocation) > 0.0 {
|
||||
// let metersAway = positionAnnotation.coordinate.distance(from: LocationHelper.currentLocation)
|
||||
// subtitle.text! += "distance".localized + ": \(distanceFormatter.string(fromDistance: Double(metersAway))) \n"
|
||||
// }
|
||||
// subtitle.text! += positionAnnotation.time?.formatted() ?? "Unknown \n"
|
||||
// subtitle.numberOfLines = 0
|
||||
// annotationView.detailCalloutAccessoryView = subtitle
|
||||
// let detailsIcon = UIButton(type: .detailDisclosure)
|
||||
// detailsIcon.setImage(UIImage(systemName: "trash"), for: .normal)
|
||||
// annotationView.rightCalloutAccessoryView = detailsIcon
|
||||
// return annotationView
|
||||
// case let waypointAnnotation as WaypointEntity:
|
||||
// let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "waypoint") as? MKMarkerAnnotationView ?? MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: String(waypointAnnotation.id))
|
||||
// annotationView.tag = Int(waypointAnnotation.id)
|
||||
// annotationView.isEnabled = true
|
||||
// annotationView.canShowCallout = true
|
||||
// if waypointAnnotation.icon == 0 {
|
||||
// annotationView.glyphText = "📍"
|
||||
// } else {
|
||||
// annotationView.glyphText = String(UnicodeScalar(Int(waypointAnnotation.icon)) ?? "📍")
|
||||
// }
|
||||
// annotationView.markerTintColor = UIColor(.accentColor)
|
||||
// annotationView.displayPriority = .required
|
||||
// annotationView.titleVisibility = .adaptive
|
||||
// let leftIcon = UIImageView(image: annotationView.glyphText?.image())
|
||||
// leftIcon.backgroundColor = UIColor(.accentColor)
|
||||
// annotationView.leftCalloutAccessoryView = leftIcon
|
||||
// let subtitle = UILabel()
|
||||
// if waypointAnnotation.longDescription?.count ?? 0 > 0 {
|
||||
// subtitle.text = (waypointAnnotation.longDescription ?? "") + "\n"
|
||||
// } else {
|
||||
// subtitle.text = ""
|
||||
// }
|
||||
// if LocationHelper.currentLocation.distance(from: LocationHelper.DefaultLocation) > 0.0 {
|
||||
// let metersAway = waypointAnnotation.coordinate.distance(from: LocationHelper.currentLocation)
|
||||
// let distanceFormatter = MKDistanceFormatter()
|
||||
// subtitle.text! += "distance".localized + ": \(distanceFormatter.string(fromDistance: Double(metersAway))) \n"
|
||||
// }
|
||||
// if waypointAnnotation.created != nil {
|
||||
// subtitle.text! += "Created: \(waypointAnnotation.created?.formatted() ?? "Unknown") \n"
|
||||
// }
|
||||
// if waypointAnnotation.lastUpdated != nil {
|
||||
// subtitle.text! += "Updated: \(waypointAnnotation.lastUpdated?.formatted() ?? "Unknown") \n"
|
||||
// }
|
||||
// if waypointAnnotation.expire != nil {
|
||||
// subtitle.text! += "Expires: \(waypointAnnotation.expire?.formatted() ?? "Unknown") \n"
|
||||
// }
|
||||
// subtitle.numberOfLines = 0
|
||||
// annotationView.detailCalloutAccessoryView = subtitle
|
||||
// let editIcon = UIButton(type: .detailDisclosure)
|
||||
// editIcon.setImage(UIImage(systemName: "square.and.pencil"), for: .normal)
|
||||
// annotationView.rightCalloutAccessoryView = editIcon
|
||||
// return annotationView
|
||||
// default: return nil
|
||||
// }
|
||||
// }
|
||||
// func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
|
||||
// switch view.annotation {
|
||||
// case _ as WaypointEntity:
|
||||
// // Only Allow Edit for waypoint annotations with a id
|
||||
// if view.tag > 0 {
|
||||
// parent.onWaypointEdit(view.tag)
|
||||
// }
|
||||
// default: break
|
||||
// }
|
||||
// }
|
||||
// @objc func longPressHandler(_ gesture: UILongPressGestureRecognizer) {
|
||||
// if gesture.state != UIGestureRecognizer.State.ended {
|
||||
// return
|
||||
// } else if gesture.state != UIGestureRecognizer.State.began {
|
||||
// // Screen Position - CGPoint
|
||||
// let location = longPressRecognizer.location(in: self.parent.mapView)
|
||||
// // Map Coordinate - CLLocationCoordinate2D
|
||||
// let coordinate = self.parent.mapView.convert(location, toCoordinateFrom: self.parent.mapView)
|
||||
// let annotation = MKPointAnnotation()
|
||||
// annotation.title = "Dropped Pin"
|
||||
// annotation.coordinate = coordinate
|
||||
// parent.mapView.addAnnotation(annotation)
|
||||
// UINotificationFeedbackGenerator().notificationOccurred(.success)
|
||||
// parent.onLongPress(coordinate)
|
||||
// }
|
||||
// }
|
||||
// public func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
|
||||
// if let tileOverlay = overlay as? MKTileOverlay {
|
||||
// return MKTileOverlayRenderer(tileOverlay: tileOverlay)
|
||||
// } else {
|
||||
// if let routePolyline = overlay as? MKPolyline {
|
||||
// let titleString = routePolyline.title ?? "0"
|
||||
// let renderer = MKPolylineRenderer(polyline: routePolyline)
|
||||
// renderer.strokeColor = UIColor(hex: UInt32(titleString) ?? 0).lighter()
|
||||
// renderer.lineWidth = 8
|
||||
// return renderer
|
||||
// }
|
||||
// if let polygon = overlay as? MKPolygon {
|
||||
// let renderer = MKPolygonRenderer(polygon: polygon)
|
||||
// renderer.fillColor = UIColor.purple.withAlphaComponent(0.2)
|
||||
// renderer.strokeColor = .purple.withAlphaComponent(0.7)
|
||||
// return renderer
|
||||
// }
|
||||
// return MKOverlayRenderer(overlay: overlay)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// /// is supposed to be located in the folder with the map name
|
||||
// public struct DefaultTile: Hashable {
|
||||
// let tileName: String
|
||||
// let tileType: String
|
||||
// public init(tileName: String, tileType: String) {
|
||||
// self.tileName = tileName
|
||||
// self.tileType = tileType
|
||||
// }
|
||||
// }
|
||||
// public struct CustomMapOverlay: Equatable, Hashable {
|
||||
// let mapName: String
|
||||
// let tileType: String
|
||||
// var canReplaceMapContent: Bool
|
||||
// var minimumZoomLevel: Int?
|
||||
// var maximumZoomLevel: Int?
|
||||
// let defaultTile: DefaultTile?
|
||||
// public init(
|
||||
// mapName: String,
|
||||
// tileType: String,
|
||||
// canReplaceMapContent: Bool = true, // false for transparent tiles
|
||||
// minimumZoomLevel: Int? = nil,
|
||||
// maximumZoomLevel: Int? = nil,
|
||||
// defaultTile: DefaultTile? = nil
|
||||
// ) {
|
||||
// self.mapName = mapName
|
||||
// self.tileType = tileType
|
||||
// self.canReplaceMapContent = canReplaceMapContent
|
||||
// self.minimumZoomLevel = minimumZoomLevel
|
||||
// self.maximumZoomLevel = maximumZoomLevel
|
||||
// self.defaultTile = defaultTile
|
||||
// }
|
||||
// public init?(
|
||||
// mapName: String?,
|
||||
// tileType: String,
|
||||
// canReplaceMapContent: Bool = true, // false for transparent tiles
|
||||
// minimumZoomLevel: Int? = nil,
|
||||
// maximumZoomLevel: Int? = nil,
|
||||
// defaultTile: DefaultTile? = nil
|
||||
// ) {
|
||||
// if mapName == nil || mapName! == "" {
|
||||
// return nil
|
||||
// }
|
||||
// self.mapName = mapName!
|
||||
// self.tileType = tileType
|
||||
// self.canReplaceMapContent = canReplaceMapContent
|
||||
// self.minimumZoomLevel = minimumZoomLevel
|
||||
// self.maximumZoomLevel = maximumZoomLevel
|
||||
// self.defaultTile = defaultTile
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
import SwiftUI
|
||||
|
||||
struct TileDownloadStatus: View {
|
||||
@ObservedObject var tileManager = OfflineTileManager.shared
|
||||
|
||||
var body: some View {
|
||||
if tileManager.status == .downloading {
|
||||
Image(systemName: "arrow.down.circle.fill")
|
||||
.foregroundColor(.gray)
|
||||
} else {
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,164 +0,0 @@
|
|||
////
|
||||
//// NodeMapControl.swift
|
||||
//// Meshtastic
|
||||
////
|
||||
//// Created by Garth Vander Houwen on 9/9/23.
|
||||
////
|
||||
//import SwiftUI
|
||||
//import CoreLocation
|
||||
//import MapKit
|
||||
//import WeatherKit
|
||||
//import OSLog
|
||||
//
|
||||
//struct NodeMapMapkit: View {
|
||||
//
|
||||
// @Environment(\.managedObjectContext) var context
|
||||
// @EnvironmentObject var bleManager: BLEManager
|
||||
// /// Weather
|
||||
// /// The current weather condition for the city.
|
||||
// @State private var condition: WeatherCondition?
|
||||
// @State private var temperature: Measurement<UnitTemperature>?
|
||||
// @State private var humidity: Int?
|
||||
// @State private var symbolName: String = "cloud.fill"
|
||||
// @State private var attributionLink: URL?
|
||||
// @State private var attributionLogo: URL?
|
||||
//
|
||||
// @Environment(\.colorScheme) var colorScheme: ColorScheme
|
||||
// @AppStorage("meshMapType") private var meshMapType = 0
|
||||
// @AppStorage("meshMapShowNodeHistory") private var meshMapShowNodeHistory = false
|
||||
// @AppStorage("meshMapShowRouteLines") private var meshMapShowRouteLines = false
|
||||
// @State private var selectedMapLayer: MapLayer = .standard
|
||||
// @State var waypointCoordinate: WaypointCoordinate?
|
||||
// @State var editingWaypoint: Int = 0
|
||||
// @State private var customMapOverlay: MapViewSwiftUI.CustomMapOverlay? = MapViewSwiftUI.CustomMapOverlay(
|
||||
// mapName: "offlinemap",
|
||||
// tileType: "png",
|
||||
// canReplaceMapContent: true
|
||||
// )
|
||||
// @FetchRequest(sortDescriptors: [NSSortDescriptor(key: "name", ascending: false)],
|
||||
// predicate: NSPredicate(
|
||||
// format: "expire == nil || expire >= %@", Date() as NSDate
|
||||
// ), animation: .none)
|
||||
// private var waypoints: FetchedResults<WaypointEntity>
|
||||
// @ObservedObject var node: NodeInfoEntity
|
||||
//
|
||||
// var body: some View {
|
||||
//
|
||||
// NavigationStack {
|
||||
// GeometryReader { bounds in
|
||||
// VStack {
|
||||
// if node.hasPositions {
|
||||
// ZStack {
|
||||
// let positionArray = node.positions?.array as? [PositionEntity] ?? []
|
||||
// let lastTenThousand = Array(positionArray.prefix(10000))
|
||||
// // let todaysPositions = positionArray.filter { $0.time! >= Calendar.current.startOfDay(for: Date()) }
|
||||
// ZStack {
|
||||
// MapViewSwiftUI(onLongPress: { coord in
|
||||
// waypointCoordinate = WaypointCoordinate(id: .init(), coordinate: coord, waypointId: 0)
|
||||
// }, onWaypointEdit: { wpId in
|
||||
// if wpId > 0 {
|
||||
// waypointCoordinate = WaypointCoordinate(id: .init(), coordinate: nil, waypointId: Int64(wpId))
|
||||
// }
|
||||
// },
|
||||
// selectedMapLayer: selectedMapLayer,
|
||||
// positions: lastTenThousand,
|
||||
// waypoints: Array(waypoints),
|
||||
// userTrackingMode: MKUserTrackingMode.none,
|
||||
// showNodeHistory: meshMapShowNodeHistory,
|
||||
// showRouteLines: meshMapShowRouteLines,
|
||||
// customMapOverlay: self.customMapOverlay
|
||||
// )
|
||||
// VStack(alignment: .leading) {
|
||||
// Spacer()
|
||||
// HStack(alignment: .bottom, spacing: 1) {
|
||||
// Picker("Map Type", selection: $selectedMapLayer) {
|
||||
// ForEach(MapLayer.allCases, id: \.self) { layer in
|
||||
// if layer == MapLayer.offline && UserDefaults.enableOfflineMaps {
|
||||
// Text(layer.localized)
|
||||
// } else if layer != MapLayer.offline {
|
||||
// Text(layer.localized)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// .onChange(of: (selectedMapLayer)) { newMapLayer in
|
||||
// UserDefaults.mapLayer = newMapLayer
|
||||
// }
|
||||
// .background(.thinMaterial, in: RoundedRectangle(cornerRadius: 12, style: .continuous))
|
||||
// .pickerStyle(.menu)
|
||||
// .padding(5)
|
||||
// VStack {
|
||||
// VStack {
|
||||
// Label(temperature?.formatted(.measurement(width: .narrow)) ?? "??", systemImage: symbolName)
|
||||
// .font(.caption)
|
||||
//
|
||||
// Label("\(humidity ?? 0)%", systemImage: "humidity")
|
||||
// .font(.caption2)
|
||||
//
|
||||
// AsyncImage(url: attributionLogo) { image in
|
||||
// image
|
||||
// .resizable()
|
||||
// .scaledToFit()
|
||||
// } placeholder: {
|
||||
// ProgressView()
|
||||
// .controlSize(.mini)
|
||||
// }
|
||||
// .frame(height: 10)
|
||||
//
|
||||
// Link("Other data sources", destination: attributionLink ?? URL(string: "https://weather-data.apple.com/legal-attribution.html")!)
|
||||
// .font(.caption2)
|
||||
// }
|
||||
// .padding(5)
|
||||
//
|
||||
// }
|
||||
// .background(.thinMaterial, in: RoundedRectangle(cornerRadius: 12, style: .continuous))
|
||||
// .padding(5)
|
||||
// .task {
|
||||
// do {
|
||||
// if node.hasPositions {
|
||||
// let mostRecent = node.positions?.lastObject as? PositionEntity
|
||||
// let weather = try await WeatherService.shared.weather(for: mostRecent?.nodeLocation ?? CLLocation(latitude: LocationHelper.currentLocation.latitude, longitude: LocationHelper.currentLocation.longitude))
|
||||
// condition = weather.currentWeather.condition
|
||||
// temperature = weather.currentWeather.temperature
|
||||
// humidity = Int(weather.currentWeather.humidity * 100)
|
||||
// symbolName = weather.currentWeather.symbolName
|
||||
// let attribution = try await WeatherService.shared.attribution
|
||||
// attributionLink = attribution.legalPageURL
|
||||
// attributionLogo = colorScheme == .light ? attribution.combinedMarkLightURL : attribution.combinedMarkDarkURL
|
||||
// }
|
||||
// } catch {
|
||||
// Logger.services.error("Could not gather weather information: \(error.localizedDescription)")
|
||||
// condition = .clear
|
||||
// symbolName = "cloud.fill"
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// .ignoresSafeArea(.all, edges: [.top, .leading, .trailing])
|
||||
// .frame(idealWidth: bounds.size.width, minHeight: bounds.size.height / 1.65)
|
||||
// }
|
||||
// } else {
|
||||
// HStack {
|
||||
// }
|
||||
// .padding([.top], 20)
|
||||
// }
|
||||
// }
|
||||
// .edgesIgnoringSafeArea([.leading, .trailing])
|
||||
// .sheet(item: $waypointCoordinate, content: { wpc in
|
||||
// WaypointFormMapKit(coordinate: wpc)
|
||||
// .presentationDetents([.medium, .large])
|
||||
// .presentationDragIndicator(.automatic)
|
||||
// })
|
||||
// .navigationBarTitle(String(node.user?.longName ?? "unknown".localized), displayMode: .inline)
|
||||
// .navigationBarItems(trailing:
|
||||
// ZStack {
|
||||
// ConnectedDevice(
|
||||
// bluetoothOn: bleManager.isSwitchedOn,
|
||||
// deviceConnected: bleManager.connectedPeripheral != nil,
|
||||
// name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
|
||||
// })
|
||||
// }
|
||||
// .padding(.bottom, 2)
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
|
@ -1,266 +0,0 @@
|
|||
////
|
||||
//// WaypointFormView.swift
|
||||
//// Meshtastic
|
||||
////
|
||||
//// Copyright Garth Vander Houwen 1/10/23.
|
||||
////
|
||||
//
|
||||
//import CoreLocation
|
||||
//import MeshtasticProtobufs
|
||||
//import OSLog
|
||||
//import SwiftUI
|
||||
//
|
||||
//struct WaypointFormMapKit: View {
|
||||
//
|
||||
// @EnvironmentObject var bleManager: BLEManager
|
||||
// @Environment(\.dismiss) private var dismiss
|
||||
// @State var coordinate: WaypointCoordinate
|
||||
// @FocusState private var iconIsFocused: Bool
|
||||
// @State private var name: String = ""
|
||||
// @State private var description: String = ""
|
||||
// @State private var icon: String = "📍"
|
||||
// @State private var latitude: Double = 0
|
||||
// @State private var longitude: Double = 0
|
||||
// @State private var expires: Bool = false
|
||||
// @State private var expire: Date = Date.now.addingTimeInterval(60 * 480) // 1 minute * 480 = 8 Hours
|
||||
// @State private var locked: Bool = false
|
||||
// @State private var lockedTo: Int64 = 0
|
||||
//
|
||||
// var body: some View {
|
||||
//
|
||||
// Form {
|
||||
// let distance = CLLocation(latitude: LocationHelper.currentLocation.latitude, longitude: LocationHelper.currentLocation.longitude).distance(from: CLLocation(latitude: coordinate.coordinate?.latitude ?? 0, longitude: coordinate.coordinate?.longitude ?? 0))
|
||||
// Section(header: Text((coordinate.waypointId > 0) ? "Editing Waypoint" : "Create Waypoint")) {
|
||||
// HStack {
|
||||
// Text("Location: \(String(format: "%.5f", latitude) + "," + String(format: "%.5f", longitude))")
|
||||
// .textSelection(.enabled)
|
||||
// .foregroundColor(Color.gray)
|
||||
// .font(.caption2)
|
||||
// if coordinate.coordinate?.latitude ?? 0 != 0 && coordinate.coordinate?.longitude ?? 0 != 0 {
|
||||
// DistanceText(meters: distance)
|
||||
// .foregroundColor(Color.gray)
|
||||
// .font(.caption2)
|
||||
// }
|
||||
// }
|
||||
// HStack {
|
||||
// Text("Name")
|
||||
// Spacer()
|
||||
// TextField(
|
||||
// "Name",
|
||||
// text: $name,
|
||||
// axis: .vertical
|
||||
// )
|
||||
// .foregroundColor(Color.gray)
|
||||
// .onChange(of: name) {
|
||||
// var totalBytes = name.utf8.count
|
||||
// // Only mess with the value if it is too big
|
||||
// while totalBytes > 30 {
|
||||
// name = String(name.dropLast())
|
||||
// totalBytes = name.utf8.count
|
||||
// }
|
||||
// if totalBytes > 30 {
|
||||
// name = String(name.dropLast())
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// HStack {
|
||||
// Text("Description")
|
||||
// Spacer()
|
||||
// TextField(
|
||||
// "Description",
|
||||
// text: $description,
|
||||
// axis: .vertical
|
||||
// )
|
||||
// .foregroundColor(Color.gray)
|
||||
// .onChange(of: description) {
|
||||
// var totalBytes = description.utf8.count
|
||||
// // Only mess with the value if it is too big
|
||||
// while totalBytes > 100 {
|
||||
// description = String(description.dropLast())
|
||||
// totalBytes = description.utf8.count
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// HStack {
|
||||
// Text("Icon")
|
||||
// Spacer()
|
||||
// EmojiOnlyTextField(text: $icon, placeholder: "Select an emoji")
|
||||
// .font(.title)
|
||||
// .focused($iconIsFocused)
|
||||
// .onChange(of: icon) { _, value in
|
||||
//
|
||||
// // If you have anything other than emojis in your string make it empty
|
||||
// if !value.onlyEmojis() {
|
||||
// icon = ""
|
||||
// }
|
||||
// // If a second emoji is entered delete the first one
|
||||
// if value.count >= 1 {
|
||||
//
|
||||
// if value.count > 1 {
|
||||
// let index = value.index(value.startIndex, offsetBy: 1)
|
||||
// icon = String(value[index])
|
||||
// }
|
||||
// iconIsFocused = false
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// }
|
||||
// Toggle(isOn: $expires) {
|
||||
// Label("Expires", systemImage: "clock.badge.xmark")
|
||||
// }
|
||||
// .toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
// if expires {
|
||||
// DatePicker("Expire", selection: $expire, in: Date.now...)
|
||||
// .datePickerStyle(.compact)
|
||||
// .font(.callout)
|
||||
// }
|
||||
// Toggle(isOn: $locked) {
|
||||
// Label("Locked", systemImage: "lock")
|
||||
// }
|
||||
// .toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
// }
|
||||
// }
|
||||
// HStack {
|
||||
// Button {
|
||||
//
|
||||
// var newWaypoint = Waypoint()
|
||||
// // Loading a waypoint from edit
|
||||
// if coordinate.waypointId > 0 {
|
||||
// newWaypoint.id = UInt32(coordinate.waypointId)
|
||||
// let waypoint = getWaypoint(id: Int64(coordinate.waypointId), context: bleManager.context)
|
||||
// newWaypoint.latitudeI = waypoint.latitudeI
|
||||
// newWaypoint.longitudeI = waypoint.longitudeI
|
||||
// } else {
|
||||
// // New waypoint
|
||||
// newWaypoint.id = UInt32.random(in: UInt32(UInt8.max)..<UInt32.max)
|
||||
// newWaypoint.latitudeI = Int32(Double(coordinate.coordinate?.latitude ?? 0) * 1e7)
|
||||
// newWaypoint.longitudeI = Int32(Double(coordinate.coordinate?.longitude ?? 0) * 1e7)
|
||||
// }
|
||||
// newWaypoint.name = name.count > 0 ? name : "Dropped Pin"
|
||||
// newWaypoint.description_p = description
|
||||
// // Unicode scalar value for the icon emoji string
|
||||
// let unicodeScalers = icon.unicodeScalars
|
||||
// // First element as an UInt32
|
||||
// let unicode = unicodeScalers[unicodeScalers.startIndex].value
|
||||
// newWaypoint.icon = unicode
|
||||
// if locked {
|
||||
// if lockedTo == 0 {
|
||||
// newWaypoint.lockedTo = UInt32(bleManager.connectedPeripheral!.num)
|
||||
// } else {
|
||||
// newWaypoint.lockedTo = UInt32(lockedTo)
|
||||
// }
|
||||
// }
|
||||
// if expires {
|
||||
// newWaypoint.expire = UInt32(expire.timeIntervalSince1970)
|
||||
// } else {
|
||||
// newWaypoint.expire = 0
|
||||
// }
|
||||
// if bleManager.sendWaypoint(waypoint: newWaypoint) {
|
||||
// dismiss()
|
||||
// } else {
|
||||
// dismiss()
|
||||
// Logger.mesh.error("Send waypoint failed")
|
||||
// }
|
||||
// } label: {
|
||||
// Label("Send", systemImage: "arrow.up")
|
||||
// }
|
||||
// .buttonStyle(.bordered)
|
||||
// .buttonBorderShape(.capsule)
|
||||
// .controlSize(.regular)
|
||||
// .disabled(bleManager.connectedPeripheral == nil)
|
||||
// .padding(.bottom)
|
||||
//
|
||||
// Button(role: .cancel) {
|
||||
// dismiss()
|
||||
// } label: {
|
||||
// Label("cancel", systemImage: "x.circle")
|
||||
// }
|
||||
// .buttonStyle(.bordered)
|
||||
// .buttonBorderShape(.capsule)
|
||||
// .controlSize(.regular)
|
||||
// .padding(.bottom)
|
||||
//
|
||||
// if coordinate.waypointId > 0 {
|
||||
//
|
||||
// Menu {
|
||||
// Button("For me", action: {
|
||||
// let waypoint = getWaypoint(id: Int64(coordinate.waypointId), context: bleManager.context)
|
||||
// bleManager.context.delete(waypoint)
|
||||
// do {
|
||||
// try bleManager.context.save()
|
||||
// } catch {
|
||||
// bleManager.context.rollback()
|
||||
// }
|
||||
// dismiss() })
|
||||
// Button("For everyone", action: {
|
||||
// var newWaypoint = Waypoint()
|
||||
//
|
||||
// if coordinate.waypointId > 0 {
|
||||
// newWaypoint.id = UInt32(coordinate.waypointId)
|
||||
// }
|
||||
// newWaypoint.name = name.count > 0 ? name : "Dropped Pin"
|
||||
// newWaypoint.description_p = description
|
||||
// newWaypoint.latitudeI = Int32(coordinate.coordinate?.latitude ?? 0 * 1e7)
|
||||
// newWaypoint.longitudeI = Int32(coordinate.coordinate?.longitude ?? 0 * 1e7)
|
||||
// // Unicode scalar value for the icon emoji string
|
||||
// let unicodeScalers = icon.unicodeScalars
|
||||
// // First element as an UInt32
|
||||
// let unicode = unicodeScalers[unicodeScalers.startIndex].value
|
||||
// newWaypoint.icon = unicode
|
||||
// if locked {
|
||||
// if lockedTo == 0 {
|
||||
// newWaypoint.lockedTo = UInt32(bleManager.connectedPeripheral!.num)
|
||||
// } else {
|
||||
// newWaypoint.lockedTo = UInt32(lockedTo)
|
||||
// }
|
||||
// }
|
||||
// newWaypoint.expire = 1
|
||||
// if bleManager.sendWaypoint(waypoint: newWaypoint) {
|
||||
// dismiss()
|
||||
// } else {
|
||||
// dismiss()
|
||||
// Logger.mesh.error("Send waypoint failed")
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
// label: {
|
||||
// Label("delete", systemImage: "trash")
|
||||
// .foregroundColor(.red)
|
||||
// }
|
||||
// .buttonStyle(.bordered)
|
||||
// .buttonBorderShape(.capsule)
|
||||
// .controlSize(.regular)
|
||||
// .padding(.bottom)
|
||||
// }
|
||||
// }
|
||||
// .onAppear {
|
||||
// if coordinate.waypointId > 0 {
|
||||
// let waypoint = getWaypoint(id: Int64(coordinate.waypointId), context: bleManager.context)
|
||||
// name = waypoint.name ?? "Dropped Pin"
|
||||
// description = waypoint.longDescription ?? ""
|
||||
// icon = String(UnicodeScalar(Int(waypoint.icon)) ?? "📍")
|
||||
// latitude = Double(waypoint.latitudeI) / 1e7
|
||||
// longitude = Double(waypoint.longitudeI) / 1e7
|
||||
// if waypoint.expire != nil {
|
||||
// expires = true
|
||||
// expire = waypoint.expire ?? Date()
|
||||
// } else {
|
||||
// expires = false
|
||||
// }
|
||||
// if waypoint.locked > 0 {
|
||||
// locked = true
|
||||
// lockedTo = waypoint.locked
|
||||
// }
|
||||
// } else {
|
||||
// name = ""
|
||||
// description = ""
|
||||
// locked = false
|
||||
// expires = false
|
||||
// expire = Date.now.addingTimeInterval(60 * 480)
|
||||
// icon = "📍"
|
||||
// latitude = coordinate.coordinate?.latitude ?? 0
|
||||
// longitude = coordinate.coordinate?.longitude ?? 0
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
|
@ -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)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {}
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ struct RetryButton: View {
|
|||
do {
|
||||
try context.save()
|
||||
} catch {
|
||||
Logger.data.error("Failed to delete message \(messageID): \(error.localizedDescription)")
|
||||
Logger.data.error("Failed to delete message \(messageID, privacy: .public): \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
if !bleManager.sendMessage(
|
||||
message: payload,
|
||||
|
|
@ -47,7 +47,7 @@ struct RetryButton: View {
|
|||
replyID: replyID
|
||||
) {
|
||||
// Best effort, unlikely since we already checked BLE state
|
||||
Logger.services.warning("Failed to resend message \(messageID)")
|
||||
Logger.services.warning("Failed to resend message \(messageID, privacy: .public)")
|
||||
} else {
|
||||
switch destination {
|
||||
case .user:
|
||||
|
|
|
|||
|
|
@ -31,10 +31,10 @@ struct TapbackResponses: View {
|
|||
tapback.read = true
|
||||
do {
|
||||
try context.save()
|
||||
Logger.data.info("📖 Read tapback \(tapback.messageId) ")
|
||||
Logger.data.info("📖 Read tapback \(tapback.messageId, privacy: .public) ")
|
||||
onRead()
|
||||
} catch {
|
||||
Logger.data.error("Failed to read tapback \(tapback.messageId): \(error.localizedDescription)")
|
||||
Logger.data.error("Failed to read tapback \(tapback.messageId, privacy: .public): \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -103,11 +103,11 @@ struct UserMessageList: View {
|
|||
message.read = true
|
||||
do {
|
||||
try context.save()
|
||||
Logger.data.info("📖 [App] Read message \(message.messageId) ")
|
||||
Logger.data.info("📖 [App] Read message \(message.messageId, privacy: .public) ")
|
||||
appState.unreadDirectMessages = user.unreadMessages
|
||||
|
||||
} catch {
|
||||
Logger.data.error("Failed to read message \(message.messageId): \(error.localizedDescription)")
|
||||
Logger.data.error("Failed to read message \(message.messageId, privacy: .public): \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -135,7 +135,7 @@ struct DetectionSensorLog: View {
|
|||
self.isExporting = false
|
||||
Logger.services.info("Detection Sensor metrics log download succeeded.")
|
||||
case .failure(let error):
|
||||
Logger.services.error("Detection Sensor log download failed: \(error.localizedDescription).")
|
||||
Logger.services.error("Detection Sensor log download failed: \(error.localizedDescription, privacy: .public).")
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -211,7 +211,7 @@ struct DeviceMetricsLog: View {
|
|||
) {
|
||||
Button("device.metrics.delete", role: .destructive) {
|
||||
if clearTelemetry(destNum: node.num, metricsType: 0, context: context) {
|
||||
Logger.data.notice("Cleared Device Metrics for \(node.num)")
|
||||
Logger.data.notice("Cleared Device Metrics for \(node.num, privacy: .public)")
|
||||
} else {
|
||||
Logger.data.error("Clear Device Metrics Log Failed")
|
||||
}
|
||||
|
|
@ -257,7 +257,7 @@ struct DeviceMetricsLog: View {
|
|||
self.isExporting = false
|
||||
Logger.services.info("Device metrics log download succeeded.")
|
||||
case .failure(let error):
|
||||
Logger.services.error("Device metrics log download failed: \(error.localizedDescription)")
|
||||
Logger.services.error("Device metrics log download failed: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -177,7 +177,7 @@ struct EnvironmentMetricsLog: View {
|
|||
self.isExporting = false
|
||||
Logger.services.info("Environment metrics log download succeeded.")
|
||||
case .failure(let error):
|
||||
Logger.services.error("Environment metrics log download failed: \(error.localizedDescription)")
|
||||
Logger.services.error("Environment metrics log download failed: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ struct DeleteNodeButton: View {
|
|||
id: node.num,
|
||||
context: context
|
||||
) else {
|
||||
Logger.data.error("Unable to find node info to delete node \(node.num)")
|
||||
Logger.data.error("Unable to find node info to delete node \(node.num, privacy: .public)")
|
||||
return
|
||||
}
|
||||
let success = bleManager.removeNode(
|
||||
|
|
@ -49,7 +49,7 @@ struct DeleteNodeButton: View {
|
|||
connectedNodeNum: connectedNode.num
|
||||
)
|
||||
if !success {
|
||||
Logger.data.error("Failed to delete node \(deleteNode.user?.longName ?? "unknown".localized)")
|
||||
Logger.data.error("Failed to delete node \(deleteNode.user?.longName ?? "unknown".localized, privacy: .public)")
|
||||
} else {
|
||||
dismiss()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,20 +19,20 @@ struct NavigateToButton: View {
|
|||
Logger.services.error("NavigateToAction: Selected node does not exist")
|
||||
return
|
||||
}
|
||||
|
||||
Logger.services.info("Fetching NodeInfoEntity for userNum: \(userNum)")
|
||||
|
||||
|
||||
Logger.services.info("Fetching NodeInfoEntity for userNum: \(userNum, privacy: .public)")
|
||||
|
||||
let fetchRequest: NSFetchRequest<NodeInfoEntity> = NSFetchRequest(entityName: "NodeInfoEntity")
|
||||
fetchRequest.predicate = NSPredicate(format: "num == %lld", Int64(userNum))
|
||||
|
||||
|
||||
do {
|
||||
let fetchedNodes = try PersistenceController.shared.container.viewContext.fetch(fetchRequest)
|
||||
|
||||
|
||||
guard let nodeInfo = fetchedNodes.first else {
|
||||
Logger.services.error("NavigateToAction: Node with userNum \(userNum) not found in Core Data")
|
||||
Logger.services.error("NavigateToAction: Node with userNum \(userNum, privacy: .public) not found in Core Data")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
if let latitude = nodeInfo.latestPosition?.latitude,
|
||||
let longitude = nodeInfo.latestPosition?.longitude {
|
||||
if let url = URL(string: "maps://?saddr=&daddr=\(latitude),\(longitude)") {
|
||||
|
|
@ -41,10 +41,10 @@ struct NavigateToButton: View {
|
|||
Logger.services.error("Failed to create URL for navigation")
|
||||
}
|
||||
} else {
|
||||
Logger.services.warning("NavigateToAction: Node \(userNum) has invalid or missing coordinates")
|
||||
Logger.services.warning("NavigateToAction: Node \(userNum, privacy: .public) has invalid or missing coordinates")
|
||||
}
|
||||
} catch {
|
||||
Logger.services.error("NavigateToAction: Failed to fetch node with userNum \(userNum): \(error.localizedDescription)")
|
||||
Logger.services.error("NavigateToAction: Failed to fetch node with userNum \(userNum, privacy: .public): \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
} label: {
|
||||
Label {
|
||||
|
|
|
|||
|
|
@ -1,233 +0,0 @@
|
|||
//
|
||||
// EnvironmentDefaultSeries.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Created by Jake Bordens on 12/11/24.
|
||||
//
|
||||
|
||||
import Charts
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
// This is the default configuration used by the EnvironmentMetricsLog view for the chart
|
||||
extension MetricsSeriesList {
|
||||
static var environmentDefaultChartSeries: MetricsSeriesList {
|
||||
MetricsSeriesList([
|
||||
// Temperature Series Configuration
|
||||
MetricsChartSeries(
|
||||
id: "temperature",
|
||||
keyPath: \.temperature,
|
||||
name: "Temperature",
|
||||
abbreviatedName: "Temp",
|
||||
conversion: { t in t.map { Float($0.localeTemperature()) } },
|
||||
foregroundStyle: { chartRange in
|
||||
let locale = NSLocale.current as NSLocale
|
||||
let localeUnit = locale.object(forKey: NSLocale.Key(rawValue: "kCFLocaleTemperatureUnitKey"))
|
||||
let format: UnitTemperature = localeUnit as? String ?? "Celsius" == "Fahrenheit" ? .fahrenheit : .celsius
|
||||
let lowerBound = chartRange.map { Double($0.lowerBound) } ?? 0.0
|
||||
let upperBound = chartRange.map { Double($0.upperBound) } ?? 100.0
|
||||
let stops: [Gradient.Stop] = generateStops(minTemp: lowerBound, maxTemp: upperBound, tempUnit: format, opacity: 1.0)
|
||||
return LinearGradient(stops: stops, startPoint: .bottom, endPoint: .top)
|
||||
},
|
||||
chartBody: { series, chartRange, time, temperature in
|
||||
if let temperature {
|
||||
AreaMark(
|
||||
x: .value("Time", time),
|
||||
yStart: .value(series.abbreviatedName, chartRange?.lowerBound.doubleValue ?? 0.0),
|
||||
yEnd: .value(
|
||||
series.abbreviatedName, temperature.localeTemperature())
|
||||
)
|
||||
.interpolationMethod(.catmullRom)
|
||||
.foregroundStyle(by: .value("Series", series.abbreviatedName))
|
||||
.alignsMarkStylesWithPlotArea()
|
||||
.accessibilityHidden(true)
|
||||
.opacity(0.6)
|
||||
LineMark(
|
||||
x: .value("Time", time),
|
||||
y: .value(
|
||||
series.abbreviatedName, temperature.localeTemperature())
|
||||
)
|
||||
.interpolationMethod(.catmullRom)
|
||||
.foregroundStyle(by: .value("Series", series.abbreviatedName))
|
||||
.lineStyle(StrokeStyle(lineWidth: 4))
|
||||
.alignsMarkStylesWithPlotArea()
|
||||
}
|
||||
}),
|
||||
|
||||
// Relative Humidity Series Configuration
|
||||
MetricsChartSeries(
|
||||
id: "relativeHumidity",
|
||||
keyPath: \.relativeHumidity,
|
||||
name: "Relative Humidity",
|
||||
abbreviatedName: "Hum",
|
||||
foregroundStyle: { _ in
|
||||
.linearGradient(
|
||||
colors: [Color(UIColor.purple.darker(componentDelta: 0.2)), .purple],
|
||||
startPoint: .bottom, endPoint: .top
|
||||
)
|
||||
},
|
||||
chartBody: { series, _, time, humidity in
|
||||
if let humidity {
|
||||
LineMark(
|
||||
x: .value("Time", time),
|
||||
y: .value(series.abbreviatedName, humidity)
|
||||
)
|
||||
.interpolationMethod(.catmullRom)
|
||||
.foregroundStyle(by: .value("Series", series.abbreviatedName))
|
||||
.lineStyle(StrokeStyle(lineWidth: 4))
|
||||
.alignsMarkStylesWithPlotArea()
|
||||
}
|
||||
}),
|
||||
|
||||
// Barometric Pressure Series Configuration
|
||||
MetricsChartSeries(
|
||||
id: "barometricPressure",
|
||||
keyPath: \.barometricPressure,
|
||||
name: "Barometric Pressure",
|
||||
abbreviatedName: "Bar",
|
||||
visible: false,
|
||||
foregroundStyle: { _ in
|
||||
.linearGradient(
|
||||
colors: [Color(UIColor.green.darker(componentDelta: 0.3)), .green],
|
||||
startPoint: .bottom, endPoint: .top
|
||||
)
|
||||
},
|
||||
chartBody: { series, _, time, pressure in
|
||||
if let pressure {
|
||||
LineMark(
|
||||
x: .value("Time", time),
|
||||
y: .value(series.abbreviatedName, pressure)
|
||||
)
|
||||
.interpolationMethod(.catmullRom)
|
||||
.foregroundStyle(by: .value("Series", series.abbreviatedName))
|
||||
.lineStyle(StrokeStyle(lineWidth: 4))
|
||||
.alignsMarkStylesWithPlotArea()
|
||||
}
|
||||
}),
|
||||
|
||||
// Indoor Air Quality Series Configuration
|
||||
MetricsChartSeries(
|
||||
id: "iaq",
|
||||
keyPath: \.iaq,
|
||||
name: "Indoor Air Quality",
|
||||
abbreviatedName: "IAQ",
|
||||
visible: false,
|
||||
foregroundStyle: { _ in .gray },
|
||||
chartBody: { series, _, time, iaq in
|
||||
if let iaq {
|
||||
let iaqEnum = Iaq.getIaq(for: Int(iaq))
|
||||
PointMark(
|
||||
x: .value("Time", time),
|
||||
y: .value(series.abbreviatedName, Float(iaq))
|
||||
)
|
||||
.symbol(Circle())
|
||||
.foregroundStyle(iaqEnum.color)
|
||||
LineMark(
|
||||
x: .value("Time", time),
|
||||
y: .value(series.abbreviatedName, Float(iaq))
|
||||
)
|
||||
.interpolationMethod(.catmullRom)
|
||||
.foregroundStyle(by: .value("Series", series.abbreviatedName))
|
||||
.lineStyle(StrokeStyle(lineWidth: 4))
|
||||
.alignsMarkStylesWithPlotArea()
|
||||
}
|
||||
}),
|
||||
|
||||
// Combined Wind Speed and Direction Series Configuration -- For use in Chart only
|
||||
MetricsChartSeries(
|
||||
id: "windSpeedAndDirection",
|
||||
keyPath: \.windSpeedAndDirection,
|
||||
name: "Wind Speed/Direction",
|
||||
abbreviatedName: "Speed/Dir",
|
||||
visible: false,
|
||||
foregroundStyle: { _ in
|
||||
.linearGradient(
|
||||
colors: [Color(UIColor.yellow.darker(componentDelta: 0.3)), Color(UIColor.yellow.darker(componentDelta: 0.1))],
|
||||
startPoint: .bottom, endPoint: .top
|
||||
)
|
||||
},
|
||||
chartBody: { series, _, time, wsad in
|
||||
if let wsad {
|
||||
// debug data: var wsad = WindSpeedAndDirection(windSpeed:Float.random(in:0...25), windDirection: Int32.random(in:0..<3)*90 )
|
||||
LineMark(
|
||||
x: .value("Time", time),
|
||||
y: .value(series.abbreviatedName, wsad.windSpeed)
|
||||
)
|
||||
.interpolationMethod(.catmullRom)
|
||||
.foregroundStyle(by: .value("Series", series.abbreviatedName))
|
||||
.lineStyle(StrokeStyle(lineWidth: 4))
|
||||
.alignsMarkStylesWithPlotArea()
|
||||
PointMark(
|
||||
x: .value("Time", time),
|
||||
y: .value(series.abbreviatedName, wsad.windSpeed)
|
||||
)
|
||||
.symbol {
|
||||
if let wd = wsad.windDirection {
|
||||
Image(systemName: "location.north.circle.fill")
|
||||
.symbolRenderingMode(.palette)
|
||||
.foregroundStyle(Color.white, Color(UIColor.yellow.darker(componentDelta: 0.3)))
|
||||
.rotationEffect(
|
||||
.degrees(Double(wd)))
|
||||
}
|
||||
}.foregroundStyle(.yellow)
|
||||
}
|
||||
})
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
// Extension to combine windspeed and direction into one attribute for rendering
|
||||
// for rendering on the chart.
|
||||
@objc class WindSpeedAndDirection: NSObject, Plottable, Comparable {
|
||||
|
||||
let windSpeed: Float
|
||||
let windDirection: Int32?
|
||||
init(windSpeed: Float, windDirection: Int32?) {
|
||||
self.windSpeed = windSpeed
|
||||
self.windDirection = windDirection
|
||||
}
|
||||
|
||||
// Plottable Conformance
|
||||
required init?(primitivePlottable: Float) { nil }
|
||||
var primitivePlottable: Float { windSpeed }
|
||||
|
||||
static func < (lhs: WindSpeedAndDirection, rhs: WindSpeedAndDirection) -> Bool {
|
||||
lhs.windSpeed < rhs.windSpeed
|
||||
}
|
||||
}
|
||||
|
||||
@objc extension TelemetryEntity {
|
||||
var windSpeedAndDirection: WindSpeedAndDirection? {
|
||||
guard let windSpeed = self.windSpeed else { return nil }
|
||||
|
||||
return WindSpeedAndDirection(windSpeed: windSpeed, windDirection: self.windDirection)
|
||||
}
|
||||
}
|
||||
|
||||
// From: https://github.com/meshtastic/Meshtastic-Apple/pull/1013/commits/bc932567c742c8fa9fd30752237b10cb762c5ef3
|
||||
// Set up gradient stops relative to the scale of the temperature chart
|
||||
func generateStops(minTemp: Double, maxTemp: Double, tempUnit: UnitTemperature, opacity: Double) -> [Gradient.Stop] {
|
||||
var gradientStops = [Gradient.Stop]()
|
||||
|
||||
let stopTargets: [(Double, Color)] = [
|
||||
((tempUnit == .celsius ? 0 : 32), .blue),
|
||||
((tempUnit == .celsius ? 20 : 68), .yellow),
|
||||
((tempUnit == .celsius ? 30 : 86), .orange),
|
||||
((tempUnit == .celsius ? 55 : 125), .red)
|
||||
]
|
||||
for (stopValue, color) in stopTargets {
|
||||
let stopLocation = transform(stopValue, from: minTemp...maxTemp, to: 0...1)
|
||||
gradientStops.append(Gradient.Stop(color: color.opacity(opacity), location: stopLocation))
|
||||
}
|
||||
return gradientStops
|
||||
}
|
||||
|
||||
// Map inputRange to outputRange
|
||||
func transform<T: FloatingPoint>(_ input: T, from inputRange: ClosedRange<T>, to outputRange: ClosedRange<T>) -> T {
|
||||
// need to determine what that value would be in (to.low, to.high)
|
||||
// difference in output range / difference in input range = slope
|
||||
let slope = (outputRange.upperBound - outputRange.lowerBound) / (inputRange.upperBound - inputRange.lowerBound)
|
||||
// slope * normalized input + output lower
|
||||
let output = slope * (input - inputRange.lowerBound) + outputRange.lowerBound
|
||||
return output
|
||||
}
|
||||
|
|
@ -73,6 +73,77 @@ extension MetricsColumnList {
|
|||
}
|
||||
}),
|
||||
|
||||
// Various Lux
|
||||
MetricsTableColumn(
|
||||
id: "lux",
|
||||
keyPath: \.lux,
|
||||
name: "Lux",
|
||||
abbreviatedName: "Lux",
|
||||
minWidth: 30, maxWidth: 50,
|
||||
visible: false,
|
||||
tableBody: { _, lux in
|
||||
lux.map {
|
||||
Text("\($0.formatted(.number.grouping(.never).precision(.fractionLength(1))))")
|
||||
} ?? Text(Constants.nilValueIndicator)
|
||||
}),
|
||||
|
||||
MetricsTableColumn(
|
||||
id: "whiteLux",
|
||||
keyPath: \.whiteLux,
|
||||
name: "White Lux",
|
||||
abbreviatedName: "White",
|
||||
minWidth: 30, maxWidth: 50,
|
||||
visible: false,
|
||||
tableBody: { _, lux in
|
||||
lux.map {
|
||||
Text("\($0.formatted(.number.grouping(.never).precision(.fractionLength(1))))")
|
||||
} ?? Text(Constants.nilValueIndicator)
|
||||
}),
|
||||
|
||||
MetricsTableColumn(
|
||||
id: "uvLux",
|
||||
keyPath: \.uvLux,
|
||||
name: "UV Lux",
|
||||
abbreviatedName: "UV",
|
||||
minWidth: 30, maxWidth: 50,
|
||||
visible: false,
|
||||
tableBody: { _, lux in
|
||||
lux.map {
|
||||
Text("\($0.formatted(.number.grouping(.never).precision(.fractionLength(1))))")
|
||||
} ?? Text(Constants.nilValueIndicator)
|
||||
}),
|
||||
|
||||
MetricsTableColumn(
|
||||
id: "irLux",
|
||||
keyPath: \.irLux,
|
||||
name: "IR Lux",
|
||||
abbreviatedName: "IR",
|
||||
minWidth: 30, maxWidth: 50,
|
||||
visible: false,
|
||||
tableBody: { _, lux in
|
||||
lux.map {
|
||||
Text("\($0.formatted(.number.grouping(.never).precision(.fractionLength(1))))")
|
||||
} ?? Text(Constants.nilValueIndicator)
|
||||
}),
|
||||
|
||||
// Radiation
|
||||
MetricsTableColumn(
|
||||
id: "radiation",
|
||||
keyPath: \.radiation,
|
||||
name: "Radiation",
|
||||
abbreviatedName: "☢️",
|
||||
minWidth: 30, maxWidth: 50,
|
||||
visible: false,
|
||||
tableBody: { _, radiation in
|
||||
radiation.map {
|
||||
if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac {
|
||||
Text(verbatim: "\($0.formatted(.number.grouping(.never).precision(.fractionLength(1)))) µR/h")
|
||||
} else {
|
||||
Text("\($0.formatted(.number.grouping(.never).precision(.fractionLength(1))))")
|
||||
}
|
||||
} ?? Text(Constants.nilValueIndicator)
|
||||
}),
|
||||
|
||||
// Wind Direction Series Configuration
|
||||
MetricsTableColumn(
|
||||
id: "windDirection",
|
||||
|
|
@ -127,6 +198,126 @@ extension MetricsColumnList {
|
|||
} ?? Text(verbatim: Constants.nilValueIndicator)
|
||||
}),
|
||||
|
||||
// Rainfall 1-hour
|
||||
MetricsTableColumn(
|
||||
id: "rainfall1H",
|
||||
keyPath: \.rainfall1H,
|
||||
name: "Rainfall (1H)",
|
||||
abbreviatedName: "Rain 1H",
|
||||
minWidth: 30, maxWidth: 60,
|
||||
visible: false,
|
||||
tableBody: { _, rainfall in
|
||||
rainfall.map {
|
||||
let rain = Measurement(
|
||||
value: Double($0), unit: UnitLength.millimeters)
|
||||
return Text(
|
||||
rain.formatted(
|
||||
.measurement(
|
||||
width: .abbreviated,
|
||||
numberFormatStyle: .number.grouping(.never)
|
||||
.precision(
|
||||
.fractionLength(0))))
|
||||
)
|
||||
} ?? Text(Constants.nilValueIndicator)
|
||||
}),
|
||||
|
||||
// Rainfall 24-hour
|
||||
MetricsTableColumn(
|
||||
id: "rainfall24H",
|
||||
keyPath: \.rainfall24H,
|
||||
name: "Rainfall (24H)",
|
||||
abbreviatedName: "Rain 24H",
|
||||
minWidth: 30, maxWidth: 60,
|
||||
visible: false,
|
||||
tableBody: { _, rainfall in
|
||||
rainfall.map {
|
||||
let rain = Measurement(
|
||||
value: Double($0), unit: UnitLength.millimeters)
|
||||
return Text(
|
||||
rain.formatted(
|
||||
.measurement(
|
||||
width: .abbreviated,
|
||||
numberFormatStyle: .number.grouping(.never)
|
||||
.precision(
|
||||
.fractionLength(0))))
|
||||
)
|
||||
} ?? Text(Constants.nilValueIndicator)
|
||||
}),
|
||||
|
||||
// Weight
|
||||
MetricsTableColumn(
|
||||
id: "weight",
|
||||
keyPath: \.weight,
|
||||
name: "Weight",
|
||||
abbreviatedName: "kg",
|
||||
minWidth: 30, maxWidth: 60,
|
||||
visible: false,
|
||||
tableBody: { _, weight in
|
||||
weight.map {
|
||||
let weight = Measurement(
|
||||
value: Double($0), unit: UnitMass.kilograms)
|
||||
return Text(
|
||||
weight.formatted(
|
||||
.measurement(
|
||||
width: .abbreviated,
|
||||
numberFormatStyle: .number.grouping(.never)
|
||||
.precision(
|
||||
.fractionLength(0))))
|
||||
)
|
||||
} ?? Text(Constants.nilValueIndicator)
|
||||
}),
|
||||
|
||||
// Distance sensor, often used for water level
|
||||
MetricsTableColumn(
|
||||
id: "distance",
|
||||
keyPath: \.distance,
|
||||
name: "Distance",
|
||||
abbreviatedName: "Dist",
|
||||
minWidth: 30, maxWidth: 50,
|
||||
visible: false,
|
||||
tableBody: { _, distance in
|
||||
distance.map {
|
||||
if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac {
|
||||
Text(verbatim: "\($0.formatted(.number.grouping(.never).precision(.fractionLength(1)))) mm")
|
||||
} else {
|
||||
Text("\($0.formatted(.number.grouping(.never).precision(.fractionLength(1))))")
|
||||
}
|
||||
} ?? Text(Constants.nilValueIndicator)
|
||||
}),
|
||||
|
||||
// Soil Temperature
|
||||
MetricsTableColumn(
|
||||
id: "soilTemperature",
|
||||
keyPath: \.soilTemperature,
|
||||
name: "Soil Temperature",
|
||||
abbreviatedName: "Soil Temp",
|
||||
minWidth: 30, maxWidth: 50,
|
||||
visible: false,
|
||||
tableBody: { _, soilTemperature in
|
||||
soilTemperature.map {
|
||||
Text($0.formattedTemperature())
|
||||
} ?? Text(verbatim: Constants.nilValueIndicator)
|
||||
|
||||
}),
|
||||
|
||||
// Soil Moisture
|
||||
MetricsTableColumn(
|
||||
id: "soilMoisture",
|
||||
keyPath: \.soilMoisture,
|
||||
name: "Soil Moisture",
|
||||
abbreviatedName: "Moist",
|
||||
minWidth: 30, maxWidth: 50,
|
||||
visible: false,
|
||||
tableBody: { _, moisture in
|
||||
moisture.map {
|
||||
if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac {
|
||||
Text("\($0.formatted(.number.grouping(.never).precision(.fractionLength(0))))%")
|
||||
} else {
|
||||
Text("\($0.formatted(.number.grouping(.never).precision(.fractionLength(0))))")
|
||||
}
|
||||
} ?? Text(Constants.nilValueIndicator)
|
||||
}),
|
||||
|
||||
// Timestamp Series Configuration -- for use in table only
|
||||
MetricsTableColumn(
|
||||
id: "time",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,522 @@
|
|||
//
|
||||
// EnvironmentDefaultSeries.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Created by Jake Bordens on 12/11/24.
|
||||
//
|
||||
|
||||
import Charts
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
// This is the default configuration used by the EnvironmentMetricsLog view for the chart
|
||||
extension MetricsSeriesList {
|
||||
static var environmentDefaultChartSeries: MetricsSeriesList {
|
||||
MetricsSeriesList([
|
||||
// Temperature Series Configuration
|
||||
MetricsChartSeries(
|
||||
id: "temperature",
|
||||
keyPath: \.temperature,
|
||||
name: "Temperature",
|
||||
abbreviatedName: "Temp",
|
||||
minumumYAxisSpan: 50.0,
|
||||
conversion: { t in t.map { Float($0.localeTemperature()) } },
|
||||
foregroundStyle: { chartRange in
|
||||
let locale = NSLocale.current as NSLocale
|
||||
let localeUnit = locale.object(forKey: NSLocale.Key(rawValue: "kCFLocaleTemperatureUnitKey"))
|
||||
let format: UnitTemperature = localeUnit as? String ?? "Celsius" == "Fahrenheit" ? .fahrenheit : .celsius
|
||||
let lowerBound = chartRange.map { Double($0.lowerBound) } ?? 0.0
|
||||
let upperBound = chartRange.map { Double($0.upperBound) } ?? 100.0
|
||||
let stops: [Gradient.Stop] = generateStops(minTemp: lowerBound, maxTemp: upperBound, tempUnit: format, opacity: 1.0)
|
||||
return LinearGradient(stops: stops, startPoint: .bottom, endPoint: .top)
|
||||
},
|
||||
chartBody: { series, chartRange, time, temperature in
|
||||
if let temperature {
|
||||
AreaMark(
|
||||
x: .value("Time", time),
|
||||
yStart: .value(series.abbreviatedName, chartRange?.lowerBound.doubleValue ?? 0.0),
|
||||
yEnd: .value(
|
||||
series.abbreviatedName, temperature.localeTemperature())
|
||||
)
|
||||
.interpolationMethod(.catmullRom)
|
||||
.foregroundStyle(by: .value("Series", series.abbreviatedName))
|
||||
.alignsMarkStylesWithPlotArea()
|
||||
.accessibilityHidden(true)
|
||||
.opacity(0.6)
|
||||
LineMark(
|
||||
x: .value("Time", time),
|
||||
y: .value(
|
||||
series.abbreviatedName, temperature.localeTemperature())
|
||||
)
|
||||
.interpolationMethod(.catmullRom)
|
||||
.foregroundStyle(by: .value("Series", series.abbreviatedName))
|
||||
.lineStyle(StrokeStyle(lineWidth: 4))
|
||||
.alignsMarkStylesWithPlotArea()
|
||||
}
|
||||
}),
|
||||
|
||||
// Relative Humidity Series Configuration
|
||||
MetricsChartSeries(
|
||||
id: "relativeHumidity",
|
||||
keyPath: \.relativeHumidity,
|
||||
name: "Relative Humidity",
|
||||
abbreviatedName: "Hum",
|
||||
initialYAxisRange: 0.0...100.0,
|
||||
foregroundStyle: { _ in
|
||||
.linearGradient(
|
||||
colors: [Color(UIColor.purple.darker(componentDelta: 0.2)), .purple],
|
||||
startPoint: .bottom, endPoint: .top
|
||||
)
|
||||
},
|
||||
chartBody: { series, _, time, humidity in
|
||||
if let humidity {
|
||||
LineMark(
|
||||
x: .value("Time", time),
|
||||
y: .value(series.abbreviatedName, humidity)
|
||||
)
|
||||
.interpolationMethod(.catmullRom)
|
||||
.foregroundStyle(by: .value("Series", series.abbreviatedName))
|
||||
.lineStyle(StrokeStyle(lineWidth: 4))
|
||||
.alignsMarkStylesWithPlotArea()
|
||||
}
|
||||
}),
|
||||
|
||||
// Barometric Pressure Series Configuration
|
||||
MetricsChartSeries(
|
||||
id: "barometricPressure",
|
||||
keyPath: \.barometricPressure,
|
||||
name: "Barometric Pressure",
|
||||
abbreviatedName: "Bar",
|
||||
visible: false,
|
||||
foregroundStyle: { _ in
|
||||
.linearGradient(
|
||||
colors: [Color(UIColor.green.darker(componentDelta: 0.3)), .green],
|
||||
startPoint: .bottom, endPoint: .top
|
||||
)
|
||||
},
|
||||
chartBody: { series, _, time, pressure in
|
||||
if let pressure {
|
||||
LineMark(
|
||||
x: .value("Time", time),
|
||||
y: .value(series.abbreviatedName, pressure)
|
||||
)
|
||||
.interpolationMethod(.catmullRom)
|
||||
.foregroundStyle(by: .value("Series", series.abbreviatedName))
|
||||
.lineStyle(StrokeStyle(lineWidth: 4))
|
||||
.alignsMarkStylesWithPlotArea()
|
||||
}
|
||||
}),
|
||||
|
||||
// Indoor Air Quality Series Configuration
|
||||
MetricsChartSeries(
|
||||
id: "iaq",
|
||||
keyPath: \.iaq,
|
||||
name: "Indoor Air Quality",
|
||||
abbreviatedName: "IAQ",
|
||||
visible: false,
|
||||
foregroundStyle: { _ in .gray },
|
||||
chartBody: { series, _, time, iaq in
|
||||
if let iaq {
|
||||
let iaqEnum = Iaq.getIaq(for: Int(iaq))
|
||||
PointMark(
|
||||
x: .value("Time", time),
|
||||
y: .value(series.abbreviatedName, Float(iaq))
|
||||
)
|
||||
.symbol(Circle())
|
||||
.foregroundStyle(iaqEnum.color)
|
||||
LineMark(
|
||||
x: .value("Time", time),
|
||||
y: .value(series.abbreviatedName, Float(iaq))
|
||||
)
|
||||
.interpolationMethod(.catmullRom)
|
||||
.foregroundStyle(by: .value("Series", series.abbreviatedName))
|
||||
.lineStyle(StrokeStyle(lineWidth: 4))
|
||||
.alignsMarkStylesWithPlotArea()
|
||||
}
|
||||
}),
|
||||
|
||||
// Lux
|
||||
MetricsChartSeries(
|
||||
id: "lux",
|
||||
keyPath: \.lux,
|
||||
name: "Lux",
|
||||
abbreviatedName: "Lux",
|
||||
visible: false,
|
||||
foregroundStyle: { _ in
|
||||
.linearGradient(
|
||||
colors: [Color(UIColor.cyan.lighter(componentDelta: 0.3)), .cyan],
|
||||
startPoint: .bottom, endPoint: .top
|
||||
)
|
||||
},
|
||||
chartBody: { series, _, time, lux in
|
||||
if let lux {
|
||||
LineMark(
|
||||
x: .value("Time", time),
|
||||
y: .value(series.abbreviatedName, lux)
|
||||
)
|
||||
.interpolationMethod(.catmullRom)
|
||||
.foregroundStyle(by: .value("Series", series.abbreviatedName))
|
||||
.lineStyle(StrokeStyle(lineWidth: 4))
|
||||
.alignsMarkStylesWithPlotArea()
|
||||
}
|
||||
}),
|
||||
|
||||
// White Lux
|
||||
MetricsChartSeries(
|
||||
id: "whiteLux",
|
||||
keyPath: \.whiteLux,
|
||||
name: "White Lux",
|
||||
abbreviatedName: "White",
|
||||
visible: false,
|
||||
foregroundStyle: { _ in
|
||||
.linearGradient(
|
||||
colors: [Color(UIColor.cyan.lighter(componentDelta: 0.5)), Color(UIColor.cyan.lighter(componentDelta: 0.2))],
|
||||
startPoint: .bottom, endPoint: .top
|
||||
)
|
||||
},
|
||||
chartBody: { series, _, time, lux in
|
||||
if let lux {
|
||||
LineMark(
|
||||
x: .value("Time", time),
|
||||
y: .value(series.abbreviatedName, lux)
|
||||
)
|
||||
.interpolationMethod(.catmullRom)
|
||||
.foregroundStyle(by: .value("Series", series.abbreviatedName))
|
||||
.lineStyle(StrokeStyle(lineWidth: 4))
|
||||
.alignsMarkStylesWithPlotArea()
|
||||
}
|
||||
}),
|
||||
|
||||
// UV Lux
|
||||
MetricsChartSeries(
|
||||
id: "uvLux",
|
||||
keyPath: \.uvLux,
|
||||
name: "UV Lux",
|
||||
abbreviatedName: "UV",
|
||||
visible: false,
|
||||
foregroundStyle: { _ in
|
||||
.linearGradient(
|
||||
colors: [Color(UIColor.systemIndigo.lighter(componentDelta: 0.4)), Color(UIColor.systemIndigo.lighter(componentDelta: 0.2))],
|
||||
startPoint: .bottom, endPoint: .top
|
||||
)
|
||||
},
|
||||
chartBody: { series, _, time, lux in
|
||||
if let lux {
|
||||
LineMark(
|
||||
x: .value("Time", time),
|
||||
y: .value(series.abbreviatedName, lux)
|
||||
)
|
||||
.interpolationMethod(.catmullRom)
|
||||
.foregroundStyle(by: .value("Series", series.abbreviatedName))
|
||||
.lineStyle(StrokeStyle(lineWidth: 4))
|
||||
.alignsMarkStylesWithPlotArea()
|
||||
}
|
||||
}),
|
||||
|
||||
// IR Lux
|
||||
MetricsChartSeries(
|
||||
id: "irLux",
|
||||
keyPath: \.irLux,
|
||||
name: "IR Lux",
|
||||
abbreviatedName: "IR",
|
||||
visible: false,
|
||||
foregroundStyle: { _ in
|
||||
.linearGradient(
|
||||
colors: [Color(UIColor.red.darker(componentDelta: 0.5)), .red],
|
||||
startPoint: .bottom, endPoint: .top
|
||||
)
|
||||
},
|
||||
chartBody: { series, _, time, lux in
|
||||
if let lux {
|
||||
LineMark(
|
||||
x: .value("Time", time),
|
||||
y: .value(series.abbreviatedName, lux)
|
||||
)
|
||||
.interpolationMethod(.catmullRom)
|
||||
.foregroundStyle(by: .value("Series", series.abbreviatedName))
|
||||
.lineStyle(StrokeStyle(lineWidth: 4))
|
||||
.alignsMarkStylesWithPlotArea()
|
||||
}
|
||||
}),
|
||||
|
||||
// Radiation
|
||||
MetricsChartSeries(
|
||||
id: "radiation",
|
||||
keyPath: \.radiation,
|
||||
name: "Radiation",
|
||||
abbreviatedName: "☢️",
|
||||
minumumYAxisSpan: 20.0,
|
||||
visible: false,
|
||||
foregroundStyle: { _ in
|
||||
.linearGradient(
|
||||
colors: [Color(UIColor.orange.darker(componentDelta: 0.4)), .orange],
|
||||
startPoint: .bottom, endPoint: .top
|
||||
)
|
||||
},
|
||||
chartBody: { series, _, time, radiation in
|
||||
if let radiation {
|
||||
LineMark(
|
||||
x: .value("Time", time),
|
||||
y: .value(series.abbreviatedName, radiation)
|
||||
)
|
||||
.interpolationMethod(.catmullRom)
|
||||
.foregroundStyle(by: .value("Series", series.abbreviatedName))
|
||||
.lineStyle(StrokeStyle(lineWidth: 4))
|
||||
.alignsMarkStylesWithPlotArea()
|
||||
}
|
||||
}),
|
||||
|
||||
// Combined Wind Speed and Direction Series Configuration -- For use in Chart only
|
||||
MetricsChartSeries(
|
||||
id: "windSpeedAndDirection",
|
||||
keyPath: \.windSpeedAndDirection,
|
||||
name: "Wind Speed/Direction",
|
||||
abbreviatedName: "Speed/Dir",
|
||||
visible: false,
|
||||
foregroundStyle: { _ in
|
||||
.linearGradient(
|
||||
colors: [Color(UIColor.yellow.darker(componentDelta: 0.3)), Color(UIColor.yellow.darker(componentDelta: 0.1))],
|
||||
startPoint: .bottom, endPoint: .top
|
||||
)
|
||||
},
|
||||
chartBody: { series, _, time, wsad in
|
||||
if let wsad {
|
||||
// debug data: var wsad = WindSpeedAndDirection(windSpeed:Float.random(in:0...25), windDirection: Int32.random(in:0..<3)*90 )
|
||||
LineMark(
|
||||
x: .value("Time", time),
|
||||
y: .value(series.abbreviatedName, wsad.windSpeed)
|
||||
)
|
||||
.interpolationMethod(.catmullRom)
|
||||
.foregroundStyle(by: .value("Series", series.abbreviatedName))
|
||||
.lineStyle(StrokeStyle(lineWidth: 4))
|
||||
.alignsMarkStylesWithPlotArea()
|
||||
PointMark(
|
||||
x: .value("Time", time),
|
||||
y: .value(series.abbreviatedName, wsad.windSpeed)
|
||||
)
|
||||
.symbol {
|
||||
if let wd = wsad.windDirection {
|
||||
Image(systemName: "location.north.circle.fill")
|
||||
.symbolRenderingMode(.palette)
|
||||
.foregroundStyle(Color.white, Color(UIColor.yellow.darker(componentDelta: 0.3)))
|
||||
.rotationEffect(
|
||||
.degrees(Double(wd)))
|
||||
}
|
||||
}.foregroundStyle(.yellow)
|
||||
}
|
||||
}),
|
||||
|
||||
// Rainfaill 1-hour
|
||||
MetricsChartSeries(
|
||||
id: "rainfall1H",
|
||||
keyPath: \.rainfall1H,
|
||||
name: "Rainfall 1H",
|
||||
abbreviatedName: "Rain 1H",
|
||||
visible: false,
|
||||
foregroundStyle: { _ in
|
||||
.linearGradient(
|
||||
colors: [Color(UIColor.systemBlue.darker(componentDelta: 0.5)), .blue],
|
||||
startPoint: .bottom, endPoint: .top
|
||||
)
|
||||
},
|
||||
chartBody: { series, _, time, rainfall in
|
||||
if let rainfall {
|
||||
LineMark(
|
||||
x: .value("Time", time),
|
||||
y: .value(series.abbreviatedName, rainfall)
|
||||
)
|
||||
.interpolationMethod(.catmullRom)
|
||||
.foregroundStyle(by: .value("Series", series.abbreviatedName))
|
||||
.lineStyle(StrokeStyle(lineWidth: 4))
|
||||
.alignsMarkStylesWithPlotArea()
|
||||
}
|
||||
}),
|
||||
|
||||
// Rainfaill 24-hour
|
||||
MetricsChartSeries(
|
||||
id: "rainfall24H",
|
||||
keyPath: \.rainfall24H,
|
||||
name: "Rainfall 24H",
|
||||
abbreviatedName: "Rain 24H",
|
||||
visible: false,
|
||||
foregroundStyle: { _ in
|
||||
.linearGradient(
|
||||
colors: [Color(UIColor.systemBlue.darker(componentDelta: 0.5)), .cyan],
|
||||
startPoint: .bottom, endPoint: .top
|
||||
)
|
||||
},
|
||||
chartBody: { series, _, time, rainfall in
|
||||
if let rainfall {
|
||||
LineMark(
|
||||
x: .value("Time", time),
|
||||
y: .value(series.abbreviatedName, rainfall)
|
||||
)
|
||||
.interpolationMethod(.catmullRom)
|
||||
.foregroundStyle(by: .value("Series", series.abbreviatedName))
|
||||
.lineStyle(StrokeStyle(lineWidth: 4))
|
||||
.alignsMarkStylesWithPlotArea()
|
||||
}
|
||||
}),
|
||||
|
||||
// Weight
|
||||
MetricsChartSeries(
|
||||
id: "weight",
|
||||
keyPath: \.weight,
|
||||
name: "Weight",
|
||||
abbreviatedName: "kg",
|
||||
visible: false,
|
||||
foregroundStyle: { _ in
|
||||
.linearGradient(
|
||||
colors: [Color(UIColor.systemPink.darker(componentDelta: 0.5)), .pink],
|
||||
startPoint: .bottom, endPoint: .top
|
||||
)
|
||||
},
|
||||
chartBody: { series, _, time, weight in
|
||||
if let weight {
|
||||
LineMark(
|
||||
x: .value("Time", time),
|
||||
y: .value(series.abbreviatedName, weight)
|
||||
)
|
||||
.interpolationMethod(.catmullRom)
|
||||
.foregroundStyle(by: .value("Series", series.abbreviatedName))
|
||||
.lineStyle(StrokeStyle(lineWidth: 4))
|
||||
.alignsMarkStylesWithPlotArea()
|
||||
}
|
||||
}),
|
||||
|
||||
// Distance
|
||||
MetricsChartSeries(
|
||||
id: "distance",
|
||||
keyPath: \.distance,
|
||||
name: "Distance",
|
||||
abbreviatedName: "Dist",
|
||||
visible: false,
|
||||
foregroundStyle: { _ in
|
||||
.linearGradient(
|
||||
colors: [Color(UIColor.systemTeal.darker(componentDelta: 0.7)), Color(UIColor.systemTeal)],
|
||||
startPoint: .bottom, endPoint: .top
|
||||
)
|
||||
},
|
||||
chartBody: { series, _, time, distance in
|
||||
if let distance {
|
||||
LineMark(
|
||||
x: .value("Time", time),
|
||||
y: .value(series.abbreviatedName, distance)
|
||||
)
|
||||
.interpolationMethod(.catmullRom)
|
||||
.foregroundStyle(by: .value("Series", series.abbreviatedName))
|
||||
.lineStyle(StrokeStyle(lineWidth: 4))
|
||||
.alignsMarkStylesWithPlotArea()
|
||||
}
|
||||
}),
|
||||
|
||||
// Soil Temperature
|
||||
MetricsChartSeries(
|
||||
id: "soilTemperature",
|
||||
keyPath: \.soilTemperature,
|
||||
name: "Soil Temperature",
|
||||
abbreviatedName: "Soil Temp",
|
||||
visible: false,
|
||||
foregroundStyle: { _ in
|
||||
.linearGradient(
|
||||
colors: [Color(UIColor.brown.darker(componentDelta: 0.4)), .brown],
|
||||
startPoint: .bottom, endPoint: .top
|
||||
)
|
||||
},
|
||||
chartBody: { series, _, time, soilTemp in
|
||||
if let soilTemp {
|
||||
LineMark(
|
||||
x: .value("Time", time),
|
||||
y: .value(series.abbreviatedName, soilTemp)
|
||||
)
|
||||
.interpolationMethod(.catmullRom)
|
||||
.foregroundStyle(by: .value("Series", series.abbreviatedName))
|
||||
.lineStyle(StrokeStyle(lineWidth: 4))
|
||||
.alignsMarkStylesWithPlotArea()
|
||||
}
|
||||
}),
|
||||
|
||||
// Soil Temperature
|
||||
MetricsChartSeries(
|
||||
id: "soilMoisture",
|
||||
keyPath: \.soilMoisture,
|
||||
name: "Soil Moisture",
|
||||
abbreviatedName: "Moist",
|
||||
visible: false,
|
||||
foregroundStyle: { _ in
|
||||
.linearGradient(
|
||||
colors: [Color(UIColor.blue.darker(componentDelta: 0.4)), .brown],
|
||||
startPoint: .bottom, endPoint: .top
|
||||
)
|
||||
},
|
||||
chartBody: { series, _, time, soilMoisture in
|
||||
if let soilMoisture {
|
||||
LineMark(
|
||||
x: .value("Time", time),
|
||||
y: .value(series.abbreviatedName, soilMoisture)
|
||||
)
|
||||
.interpolationMethod(.catmullRom)
|
||||
.foregroundStyle(by: .value("Series", series.abbreviatedName))
|
||||
.lineStyle(StrokeStyle(lineWidth: 4))
|
||||
.alignsMarkStylesWithPlotArea()
|
||||
}
|
||||
}),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
// Extension to combine windspeed and direction into one attribute for rendering
|
||||
// for rendering on the chart.
|
||||
@objc class WindSpeedAndDirection: NSObject, Plottable, Comparable {
|
||||
|
||||
let windSpeed: Float
|
||||
let windDirection: Int32?
|
||||
init(windSpeed: Float, windDirection: Int32?) {
|
||||
self.windSpeed = windSpeed
|
||||
self.windDirection = windDirection
|
||||
}
|
||||
|
||||
// Plottable Conformance
|
||||
required init?(primitivePlottable: Float) { nil }
|
||||
var primitivePlottable: Float { windSpeed }
|
||||
|
||||
static func < (lhs: WindSpeedAndDirection, rhs: WindSpeedAndDirection) -> Bool {
|
||||
lhs.windSpeed < rhs.windSpeed
|
||||
}
|
||||
}
|
||||
|
||||
@objc extension TelemetryEntity {
|
||||
var windSpeedAndDirection: WindSpeedAndDirection? {
|
||||
guard let windSpeed = self.windSpeed else { return nil }
|
||||
|
||||
return WindSpeedAndDirection(windSpeed: windSpeed, windDirection: self.windDirection)
|
||||
}
|
||||
}
|
||||
|
||||
// From: https://github.com/meshtastic/Meshtastic-Apple/pull/1013/commits/bc932567c742c8fa9fd30752237b10cb762c5ef3
|
||||
// Set up gradient stops relative to the scale of the temperature chart
|
||||
func generateStops(minTemp: Double, maxTemp: Double, tempUnit: UnitTemperature, opacity: Double) -> [Gradient.Stop] {
|
||||
var gradientStops = [Gradient.Stop]()
|
||||
|
||||
let stopTargets: [(Double, Color)] = [
|
||||
((tempUnit == .celsius ? 0 : 32), .blue),
|
||||
((tempUnit == .celsius ? 20 : 68), .yellow),
|
||||
((tempUnit == .celsius ? 30 : 86), .orange),
|
||||
((tempUnit == .celsius ? 55 : 125), .red)
|
||||
]
|
||||
for (stopValue, color) in stopTargets {
|
||||
let stopLocation = transform(stopValue, from: minTemp...maxTemp, to: 0...1)
|
||||
gradientStops.append(Gradient.Stop(color: color.opacity(opacity), location: stopLocation))
|
||||
}
|
||||
return gradientStops
|
||||
}
|
||||
|
||||
// Map inputRange to outputRange
|
||||
func transform<T: FloatingPoint>(_ input: T, from inputRange: ClosedRange<T>, to outputRange: ClosedRange<T>) -> T {
|
||||
// need to determine what that value would be in (to.low, to.high)
|
||||
// difference in output range / difference in input range = slope
|
||||
let slope = (outputRange.upperBound - outputRange.lowerBound) / (inputRange.upperBound - inputRange.lowerBound)
|
||||
// slope * normalized input + output lower
|
||||
let output = slope * (input - inputRange.lowerBound) + outputRange.lowerBound
|
||||
return output
|
||||
}
|
||||
|
|
@ -163,7 +163,7 @@ struct NodeDetail: View {
|
|||
}
|
||||
}
|
||||
|
||||
if let firstHeard = node.firstHeard, firstHeard.timeIntervalSince1970 > 0 {
|
||||
if let firstHeard = node.firstHeard, firstHeard.timeIntervalSince1970 > 0 && firstHeard < Calendar.current.date(byAdding: .year, value: 1, to: Date())! {
|
||||
HStack {
|
||||
Label {
|
||||
Text("First heard")
|
||||
|
|
@ -184,7 +184,7 @@ struct NodeDetail: View {
|
|||
}
|
||||
}
|
||||
|
||||
if let lastHeard = node.lastHeard, lastHeard.timeIntervalSince1970 > 0 {
|
||||
if let lastHeard = node.lastHeard, lastHeard.timeIntervalSince1970 > 0 && lastHeard < Calendar.current.date(byAdding: .year, value: 1, to: Date())! {
|
||||
HStack {
|
||||
Label {
|
||||
Text("Last heard")
|
||||
|
|
@ -195,8 +195,10 @@ struct NodeDetail: View {
|
|||
Spacer()
|
||||
|
||||
if dateFormatRelative, let text = Self.relativeFormatter.string(for: lastHeard) {
|
||||
Text(text)
|
||||
.textSelection(.enabled)
|
||||
if lastHeard.formatted() != "unknown.age".localized {
|
||||
Text(text)
|
||||
.textSelection(.enabled)
|
||||
}
|
||||
} else {
|
||||
Text(lastHeard.formatted())
|
||||
.textSelection(.enabled)
|
||||
|
|
@ -212,7 +214,7 @@ struct NodeDetail: View {
|
|||
// to use with WeatherKit, or has actual data in the most recent EnvironmentMetrics entity
|
||||
// that will be rendered in this section.
|
||||
if node.hasPositions && UserDefaults.environmentEnableWeatherKit
|
||||
|| node.hasDataForLatestEnvironmentMetrics(attributes: ["iaq", "temperature", "relativeHumidity", "barometricPressure", "windSpeed"]) {
|
||||
|| node.hasDataForLatestEnvironmentMetrics(attributes: ["iaq", "temperature", "relativeHumidity", "barometricPressure", "windSpeed", "radiation", "weight", "distance", "soilTemperature", "soilMoisture"]) {
|
||||
Section("Environment") {
|
||||
if !node.hasEnvironmentMetrics {
|
||||
LocalWeatherConditions(location: node.latestPosition?.nodeLocation)
|
||||
|
|
@ -245,6 +247,44 @@ struct NodeDetail: View {
|
|||
WindCompactWidget(speed: windSpeedMeasurement.formatted(.measurement(width: .abbreviated, numberFormatStyle: .number.precision(.fractionLength(0)))),
|
||||
gust: node.latestEnvironmentMetrics?.windGust ?? 0.0 > 0.0 ? windGust?.formatted(.measurement(width: .abbreviated, numberFormatStyle: .number.precision(.fractionLength(0)))) : "", direction: direction)
|
||||
}
|
||||
if let rainfall1h = node.latestEnvironmentMetrics?.rainfall1H {
|
||||
let locale = NSLocale.current as NSLocale
|
||||
let usesMetricSystem = locale.usesMetricSystem // Returns true for metric (mm), false for imperial (inches)
|
||||
let unit = usesMetricSystem ? UnitLength.millimeters : UnitLength.inches
|
||||
let unitLabel = usesMetricSystem ? "mm" : "in"
|
||||
let measurement = Measurement(value: Double(rainfall1h), unit: UnitLength.millimeters)
|
||||
let decimals = usesMetricSystem ? 0 : 1
|
||||
let formattedRain = measurement.converted(to: unit).value.formatted(.number.precision(.fractionLength(decimals)))
|
||||
RainfallCompactWidget(timespan: .rainfall1H, rainfall: formattedRain, unit: unitLabel)
|
||||
}
|
||||
if let rainfall24h = node.latestEnvironmentMetrics?.rainfall24H {
|
||||
let locale = NSLocale.current as NSLocale
|
||||
let usesMetricSystem = locale.usesMetricSystem // Returns true for metric (mm), false for imperial (inches)
|
||||
let unit = usesMetricSystem ? UnitLength.millimeters : UnitLength.inches
|
||||
let unitLabel = usesMetricSystem ? "mm" : "in"
|
||||
let measurement = Measurement(value: Double(rainfall24h), unit: UnitLength.millimeters)
|
||||
let decimals = usesMetricSystem ? 0 : 1
|
||||
let formattedRain = measurement.converted(to: unit).value.formatted(.number.precision(.fractionLength(decimals)))
|
||||
RainfallCompactWidget(timespan: .rainfall24H, rainfall: formattedRain, unit: unitLabel)
|
||||
}
|
||||
if let radiation = node.latestEnvironmentMetrics?.radiation {
|
||||
RadiationCompactWidget(radiation: radiation.formatted(.number.precision(.fractionLength(1))), unit: "µR/hr")
|
||||
}
|
||||
if let weight = node.latestEnvironmentMetrics?.weight {
|
||||
WeightCompactWidget(weight: weight.formatted(.number.precision(.fractionLength(1))), unit: "kg")
|
||||
}
|
||||
if let distance = node.latestEnvironmentMetrics?.distance {
|
||||
DistanceCompactWidget(distance: distance.formatted(.number.precision(.fractionLength(0))), unit: "mm")
|
||||
}
|
||||
if let soilTemperature = node.latestEnvironmentMetrics?.soilTemperature {
|
||||
let locale = NSLocale.current as NSLocale
|
||||
let localeUnit = locale.object(forKey: NSLocale.Key(rawValue: "kCFLocaleTemperatureUnitKey"))
|
||||
let unit = localeUnit as? String ?? "Celsius" == "Fahrenheit" ? "°F" : "°C"
|
||||
SoilTemperatureCompactWidget(temperature: soilTemperature.localeTemperature().formatted(.number.precision(.fractionLength(0))), unit: unit)
|
||||
}
|
||||
if let soilMoisture = node.latestEnvironmentMetrics?.soilMoisture {
|
||||
SoilMoistureCompactWidget(moisture: soilMoisture.formatted(.number.precision(.fractionLength(0))), unit: "%")
|
||||
}
|
||||
}
|
||||
.padding(node.latestEnvironmentMetrics?.iaq ?? -1 > 0 ? .bottom : .vertical)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,12 +22,12 @@ struct NodeInfoItem: View {
|
|||
if user.hwModel != "UNSET" {
|
||||
VStack(alignment: .center) {
|
||||
Spacer()
|
||||
Image(systemName: currentDevice?.activelySupported ?? false ? "checkmark.seal.fill" : "x.circle")
|
||||
Image(systemName: currentDevice?.activelySupported ?? false ? "checkmark.seal.fill" : "seal.fill")
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fill)
|
||||
.frame(width: 75, height: 75)
|
||||
.foregroundStyle(currentDevice?.activelySupported ?? false ? .green : .red)
|
||||
Text( currentDevice?.activelySupported ?? false ? "Supported" : "Unsupported")
|
||||
Text( currentDevice?.activelySupported ?? false ? "Full Support" : "Community Support")
|
||||
.foregroundStyle(.gray)
|
||||
.font(.callout)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ struct NodeListItem: View {
|
|||
let (image, color) = userKeyStatus
|
||||
IconAndText(systemName: image,
|
||||
imageColor: color,
|
||||
text: node.user?.longName ?? "unknown".localized,
|
||||
text: node.user?.longName?.addingVariationSelectors ?? "unknown".localized,
|
||||
textColor: .primary)
|
||||
if node.favorite {
|
||||
Spacer()
|
||||
|
|
@ -77,10 +77,10 @@ struct NodeListItem: View {
|
|||
imageColor: .green,
|
||||
text: "connected".localized)
|
||||
}
|
||||
if node.lastHeard?.timeIntervalSince1970 ?? 0 > 0 {
|
||||
if node.lastHeard?.timeIntervalSince1970 ?? 0 > 0 && node.lastHeard! < Calendar.current.date(byAdding: .year, value: 1, to: Date())!{
|
||||
IconAndText(systemName: node.isOnline ? "checkmark.circle.fill" : "moon.circle.fill",
|
||||
imageColor: node.isOnline ? .green : .orange,
|
||||
text: node.lastHeard?.formatted() ?? "unknown")
|
||||
text: node.lastHeard?.formatted() ?? "unknown.age".localized)
|
||||
}
|
||||
let role = DeviceRoles(rawValue: Int(node.user?.role ?? 0))
|
||||
IconAndText(systemName: role?.systemName ?? "figure",
|
||||
|
|
|
|||
|
|
@ -115,7 +115,7 @@ struct MeshMap: View {
|
|||
editingWaypoint!.longitudeI = Int32((newWaypointCoord?.longitude ?? 0) * 1e7)
|
||||
editingWaypoint!.expire = Date.now.addingTimeInterval(60 * 480)
|
||||
editingWaypoint!.id = 0
|
||||
Logger.services.debug("Long press occured at Lat: \(coordinate.latitude) Long: \(coordinate.longitude)")
|
||||
Logger.services.debug("Long press occured at Lat: \(coordinate.latitude, privacy: .public) Long: \(coordinate.longitude, privacy: .public)")
|
||||
default: return
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -237,7 +237,7 @@ struct NodeList: View {
|
|||
if deleteNode != nil {
|
||||
let success = bleManager.removeNode(node: deleteNode!, connectedNodeNum: Int64(bleManager.connectedPeripheral?.num ?? -1))
|
||||
if !success {
|
||||
Logger.data.error("Failed to delete node \(deleteNode?.user?.longName ?? "unknown".localized)")
|
||||
Logger.data.error("Failed to delete node \(deleteNode?.user?.longName ?? "unknown".localized, privacy: .public)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -264,7 +264,7 @@ struct NodeList: View {
|
|||
columnVisibility: columnVisibility
|
||||
)
|
||||
.edgesIgnoringSafeArea([.leading, .trailing])
|
||||
.navigationBarTitle(String(node.user?.longName ?? "unknown".localized), displayMode: .inline)
|
||||
.navigationBarTitle(String(node.user?.longName?.addingVariationSelectors ?? "unknown".localized), displayMode: .inline)
|
||||
.navigationBarItems(
|
||||
trailing: ZStack {
|
||||
if UIDevice.current.userInterfaceIdiom != .phone {
|
||||
|
|
|
|||
|
|
@ -176,7 +176,7 @@ struct PaxCounterLog: View {
|
|||
) {
|
||||
Button("paxcounter.delete", role: .destructive) {
|
||||
if clearPax(destNum: node.num, context: context) {
|
||||
Logger.services.info("Cleared Pax Counter for \(node.num)")
|
||||
Logger.services.info("Cleared Pax Counter for \(node.num, privacy: .public)")
|
||||
} else {
|
||||
Logger.services.error("Clear Pax Counter Log Failed")
|
||||
}
|
||||
|
|
@ -216,7 +216,7 @@ struct PaxCounterLog: View {
|
|||
self.isExporting = false
|
||||
Logger.services.info("PAX Counter log download succeeded")
|
||||
case .failure(let error):
|
||||
Logger.services.error("PAX Counter log download failed: \(error.localizedDescription)")
|
||||
Logger.services.error("PAX Counter log download failed: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -163,7 +163,7 @@ struct PositionLog: View {
|
|||
Logger.services.info("Position log download succeeded.")
|
||||
self.isExporting = false
|
||||
case .failure(let error):
|
||||
Logger.services.error("Position log download failed: \(error.localizedDescription)")
|
||||
Logger.services.error("Position log download failed: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -243,7 +243,7 @@ struct PowerMetricsLog: View {
|
|||
) {
|
||||
Button("Delete Power metrics?", role: .destructive) {
|
||||
if clearTelemetry(destNum: node.num, metricsType: 2, context: context) {
|
||||
Logger.data.notice("Cleared Power Metrics for \(node.num)")
|
||||
Logger.data.notice("Cleared Power Metrics for \(node.num, privacy: .public)")
|
||||
} else {
|
||||
Logger.data.error("Clear Power Metrics Log Failed")
|
||||
}
|
||||
|
|
@ -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)")
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ struct TraceRouteLog: View {
|
|||
do {
|
||||
try context.save()
|
||||
} catch let error as NSError {
|
||||
Logger.data.error("\(error.localizedDescription)")
|
||||
Logger.data.error("\(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
} label: {
|
||||
Label("delete", systemImage: "trash")
|
||||
|
|
|
|||
|
|
@ -25,34 +25,6 @@ struct AppData: View {
|
|||
GPSStatus()
|
||||
}
|
||||
Divider()
|
||||
Button(action: {
|
||||
let container = NSPersistentContainer(name: "Meshtastic")
|
||||
guard let url = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else {
|
||||
Logger.data.error("nil File path for back")
|
||||
return
|
||||
}
|
||||
do {
|
||||
try container.copyPersistentStores(to: url.appendingPathComponent("backup").appendingPathComponent("\(UserDefaults.preferredPeripheralNum)"), overwriting: true)
|
||||
loadFiles()
|
||||
Logger.data.notice("🗂️ Made a core data backup to backup/\(UserDefaults.preferredPeripheralNum)")
|
||||
} catch {
|
||||
Logger.data.error("🗂️ Core data backup copy error: \(error, privacy: .public)")
|
||||
}
|
||||
}) {
|
||||
Label {
|
||||
Text("Backup Database")
|
||||
.font(idiom == .phone ? .callout : .title)
|
||||
} icon: {
|
||||
Image(systemName: "cylinder.split.1x2")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.font(idiom == .phone ? .callout : .title)
|
||||
.frame(width: 35)
|
||||
}
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
.buttonBorderShape(.capsule)
|
||||
.controlSize(.large)
|
||||
Divider()
|
||||
}
|
||||
|
||||
List(files, id: \.self) { file in
|
||||
|
|
@ -62,20 +34,6 @@ struct AppData: View {
|
|||
Label {
|
||||
Text("Node Core Data Backup \(file.pathComponents[(idiom == .phone || idiom == .pad) ? 9 : 10])/\(file.lastPathComponent) - \(file.creationDate?.formatted() ?? "") - \(file.fileSizeString)")
|
||||
.swipeActions {
|
||||
Button(role: .none) {
|
||||
bleManager.disconnectPeripheral(reconnect: false)
|
||||
let container = NSPersistentContainer(name: "Meshtastic")
|
||||
do {
|
||||
try container.restorePersistentStore(from: file.absoluteURL)
|
||||
UserDefaults.preferredPeripheralId = ""
|
||||
UserDefaults.preferredPeripheralNum = Int(file.pathComponents[(idiom == .phone || idiom == .pad) ? 9 : 10]) ?? 0
|
||||
Logger.data.notice("🗂️ Restored a core data backup to backup/\(UserDefaults.preferredPeripheralNum, privacy: .public)")
|
||||
} catch {
|
||||
Logger.data.error("🗂️ Core data restore copy error: \(error, privacy: .public)")
|
||||
}
|
||||
} label: {
|
||||
Label("restore", systemImage: "arrow.counterclockwise")
|
||||
}
|
||||
Button(role: .destructive) {
|
||||
do {
|
||||
try FileManager.default.removeItem(at: file)
|
||||
|
|
|
|||
|
|
@ -178,7 +178,7 @@ struct AppLog: View {
|
|||
self.isExporting = false
|
||||
Logger.services.info("Application log download succeeded.")
|
||||
case .failure(let error):
|
||||
Logger.services.error("Application log download failed: \(error.localizedDescription)")
|
||||
Logger.services.error("Application log download failed: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -186,11 +186,11 @@ struct Channels: View {
|
|||
if channel.role != Channel.Role.disabled {
|
||||
do {
|
||||
try context.save()
|
||||
Logger.data.info("💾 Saved Channel: \(channel.settings.name)")
|
||||
Logger.data.info("💾 Saved Channel: \(channel.settings.name, privacy: .public)")
|
||||
} catch {
|
||||
context.rollback()
|
||||
let nsError = error as NSError
|
||||
Logger.data.error("Unresolved Core Data error in the channel editor. Error: \(nsError)")
|
||||
Logger.data.error("Unresolved Core Data error in the channel editor. Error: \(nsError, privacy: .public)")
|
||||
}
|
||||
} else {
|
||||
let objects = selectedChannel?.allPrivateMessages ?? []
|
||||
|
|
@ -203,11 +203,11 @@ struct Channels: View {
|
|||
context.delete(selectedChannel!)
|
||||
do {
|
||||
try context.save()
|
||||
Logger.data.info("💾 Deleted Channel: \(channel.settings.name)")
|
||||
Logger.data.info("💾 Deleted Channel: \(channel.settings.name, privacy: .public)")
|
||||
} catch {
|
||||
context.rollback()
|
||||
let nsError = error as NSError
|
||||
Logger.data.error("Unresolved Core Data error in the channel editor. Error: \(nsError)")
|
||||
Logger.data.error("Unresolved Core Data error in the channel editor. Error: \(nsError, privacy: .public)")
|
||||
}
|
||||
}
|
||||
let adminMessageId = bleManager.saveChannel(channel: channel, fromUser: node!.user!, toUser: node!.user!)
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ struct DeviceConfig: View {
|
|||
@State private var isPresentingFactoryResetConfirm = false
|
||||
@State var hasChanges = false
|
||||
@State var deviceRole = 0
|
||||
@State private var pendingDeviceRole = 0
|
||||
@State var buzzerGPIO = 0
|
||||
@State var buttonGPIO = 0
|
||||
@State var rebroadcastMode = 0
|
||||
|
|
@ -29,6 +30,10 @@ struct DeviceConfig: View {
|
|||
@State var ledHeartbeatEnabled = true
|
||||
@State var tripleClickAsAdHocPing = true
|
||||
@State var tzdef = ""
|
||||
|
||||
@State private var showRouterWarning = false
|
||||
@State private var confirmWarning = false
|
||||
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
|
|
@ -39,9 +44,37 @@ struct DeviceConfig: View {
|
|||
VStack(alignment: .leading) {
|
||||
Picker("Device Role", selection: $deviceRole ) {
|
||||
ForEach(DeviceRoles.allCases) { dr in
|
||||
Text(dr.name)
|
||||
Text(dr.name).tag(dr.rawValue as Int)
|
||||
}
|
||||
}
|
||||
.onChange(of: deviceRole) { oldValue, newValue in
|
||||
if !confirmWarning {
|
||||
if [2, 11].contains(newValue) {
|
||||
pendingDeviceRole = newValue
|
||||
deviceRole = oldValue // Reset selection until confirmed
|
||||
showRouterWarning = true
|
||||
}
|
||||
} else {
|
||||
confirmWarning = false
|
||||
}
|
||||
}
|
||||
.confirmationDialog(
|
||||
"Are you sure?",
|
||||
isPresented: $showRouterWarning,
|
||||
titleVisibility: .visible
|
||||
) {
|
||||
|
||||
Button("Confirm") {
|
||||
deviceRole = pendingDeviceRole
|
||||
pendingDeviceRole = 0
|
||||
confirmWarning = true
|
||||
}
|
||||
Button("Cancel", role: .cancel) {
|
||||
pendingDeviceRole = 0
|
||||
}
|
||||
} message: {
|
||||
Text("The Router roles are designed for high vantage locations like mountaintops and towers. This node needs to be able to have a good direct connection to most of the nodes on the network or else this will significantly hurt the network.")
|
||||
}
|
||||
Text(DeviceRoles(rawValue: deviceRole)?.description ?? "")
|
||||
.foregroundColor(.gray)
|
||||
.font(.callout)
|
||||
|
|
|
|||
|
|
@ -249,7 +249,6 @@ struct LoRaConfig: View {
|
|||
let expiration = node.sessionExpiration ?? Date()
|
||||
if expiration < Date() || node.loRaConfig == nil {
|
||||
Logger.mesh.info("⚙️ Empty or expired lora config requesting via PKI admin")
|
||||
|
||||
_ = bleManager.requestLoRaConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0)
|
||||
}
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -230,31 +230,88 @@ struct MQTTConfig: View {
|
|||
}
|
||||
.scrollDismissesKeyboard(.interactively)
|
||||
.disabled(self.bleManager.connectedPeripheral == nil || node?.mqttConfig == nil)
|
||||
}
|
||||
|
||||
SaveConfigButton(node: node, hasChanges: $hasChanges) {
|
||||
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1, context: context)
|
||||
if connectedNode != nil {
|
||||
var mqtt = ModuleConfig.MQTTConfig()
|
||||
mqtt.enabled = self.enabled
|
||||
mqtt.proxyToClientEnabled = self.proxyToClientEnabled
|
||||
mqtt.address = self.address
|
||||
mqtt.username = self.username
|
||||
mqtt.password = self.password
|
||||
mqtt.root = self.root
|
||||
mqtt.encryptionEnabled = self.encryptionEnabled
|
||||
mqtt.jsonEnabled = self.jsonEnabled
|
||||
mqtt.tlsEnabled = self.tlsEnabled
|
||||
mqtt.mapReportingEnabled = self.mapReportingEnabled
|
||||
mqtt.mapReportSettings.positionPrecision = UInt32(self.mapPositionPrecision)
|
||||
mqtt.mapReportSettings.publishIntervalSecs = UInt32(self.mapPublishIntervalSecs)
|
||||
let adminMessageId = bleManager.saveMQTTConfig(config: mqtt, fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0)
|
||||
if adminMessageId > 0 {
|
||||
// Should show a saved successfully alert once I know that to be true
|
||||
// for now just disable the button after a successful save
|
||||
hasChanges = false
|
||||
goBack()
|
||||
SaveConfigButton(node: node, hasChanges: $hasChanges) {
|
||||
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1, context: context)
|
||||
if connectedNode != nil {
|
||||
var mqtt = ModuleConfig.MQTTConfig()
|
||||
mqtt.enabled = self.enabled
|
||||
mqtt.proxyToClientEnabled = self.proxyToClientEnabled
|
||||
mqtt.address = self.address
|
||||
mqtt.username = self.username
|
||||
mqtt.password = self.password
|
||||
mqtt.root = self.root
|
||||
mqtt.encryptionEnabled = self.encryptionEnabled
|
||||
mqtt.jsonEnabled = self.jsonEnabled
|
||||
mqtt.tlsEnabled = self.tlsEnabled
|
||||
mqtt.mapReportingEnabled = self.mapReportingEnabled
|
||||
mqtt.mapReportSettings.positionPrecision = UInt32(self.mapPositionPrecision)
|
||||
mqtt.mapReportSettings.publishIntervalSecs = UInt32(self.mapPublishIntervalSecs)
|
||||
let adminMessageId = bleManager.saveMQTTConfig(config: mqtt, fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0)
|
||||
if adminMessageId > 0 {
|
||||
// Should show a saved successfully alert once I know that to be true
|
||||
// for now just disable the button after a successful save
|
||||
hasChanges = false
|
||||
goBack()
|
||||
}
|
||||
}
|
||||
}.onChange(of: enabled) { _, newEnabled in
|
||||
if newEnabled != node?.mqttConfig?.enabled { hasChanges = true }
|
||||
}
|
||||
.onChange(of: proxyToClientEnabled) { _, newProxyToClientEnabled in
|
||||
if newProxyToClientEnabled {
|
||||
jsonEnabled = false
|
||||
tlsEnabled = false
|
||||
}
|
||||
if newProxyToClientEnabled != node?.mqttConfig?.proxyToClientEnabled { hasChanges = true }
|
||||
}
|
||||
.onChange(of: address) { _, newAddress in
|
||||
if newAddress != node?.mqttConfig?.address ?? "" { hasChanges = true }
|
||||
}
|
||||
.onChange(of: username) { _, newUsername in
|
||||
if newUsername != node?.mqttConfig?.username ?? "" { hasChanges = true }
|
||||
}
|
||||
.onChange(of: password) { _, newPassword in
|
||||
if newPassword != node?.mqttConfig?.password ?? "" { hasChanges = true }
|
||||
}
|
||||
.onChange(of: root) { _, newRoot in
|
||||
if newRoot != node?.mqttConfig?.root ?? "" { hasChanges = true }
|
||||
}
|
||||
.onChange(of: selectedTopic) { _, newSelectedTopic in
|
||||
root = newSelectedTopic
|
||||
}
|
||||
.onChange(of: encryptionEnabled) { _, newEncryptionEnabled in
|
||||
if newEncryptionEnabled != node?.mqttConfig?.encryptionEnabled { hasChanges = true }
|
||||
}
|
||||
.onChange(of: jsonEnabled) { _, newJsonEnabled in
|
||||
if newJsonEnabled {
|
||||
proxyToClientEnabled = false
|
||||
}
|
||||
if newJsonEnabled != node?.mqttConfig?.jsonEnabled { hasChanges = true }
|
||||
}
|
||||
.onChange(of: tlsEnabled) { _, newTlsEnabled in
|
||||
if address.lowercased() == "mqtt.meshtastic.org" {
|
||||
tlsEnabled = false
|
||||
} else {
|
||||
if newTlsEnabled != node?.mqttConfig?.tlsEnabled { hasChanges = true }
|
||||
}
|
||||
}
|
||||
.onChange(of: mqttConnected) { _, newMqttConnected in
|
||||
if newMqttConnected == false {
|
||||
if bleManager.mqttProxyConnected {
|
||||
bleManager.mqttManager.disconnect()
|
||||
}
|
||||
} else {
|
||||
if !bleManager.mqttProxyConnected && node != nil {
|
||||
bleManager.mqttManager.connectFromConfigSettings(node: node!)
|
||||
}
|
||||
}
|
||||
}
|
||||
.onChange(of: mapReportingEnabled) { _, newMapReportingEnabled in
|
||||
if newMapReportingEnabled != node?.mqttConfig?.mapReportingEnabled { hasChanges = true }
|
||||
}
|
||||
.onChange(of: mapPublishIntervalSecs) { _, newMapPublishIntervalSecs in
|
||||
if newMapPublishIntervalSecs != node?.mqttConfig?.mapPublishIntervalSecs ?? -1 { hasChanges = true }
|
||||
}
|
||||
}
|
||||
.navigationTitle("mqtt.config")
|
||||
|
|
@ -267,64 +324,6 @@ struct MQTTConfig: View {
|
|||
)
|
||||
}
|
||||
)
|
||||
.onChange(of: enabled) { _, newEnabled in
|
||||
if newEnabled != node?.mqttConfig?.enabled { hasChanges = true }
|
||||
}
|
||||
.onChange(of: proxyToClientEnabled) { _, newProxyToClientEnabled in
|
||||
if newProxyToClientEnabled {
|
||||
jsonEnabled = false
|
||||
tlsEnabled = false
|
||||
}
|
||||
if newProxyToClientEnabled != node?.mqttConfig?.proxyToClientEnabled { hasChanges = true }
|
||||
}
|
||||
.onChange(of: address) { newAddress in
|
||||
if newAddress != node?.mqttConfig?.address ?? "" { hasChanges = true }
|
||||
}
|
||||
.onChange(of: username) { newUsername in
|
||||
if newUsername != node?.mqttConfig?.username ?? "" { hasChanges = true }
|
||||
}
|
||||
.onChange(of: password) { newPassword in
|
||||
if newPassword != node?.mqttConfig?.password ?? "" { hasChanges = true }
|
||||
}
|
||||
.onChange(of: root) { _, newRoot in
|
||||
if newRoot != node?.mqttConfig?.root ?? "" { hasChanges = true }
|
||||
}
|
||||
.onChange(of: selectedTopic) { _, newSelectedTopic in
|
||||
root = newSelectedTopic
|
||||
}
|
||||
.onChange(of: encryptionEnabled) { _, newEncryptionEnabled in
|
||||
if newEncryptionEnabled != node?.mqttConfig?.encryptionEnabled { hasChanges = true }
|
||||
}
|
||||
.onChange(of: jsonEnabled) { _, newJsonEnabled in
|
||||
if newJsonEnabled {
|
||||
proxyToClientEnabled = false
|
||||
}
|
||||
if newJsonEnabled != node?.mqttConfig?.jsonEnabled { hasChanges = true }
|
||||
}
|
||||
.onChange(of: tlsEnabled) { _, newTlsEnabled in
|
||||
if address.lowercased() == "mqtt.meshtastic.org" {
|
||||
tlsEnabled = false
|
||||
} else {
|
||||
if newTlsEnabled != node?.mqttConfig?.tlsEnabled { hasChanges = true }
|
||||
}
|
||||
}
|
||||
.onChange(of: mqttConnected) { _, newMqttConnected in
|
||||
if newMqttConnected == false {
|
||||
if bleManager.mqttProxyConnected {
|
||||
bleManager.mqttManager.disconnect()
|
||||
}
|
||||
} else {
|
||||
if !bleManager.mqttProxyConnected && node != nil {
|
||||
bleManager.mqttManager.connectFromConfigSettings(node: node!)
|
||||
}
|
||||
}
|
||||
}
|
||||
.onChange(of: mapReportingEnabled) { _, newMapReportingEnabled in
|
||||
if newMapReportingEnabled != node?.mqttConfig?.mapReportingEnabled { hasChanges = true }
|
||||
}
|
||||
.onChange(of: mapPublishIntervalSecs) { _, newMapPublishIntervalSecs in
|
||||
if newMapPublishIntervalSecs != node?.mqttConfig?.mapPublishIntervalSecs ?? -1 { hasChanges = true }
|
||||
}
|
||||
.onFirstAppear {
|
||||
// Need to request a MqttModuleConfig from the remote node before allowing changes
|
||||
if let connectedPeripheral = bleManager.connectedPeripheral, let node {
|
||||
|
|
@ -348,6 +347,7 @@ struct MQTTConfig: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func setMqttValues() {
|
||||
|
||||
nearbyTopics = []
|
||||
|
|
@ -357,7 +357,7 @@ struct MQTTConfig: View {
|
|||
defaultTopic = "msh/" + (region?.topic ?? "UNSET")
|
||||
geocoder.reverseGeocodeLocation(LocationsHandler.shared.locationsArray.first!, completionHandler: {(placemarks, error) in
|
||||
if let error {
|
||||
Logger.services.error("Failed to reverse geocode location: \(error.localizedDescription)")
|
||||
Logger.services.error("Failed to reverse geocode location: \(error.localizedDescription, privacy: .public)")
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -24,66 +24,78 @@ struct NetworkConfig: View {
|
|||
@State var ntpServer = ""
|
||||
@State var ethEnabled = false
|
||||
@State var ethMode = 0
|
||||
@State var udpEnabled = false
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
Form {
|
||||
ConfigHeader(title: "Network", config: \.networkConfig, node: node, onAppear: setNetworkValues)
|
||||
|
||||
if node != nil && node?.metadata?.hasWifi ?? false {
|
||||
Section(header: Text("WiFi Options")) {
|
||||
if let node {
|
||||
if node.metadata?.hasWifi ?? false {
|
||||
Section(header: Text("WiFi Options")) {
|
||||
|
||||
Toggle(isOn: $wifiEnabled) {
|
||||
Label("enabled", systemImage: "wifi")
|
||||
Text("Enabling WiFi will disable the bluetooth connection to the app.")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
Toggle(isOn: $wifiEnabled) {
|
||||
Label("enabled", systemImage: "wifi")
|
||||
Text("Enabling WiFi will disable the bluetooth connection to the app.")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
|
||||
HStack {
|
||||
Label("ssid", systemImage: "network")
|
||||
TextField("ssid", text: $wifiSsid)
|
||||
.foregroundColor(.gray)
|
||||
.autocapitalization(.none)
|
||||
.disableAutocorrection(true)
|
||||
.onChange(of: wifiSsid) {
|
||||
var totalBytes = wifiSsid.utf8.count
|
||||
// Only mess with the value if it is too big
|
||||
while totalBytes > 32 {
|
||||
wifiSsid = String(wifiSsid.dropLast())
|
||||
totalBytes = wifiSsid.utf8.count
|
||||
HStack {
|
||||
Label("ssid", systemImage: "network")
|
||||
TextField("ssid", text: $wifiSsid)
|
||||
.foregroundColor(.gray)
|
||||
.autocapitalization(.none)
|
||||
.disableAutocorrection(true)
|
||||
.onChange(of: wifiSsid) {
|
||||
var totalBytes = wifiSsid.utf8.count
|
||||
// Only mess with the value if it is too big
|
||||
while totalBytes > 32 {
|
||||
wifiSsid = String(wifiSsid.dropLast())
|
||||
totalBytes = wifiSsid.utf8.count
|
||||
}
|
||||
hasChanges = true
|
||||
}
|
||||
hasChanges = true
|
||||
}
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
.keyboardType(.default)
|
||||
HStack {
|
||||
Label("password", systemImage: "wallet.pass")
|
||||
TextField("password", text: $wifiPsk)
|
||||
.foregroundColor(.gray)
|
||||
.autocapitalization(.none)
|
||||
.disableAutocorrection(true)
|
||||
.onChange(of: wifiPsk) {
|
||||
var totalBytes = wifiPsk.utf8.count
|
||||
// Only mess with the value if it is too big
|
||||
while totalBytes > 63 {
|
||||
wifiPsk = String(wifiPsk.dropLast())
|
||||
totalBytes = wifiPsk.utf8.count
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
.keyboardType(.default)
|
||||
HStack {
|
||||
Label("password", systemImage: "wallet.pass")
|
||||
TextField("password", text: $wifiPsk)
|
||||
.foregroundColor(.gray)
|
||||
.autocapitalization(.none)
|
||||
.disableAutocorrection(true)
|
||||
.onChange(of: wifiPsk) {
|
||||
var totalBytes = wifiPsk.utf8.count
|
||||
// Only mess with the value if it is too big
|
||||
while totalBytes > 63 {
|
||||
wifiPsk = String(wifiPsk.dropLast())
|
||||
totalBytes = wifiPsk.utf8.count
|
||||
}
|
||||
hasChanges = true
|
||||
}
|
||||
hasChanges = true
|
||||
}
|
||||
.foregroundColor(.gray)
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
.keyboardType(.default)
|
||||
}
|
||||
.keyboardType(.default)
|
||||
}
|
||||
}
|
||||
if node != nil && node?.metadata?.hasEthernet ?? false {
|
||||
Section(header: Text("Ethernet Options")) {
|
||||
Toggle(isOn: $ethEnabled) {
|
||||
Label("enabled", systemImage: "network")
|
||||
Text("Enabling Ethernet will disable the bluetooth connection to the app.")
|
||||
if node.metadata?.hasEthernet ?? false {
|
||||
Section(header: Text("Ethernet Options")) {
|
||||
Toggle(isOn: $ethEnabled) {
|
||||
Label("enabled", systemImage: "network")
|
||||
Text("Enabling Ethernet will disable the bluetooth connection to the app.")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
}
|
||||
}
|
||||
|
||||
if node.metadata?.hasEthernet ?? false || node.metadata?.hasWifi ?? false {
|
||||
Section(header: Text("UDP Broadcast")) {
|
||||
Toggle(isOn: $udpEnabled) {
|
||||
Label("enabled", systemImage: "point.3.connected.trianglepath.dotted")
|
||||
Text("Enable broadcasting packets via UDP over the local network.")
|
||||
}
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -98,6 +110,7 @@ struct NetworkConfig: View {
|
|||
network.wifiSsid = self.wifiSsid
|
||||
network.wifiPsk = self.wifiPsk
|
||||
network.ethEnabled = self.ethEnabled
|
||||
network.enabledProtocols = self.udpEnabled ? UInt32(Config.NetworkConfig.ProtocolFlags.udpBroadcast.rawValue) : UInt32(Config.NetworkConfig.ProtocolFlags.noBroadcast.rawValue)
|
||||
// network.addressMode = Config.NetworkConfig.AddressMode.dhcp
|
||||
|
||||
let adminMessageId = bleManager.saveNetworkConfig(config: network, fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0)
|
||||
|
|
@ -166,14 +179,30 @@ struct NetworkConfig: View {
|
|||
}
|
||||
.onChange(of: ethEnabled) { _, newEthEnabled in
|
||||
if newEthEnabled != node?.networkConfig?.ethEnabled { hasChanges = true }
|
||||
}.onChange(of: udpEnabled) {_, newUdpEnabled in
|
||||
if let netConfig = node?.networkConfig {
|
||||
let newValue: UInt32
|
||||
if newUdpEnabled {
|
||||
newValue = UInt32(netConfig.enabledProtocols) | UInt32(Config.NetworkConfig.ProtocolFlags.udpBroadcast.rawValue)
|
||||
} else {
|
||||
newValue = UInt32(netConfig.enabledProtocols) & ~UInt32(Config.NetworkConfig.ProtocolFlags.udpBroadcast.rawValue)
|
||||
}
|
||||
if netConfig.enabledProtocols != Int32(newValue) {
|
||||
netConfig.enabledProtocols = Int32(newValue)
|
||||
hasChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func setNetworkValues() {
|
||||
self.wifiEnabled = node?.networkConfig?.wifiEnabled ?? false
|
||||
self.wifiSsid = node?.networkConfig?.wifiSsid ?? ""
|
||||
self.wifiPsk = node?.networkConfig?.wifiPsk ?? ""
|
||||
self.wifiMode = Int(node?.networkConfig?.wifiMode ?? 0)
|
||||
self.ethEnabled = node?.networkConfig?.ethEnabled ?? false
|
||||
let enabledProtocols = UInt32(node?.networkConfig?.enabledProtocols ?? Int32(Config.NetworkConfig.ProtocolFlags.noBroadcast.rawValue))
|
||||
self.udpEnabled = enabledProtocols & UInt32(Config.NetworkConfig.ProtocolFlags.udpBroadcast.rawValue) != 0
|
||||
self.hasChanges = false
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -530,7 +530,7 @@ struct PositionConfig: View {
|
|||
} catch {
|
||||
context.rollback()
|
||||
let nsError = error as NSError
|
||||
Logger.data.error("Error Saving Position Config Entity \(nsError)")
|
||||
Logger.data.error("Error Saving Position Config Entity \(nsError, privacy: .public)")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -550,7 +550,7 @@ struct PositionConfig: View {
|
|||
} catch {
|
||||
context.rollback()
|
||||
let nsError = error as NSError
|
||||
Logger.data.error("Error Saving Position Config Entity \(nsError)")
|
||||
Logger.data.error("Error Saving Position Config Entity \(nsError, privacy: .public)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -138,7 +138,7 @@ struct Firmware: View {
|
|||
.font(.callout)
|
||||
.padding(.bottom)
|
||||
} else {
|
||||
Text("OTA Updates are not supported on the this NRF Device.")
|
||||
Text("OTA Updates are not supported on this NRF Device.")
|
||||
.font(.title3)
|
||||
Link("Drag & Drop Firmware Update", destination: URL(string: "https://meshtastic.org/docs/getting-started/flashing-firmware/nrf52/drag-n-drop")!)
|
||||
.font(.callout)
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ class Api: ObservableObject {
|
|||
completion(deviceHardware)
|
||||
}
|
||||
} catch {
|
||||
Logger.services.error("JSON decode failure: \(error.localizedDescription)")
|
||||
Logger.services.error("JSON decode failure: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
@ -82,7 +82,7 @@ class Api: ObservableObject {
|
|||
completion(firmwareReleases)
|
||||
}
|
||||
} catch {
|
||||
Logger.services.error("JSON decode failure: \(error.localizedDescription)")
|
||||
Logger.services.error("JSON decode failure: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -188,7 +188,7 @@ struct RouteRecorder: View {
|
|||
} catch {
|
||||
context.rollback()
|
||||
let nsError = error as NSError
|
||||
Logger.data.error("Error Saving RouteEntity from the Route Recorder \(nsError)")
|
||||
Logger.data.error("Error Saving RouteEntity from the Route Recorder \(nsError, privacy: .public)")
|
||||
}
|
||||
} label: {
|
||||
Label("start", systemImage: "play")
|
||||
|
|
@ -246,7 +246,7 @@ struct RouteRecorder: View {
|
|||
} catch {
|
||||
context.rollback()
|
||||
let nsError = error as NSError
|
||||
Logger.data.error("Error Saving RouteEntity from the Route Recorder \(nsError)")
|
||||
Logger.data.error("Error Saving RouteEntity from the Route Recorder \(nsError, privacy: .public)")
|
||||
}
|
||||
isShowingDetails = false
|
||||
} label: {
|
||||
|
|
@ -298,11 +298,10 @@ struct RouteRecorder: View {
|
|||
do {
|
||||
try context.save()
|
||||
Logger.data.info("💾 Saved a new route location")
|
||||
// logger.info("💾 Updated Canned Messages Messages For: \(fetchedNode[0].num)")
|
||||
} catch {
|
||||
context.rollback()
|
||||
let nsError = error as NSError
|
||||
Logger.data.error("Error Saving LocationEntity from the Route Recorder \(nsError)")
|
||||
Logger.data.error("Error Saving LocationEntity from the Route Recorder \(nsError, privacy: .public)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ struct Routes: View {
|
|||
var latIndex = -1
|
||||
var longIndex = -1
|
||||
for index in headers!.indices {
|
||||
Logger.services.debug("\(index): \( headers![index])")
|
||||
Logger.services.debug("\(index, privacy: .public): \( headers![index], privacy: .public)")
|
||||
if headers![index].trimmingCharacters(in: .whitespaces) == "Latitude" {
|
||||
latIndex = index
|
||||
} else if headers![index].trimmingCharacters(in: .whitespaces) == "Longitude" {
|
||||
|
|
@ -94,7 +94,7 @@ struct Routes: View {
|
|||
do {
|
||||
try context.save()
|
||||
} catch let error as NSError {
|
||||
Logger.services.error("\(error.localizedDescription)")
|
||||
Logger.services.error("\(error.localizedDescription, privacy: .public)")
|
||||
isShowingBadFileAlert = true
|
||||
}
|
||||
} else {
|
||||
|
|
@ -103,11 +103,11 @@ struct Routes: View {
|
|||
|
||||
} catch {
|
||||
// TODO: deal with errors
|
||||
Logger.services.error("\(error.localizedDescription)")
|
||||
Logger.services.error("\(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
|
||||
} catch {
|
||||
Logger.services.error("CSV Import Error: \(error.localizedDescription)")
|
||||
Logger.services.error("CSV Import Error: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
List(routes, id: \.self, selection: $selectedRoute) { route in
|
||||
|
|
@ -151,7 +151,7 @@ struct Routes: View {
|
|||
do {
|
||||
try context.save()
|
||||
} catch let error as NSError {
|
||||
Logger.data.error("\(error.localizedDescription)")
|
||||
Logger.data.error("\(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
} label: {
|
||||
Label("delete", systemImage: "trash")
|
||||
|
|
@ -227,7 +227,7 @@ struct Routes: View {
|
|||
} catch {
|
||||
context.rollback()
|
||||
let nsError = error as NSError
|
||||
Logger.data.error("Error Saving RouteEntity from the Route Editor \(nsError)")
|
||||
Logger.data.error("Error Saving RouteEntity from the Route Editor \(nsError, privacy: .public)")
|
||||
}
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
|
|
@ -300,7 +300,7 @@ struct Routes: View {
|
|||
self.isExporting = false
|
||||
Logger.services.info("Route log download succeeded.")
|
||||
case .failure(let error):
|
||||
Logger.services.error("Route log download failed: \(error.localizedDescription).")
|
||||
Logger.services.error("Route log download failed: \(error.localizedDescription, privacy: .public).")
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
import SwiftUI
|
||||
import OSLog
|
||||
import TipKit
|
||||
import MeshtasticProtobufs
|
||||
|
||||
struct Settings: View {
|
||||
@Environment(\.managedObjectContext) var context
|
||||
|
|
@ -25,10 +26,21 @@ struct Settings: View {
|
|||
|
||||
@State private var selectedNode: Int = 0
|
||||
@State private var preferredNodeNum: Int = 0
|
||||
@State private var moduleOverride: Bool = false
|
||||
|
||||
@ObservedObject
|
||||
var router: Router
|
||||
|
||||
// MARK: Helper
|
||||
|
||||
private func isModuleSupported(_ module: ExcludedModules) -> Bool {
|
||||
return moduleOverride || Int(nodes.first(where: { $0.num == preferredNodeNum })?.metadata?.excludedModules ?? Int32.zero) & module.rawValue == 0
|
||||
}
|
||||
|
||||
private func isAnySupported(_ modules: [ExcludedModules]) -> Bool {
|
||||
return modules.map(isModuleSupported).contains(true)
|
||||
}
|
||||
|
||||
// MARK: Views
|
||||
|
||||
var radioConfigurationSection: some View {
|
||||
|
|
@ -153,57 +165,68 @@ struct Settings: View {
|
|||
}
|
||||
|
||||
var moduleConfigurationSection: some View {
|
||||
Section("module.configuration") {
|
||||
NavigationLink(value: SettingsNavigationState.ambientLighting) {
|
||||
Label {
|
||||
Text("Ambient Lighting")
|
||||
} icon: {
|
||||
Image(systemName: "light.max")
|
||||
Section {
|
||||
if isModuleSupported(.ambientlightingConfig) {
|
||||
NavigationLink(value: SettingsNavigationState.ambientLighting) {
|
||||
Label {
|
||||
Text("Ambient Lighting")
|
||||
} icon: {
|
||||
Image(systemName: "light.max")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NavigationLink(value: SettingsNavigationState.cannedMessages) {
|
||||
Label {
|
||||
Text("Canned Messages")
|
||||
} icon: {
|
||||
Image(systemName: "list.bullet.rectangle.fill")
|
||||
if isModuleSupported(.cannedmsgConfig) {
|
||||
NavigationLink(value: SettingsNavigationState.cannedMessages) {
|
||||
Label {
|
||||
Text("Canned Messages")
|
||||
} icon: {
|
||||
Image(systemName: "list.bullet.rectangle.fill")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NavigationLink(value: SettingsNavigationState.detectionSensor) {
|
||||
Label {
|
||||
Text("detection.sensor")
|
||||
} icon: {
|
||||
Image(systemName: "sensor")
|
||||
if isModuleSupported(.detectionsensorConfig) {
|
||||
NavigationLink(value: SettingsNavigationState.detectionSensor) {
|
||||
Label {
|
||||
Text("detection.sensor")
|
||||
} icon: {
|
||||
Image(systemName: "sensor")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NavigationLink(value: SettingsNavigationState.externalNotification) {
|
||||
Label {
|
||||
Text("external.notification")
|
||||
} icon: {
|
||||
Image(systemName: "megaphone")
|
||||
if isModuleSupported(.extnotifConfig) {
|
||||
NavigationLink(value: SettingsNavigationState.externalNotification) {
|
||||
Label {
|
||||
Text("external.notification")
|
||||
} icon: {
|
||||
Image(systemName: "megaphone")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NavigationLink(value: SettingsNavigationState.mqtt) {
|
||||
Label {
|
||||
Text("mqtt")
|
||||
} icon: {
|
||||
Image(systemName: "dot.radiowaves.up.forward")
|
||||
if isModuleSupported(.mqttConfig) {
|
||||
NavigationLink(value: SettingsNavigationState.mqtt) {
|
||||
Label {
|
||||
Text("mqtt")
|
||||
} icon: {
|
||||
Image(systemName: "dot.radiowaves.up.forward")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NavigationLink(value: SettingsNavigationState.rangeTest) {
|
||||
Label {
|
||||
Text("range.test")
|
||||
} icon: {
|
||||
Image(systemName: "point.3.connected.trianglepath.dotted")
|
||||
if isModuleSupported(.rangetestConfig) {
|
||||
NavigationLink(value: SettingsNavigationState.rangeTest) {
|
||||
Label {
|
||||
Text("range.test")
|
||||
} icon: {
|
||||
Image(systemName: "point.3.connected.trianglepath.dotted")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let node = nodes.first(where: { $0.num == preferredNodeNum }),
|
||||
node.metadata?.hasWifi ?? false {
|
||||
if isModuleSupported(.paxcounterConfig) {
|
||||
NavigationLink(value: SettingsNavigationState.paxCounter) {
|
||||
Label {
|
||||
Text("config.module.paxcounter.settings")
|
||||
|
|
@ -213,37 +236,61 @@ struct Settings: View {
|
|||
}
|
||||
}
|
||||
|
||||
NavigationLink(value: SettingsNavigationState.ringtone) {
|
||||
Label {
|
||||
Text("ringtone")
|
||||
} icon: {
|
||||
Image(systemName: "music.note.list")
|
||||
if isModuleSupported(.audioConfig) {
|
||||
NavigationLink(value: SettingsNavigationState.ringtone) {
|
||||
Label {
|
||||
Text("ringtone")
|
||||
} icon: {
|
||||
Image(systemName: "music.note.list")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NavigationLink(value: SettingsNavigationState.serial) {
|
||||
Label {
|
||||
Text("serial")
|
||||
} icon: {
|
||||
Image(systemName: "terminal")
|
||||
if isModuleSupported(.serialConfig) {
|
||||
NavigationLink(value: SettingsNavigationState.serial) {
|
||||
Label {
|
||||
Text("serial")
|
||||
} icon: {
|
||||
Image(systemName: "terminal")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NavigationLink(value: SettingsNavigationState.storeAndForward) {
|
||||
Label {
|
||||
Text("Store & Forward")
|
||||
} icon: {
|
||||
Image(systemName: "envelope.arrow.triangle.branch")
|
||||
if isModuleSupported(.storeforwardConfig) {
|
||||
NavigationLink(value: SettingsNavigationState.storeAndForward) {
|
||||
Label {
|
||||
Text("Store & Forward")
|
||||
} icon: {
|
||||
Image(systemName: "envelope.arrow.triangle.branch")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NavigationLink(value: SettingsNavigationState.telemetry) {
|
||||
Label {
|
||||
Text("telemetry")
|
||||
} icon: {
|
||||
Image(systemName: "chart.xyaxis.line")
|
||||
if isModuleSupported(.telemetryConfig) {
|
||||
NavigationLink(value: SettingsNavigationState.telemetry) {
|
||||
Label {
|
||||
Text("telemetry")
|
||||
} icon: {
|
||||
Image(systemName: "chart.xyaxis.line")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update this list with the modules that are shown above. If all are not supported
|
||||
// Then show a message.
|
||||
if !isAnySupported([.ambientlightingConfig, .cannedmsgConfig,
|
||||
.detectionsensorConfig, .extnotifConfig,
|
||||
.mqttConfig, .rangetestConfig, .paxcounterConfig,
|
||||
.audioConfig, .serialConfig, .storeforwardConfig,
|
||||
.telemetryConfig]) {
|
||||
Text("This node does not support any configurable modules.")
|
||||
}
|
||||
} header: {
|
||||
Text("module.configuration")
|
||||
} footer: {
|
||||
if moduleOverride {
|
||||
Text("Currently showing modules that may not be supported by this node.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -341,7 +388,7 @@ struct Settings: View {
|
|||
/// Connected Node
|
||||
if node.num == bleManager.connectedPeripheral?.num ?? 0 {
|
||||
Label {
|
||||
Text("BLE: \(node.user?.longName ?? "unknown".localized)")
|
||||
Text("BLE: \(node.user?.longName?.addingVariationSelectors ?? "unknown".localized)")
|
||||
} icon: {
|
||||
Image(systemName: "antenna.radiowaves.left.and.right")
|
||||
}
|
||||
|
|
@ -363,14 +410,14 @@ struct Settings: View {
|
|||
.tag(Int(node.num))
|
||||
} else if UserDefaults.enableAdministration && node.user?.pkiEncrypted ?? false {
|
||||
Label {
|
||||
Text("Request PKI Admin: \(node.user?.longName ?? "unknown".localized)")
|
||||
Text("Request PKI Admin: \(node.user?.longName?.addingVariationSelectors ?? "unknown".localized)")
|
||||
} icon: {
|
||||
Image(systemName: "rectangle.and.hand.point.up.left")
|
||||
}
|
||||
.tag(Int(node.num))
|
||||
} else if !UserDefaults.enableAdministration {
|
||||
Label {
|
||||
Text("Request Legacy Admin: \(node.user?.longName ?? "unknown".localized)")
|
||||
Text("Request Legacy Admin: \(node.user?.longName?.addingVariationSelectors ?? "unknown".localized)")
|
||||
} icon: {
|
||||
Image(systemName: "rectangle.and.hand.point.up.left")
|
||||
}
|
||||
|
|
@ -395,7 +442,7 @@ struct Settings: View {
|
|||
TipView(AdminChannelTip(), arrowEdge: .top)
|
||||
} else {
|
||||
if bleManager.connectedPeripheral != nil {
|
||||
Text("Connected Node \(node?.user?.longName ?? "unknown".localized)")
|
||||
Text("Connected Node \(node?.user?.longName?.addingVariationSelectors ?? "unknown".localized)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -497,7 +544,10 @@ struct Settings: View {
|
|||
}
|
||||
.navigationTitle("settings")
|
||||
.navigationBarItems(
|
||||
leading: MeshtasticLogo()
|
||||
leading: MeshtasticLogo().onLongPressGesture(minimumDuration: 1.0) {
|
||||
self.moduleOverride.toggle()
|
||||
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,12 +50,14 @@ struct UserConfig: View {
|
|||
|
||||
TextField("Long Name", text: $longName)
|
||||
.onChange(of: longName) {
|
||||
var totalBytes = longName.utf8.count
|
||||
var newValue = longName.withoutVariationSelectors
|
||||
var totalBytes = newValue.utf8.count
|
||||
// Only mess with the value if it is too big
|
||||
while totalBytes > (isLicensed ? 6 : 36) {
|
||||
longName = String(longName.dropLast())
|
||||
totalBytes = longName.utf8.count
|
||||
newValue = String(newValue.dropLast())
|
||||
totalBytes = newValue.utf8.count
|
||||
}
|
||||
longName = newValue
|
||||
}
|
||||
}
|
||||
.keyboardType(.default)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
// DO NOT EDIT.
|
||||
// swift-format-ignore-file
|
||||
// swiftlint:disable all
|
||||
//
|
||||
// Generated by the Swift generator plugin for the protocol buffer compiler.
|
||||
// Source: meshtastic/admin.proto
|
||||
|
|
@ -24,7 +25,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP
|
|||
/// This message is handled by the Admin module and is responsible for all settings/channel read/write operations.
|
||||
/// This message is used to do settings operations to both remote AND local nodes.
|
||||
/// (Prior to 1.2 these operations were done via special ToRadio operations)
|
||||
public struct AdminMessage {
|
||||
public struct AdminMessage: @unchecked Sendable {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
// methods supported on all messages.
|
||||
|
|
@ -261,6 +262,36 @@ public struct AdminMessage {
|
|||
set {payloadVariant = .setScale(newValue)}
|
||||
}
|
||||
|
||||
///
|
||||
/// Backup the node's preferences
|
||||
public var backupPreferences: AdminMessage.BackupLocation {
|
||||
get {
|
||||
if case .backupPreferences(let v)? = payloadVariant {return v}
|
||||
return .flash
|
||||
}
|
||||
set {payloadVariant = .backupPreferences(newValue)}
|
||||
}
|
||||
|
||||
///
|
||||
/// Restore the node's preferences
|
||||
public var restorePreferences: AdminMessage.BackupLocation {
|
||||
get {
|
||||
if case .restorePreferences(let v)? = payloadVariant {return v}
|
||||
return .flash
|
||||
}
|
||||
set {payloadVariant = .restorePreferences(newValue)}
|
||||
}
|
||||
|
||||
///
|
||||
/// Remove backups of the node's preferences
|
||||
public var removeBackupPreferences: AdminMessage.BackupLocation {
|
||||
get {
|
||||
if case .removeBackupPreferences(let v)? = payloadVariant {return v}
|
||||
return .flash
|
||||
}
|
||||
set {payloadVariant = .removeBackupPreferences(newValue)}
|
||||
}
|
||||
|
||||
///
|
||||
/// Set the owner for this node
|
||||
public var setOwner: User {
|
||||
|
|
@ -533,7 +564,7 @@ public struct AdminMessage {
|
|||
|
||||
///
|
||||
/// TODO: REPLACE
|
||||
public enum OneOf_PayloadVariant: Equatable {
|
||||
public enum OneOf_PayloadVariant: Equatable, Sendable {
|
||||
///
|
||||
/// Send the specified channel in the response to this message
|
||||
/// NOTE: This field is sent with the channel index + 1 (to ensure we never try to send 'zero' - which protobufs treats as not present)
|
||||
|
|
@ -603,6 +634,15 @@ public struct AdminMessage {
|
|||
/// Set zero and offset for scale chips
|
||||
case setScale(UInt32)
|
||||
///
|
||||
/// Backup the node's preferences
|
||||
case backupPreferences(AdminMessage.BackupLocation)
|
||||
///
|
||||
/// Restore the node's preferences
|
||||
case restorePreferences(AdminMessage.BackupLocation)
|
||||
///
|
||||
/// Remove backups of the node's preferences
|
||||
case removeBackupPreferences(AdminMessage.BackupLocation)
|
||||
///
|
||||
/// Set the owner for this node
|
||||
case setOwner(User)
|
||||
///
|
||||
|
|
@ -689,213 +729,11 @@ public struct AdminMessage {
|
|||
/// Tell the node to reset the nodedb.
|
||||
case nodedbReset(Int32)
|
||||
|
||||
#if !swift(>=4.1)
|
||||
public static func ==(lhs: AdminMessage.OneOf_PayloadVariant, rhs: AdminMessage.OneOf_PayloadVariant) -> Bool {
|
||||
// The use of inline closures is to circumvent an issue where the compiler
|
||||
// allocates stack space for every case branch when no optimizations are
|
||||
// enabled. https://github.com/apple/swift-protobuf/issues/1034
|
||||
switch (lhs, rhs) {
|
||||
case (.getChannelRequest, .getChannelRequest): return {
|
||||
guard case .getChannelRequest(let l) = lhs, case .getChannelRequest(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.getChannelResponse, .getChannelResponse): return {
|
||||
guard case .getChannelResponse(let l) = lhs, case .getChannelResponse(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.getOwnerRequest, .getOwnerRequest): return {
|
||||
guard case .getOwnerRequest(let l) = lhs, case .getOwnerRequest(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.getOwnerResponse, .getOwnerResponse): return {
|
||||
guard case .getOwnerResponse(let l) = lhs, case .getOwnerResponse(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.getConfigRequest, .getConfigRequest): return {
|
||||
guard case .getConfigRequest(let l) = lhs, case .getConfigRequest(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.getConfigResponse, .getConfigResponse): return {
|
||||
guard case .getConfigResponse(let l) = lhs, case .getConfigResponse(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.getModuleConfigRequest, .getModuleConfigRequest): return {
|
||||
guard case .getModuleConfigRequest(let l) = lhs, case .getModuleConfigRequest(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.getModuleConfigResponse, .getModuleConfigResponse): return {
|
||||
guard case .getModuleConfigResponse(let l) = lhs, case .getModuleConfigResponse(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.getCannedMessageModuleMessagesRequest, .getCannedMessageModuleMessagesRequest): return {
|
||||
guard case .getCannedMessageModuleMessagesRequest(let l) = lhs, case .getCannedMessageModuleMessagesRequest(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.getCannedMessageModuleMessagesResponse, .getCannedMessageModuleMessagesResponse): return {
|
||||
guard case .getCannedMessageModuleMessagesResponse(let l) = lhs, case .getCannedMessageModuleMessagesResponse(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.getDeviceMetadataRequest, .getDeviceMetadataRequest): return {
|
||||
guard case .getDeviceMetadataRequest(let l) = lhs, case .getDeviceMetadataRequest(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.getDeviceMetadataResponse, .getDeviceMetadataResponse): return {
|
||||
guard case .getDeviceMetadataResponse(let l) = lhs, case .getDeviceMetadataResponse(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.getRingtoneRequest, .getRingtoneRequest): return {
|
||||
guard case .getRingtoneRequest(let l) = lhs, case .getRingtoneRequest(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.getRingtoneResponse, .getRingtoneResponse): return {
|
||||
guard case .getRingtoneResponse(let l) = lhs, case .getRingtoneResponse(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.getDeviceConnectionStatusRequest, .getDeviceConnectionStatusRequest): return {
|
||||
guard case .getDeviceConnectionStatusRequest(let l) = lhs, case .getDeviceConnectionStatusRequest(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.getDeviceConnectionStatusResponse, .getDeviceConnectionStatusResponse): return {
|
||||
guard case .getDeviceConnectionStatusResponse(let l) = lhs, case .getDeviceConnectionStatusResponse(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.setHamMode, .setHamMode): return {
|
||||
guard case .setHamMode(let l) = lhs, case .setHamMode(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.getNodeRemoteHardwarePinsRequest, .getNodeRemoteHardwarePinsRequest): return {
|
||||
guard case .getNodeRemoteHardwarePinsRequest(let l) = lhs, case .getNodeRemoteHardwarePinsRequest(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.getNodeRemoteHardwarePinsResponse, .getNodeRemoteHardwarePinsResponse): return {
|
||||
guard case .getNodeRemoteHardwarePinsResponse(let l) = lhs, case .getNodeRemoteHardwarePinsResponse(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.enterDfuModeRequest, .enterDfuModeRequest): return {
|
||||
guard case .enterDfuModeRequest(let l) = lhs, case .enterDfuModeRequest(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.deleteFileRequest, .deleteFileRequest): return {
|
||||
guard case .deleteFileRequest(let l) = lhs, case .deleteFileRequest(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.setScale, .setScale): return {
|
||||
guard case .setScale(let l) = lhs, case .setScale(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.setOwner, .setOwner): return {
|
||||
guard case .setOwner(let l) = lhs, case .setOwner(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.setChannel, .setChannel): return {
|
||||
guard case .setChannel(let l) = lhs, case .setChannel(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.setConfig, .setConfig): return {
|
||||
guard case .setConfig(let l) = lhs, case .setConfig(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.setModuleConfig, .setModuleConfig): return {
|
||||
guard case .setModuleConfig(let l) = lhs, case .setModuleConfig(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.setCannedMessageModuleMessages, .setCannedMessageModuleMessages): return {
|
||||
guard case .setCannedMessageModuleMessages(let l) = lhs, case .setCannedMessageModuleMessages(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.setRingtoneMessage, .setRingtoneMessage): return {
|
||||
guard case .setRingtoneMessage(let l) = lhs, case .setRingtoneMessage(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.removeByNodenum, .removeByNodenum): return {
|
||||
guard case .removeByNodenum(let l) = lhs, case .removeByNodenum(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.setFavoriteNode, .setFavoriteNode): return {
|
||||
guard case .setFavoriteNode(let l) = lhs, case .setFavoriteNode(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.removeFavoriteNode, .removeFavoriteNode): return {
|
||||
guard case .removeFavoriteNode(let l) = lhs, case .removeFavoriteNode(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.setFixedPosition, .setFixedPosition): return {
|
||||
guard case .setFixedPosition(let l) = lhs, case .setFixedPosition(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.removeFixedPosition, .removeFixedPosition): return {
|
||||
guard case .removeFixedPosition(let l) = lhs, case .removeFixedPosition(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.setTimeOnly, .setTimeOnly): return {
|
||||
guard case .setTimeOnly(let l) = lhs, case .setTimeOnly(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.getUiConfigRequest, .getUiConfigRequest): return {
|
||||
guard case .getUiConfigRequest(let l) = lhs, case .getUiConfigRequest(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.getUiConfigResponse, .getUiConfigResponse): return {
|
||||
guard case .getUiConfigResponse(let l) = lhs, case .getUiConfigResponse(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.storeUiConfig, .storeUiConfig): return {
|
||||
guard case .storeUiConfig(let l) = lhs, case .storeUiConfig(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.setIgnoredNode, .setIgnoredNode): return {
|
||||
guard case .setIgnoredNode(let l) = lhs, case .setIgnoredNode(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.removeIgnoredNode, .removeIgnoredNode): return {
|
||||
guard case .removeIgnoredNode(let l) = lhs, case .removeIgnoredNode(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.beginEditSettings, .beginEditSettings): return {
|
||||
guard case .beginEditSettings(let l) = lhs, case .beginEditSettings(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.commitEditSettings, .commitEditSettings): return {
|
||||
guard case .commitEditSettings(let l) = lhs, case .commitEditSettings(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.factoryResetDevice, .factoryResetDevice): return {
|
||||
guard case .factoryResetDevice(let l) = lhs, case .factoryResetDevice(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.rebootOtaSeconds, .rebootOtaSeconds): return {
|
||||
guard case .rebootOtaSeconds(let l) = lhs, case .rebootOtaSeconds(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.exitSimulator, .exitSimulator): return {
|
||||
guard case .exitSimulator(let l) = lhs, case .exitSimulator(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.rebootSeconds, .rebootSeconds): return {
|
||||
guard case .rebootSeconds(let l) = lhs, case .rebootSeconds(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.shutdownSeconds, .shutdownSeconds): return {
|
||||
guard case .shutdownSeconds(let l) = lhs, case .shutdownSeconds(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.factoryResetConfig, .factoryResetConfig): return {
|
||||
guard case .factoryResetConfig(let l) = lhs, case .factoryResetConfig(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.nodedbReset, .nodedbReset): return {
|
||||
guard case .nodedbReset(let l) = lhs, case .nodedbReset(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
///
|
||||
/// TODO: REPLACE
|
||||
public enum ConfigType: SwiftProtobuf.Enum {
|
||||
public enum ConfigType: SwiftProtobuf.Enum, Swift.CaseIterable {
|
||||
public typealias RawValue = Int
|
||||
|
||||
///
|
||||
|
|
@ -929,6 +767,9 @@ public struct AdminMessage {
|
|||
///
|
||||
/// TODO: REPLACE
|
||||
case securityConfig // = 7
|
||||
|
||||
///
|
||||
/// Session key config
|
||||
case sessionkeyConfig // = 8
|
||||
|
||||
///
|
||||
|
|
@ -972,11 +813,25 @@ public struct AdminMessage {
|
|||
}
|
||||
}
|
||||
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
public static let allCases: [AdminMessage.ConfigType] = [
|
||||
.deviceConfig,
|
||||
.positionConfig,
|
||||
.powerConfig,
|
||||
.networkConfig,
|
||||
.displayConfig,
|
||||
.loraConfig,
|
||||
.bluetoothConfig,
|
||||
.securityConfig,
|
||||
.sessionkeyConfig,
|
||||
.deviceuiConfig,
|
||||
]
|
||||
|
||||
}
|
||||
|
||||
///
|
||||
/// TODO: REPLACE
|
||||
public enum ModuleConfigType: SwiftProtobuf.Enum {
|
||||
public enum ModuleConfigType: SwiftProtobuf.Enum, Swift.CaseIterable {
|
||||
public typealias RawValue = Int
|
||||
|
||||
///
|
||||
|
|
@ -1074,53 +929,71 @@ public struct AdminMessage {
|
|||
}
|
||||
}
|
||||
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
public static let allCases: [AdminMessage.ModuleConfigType] = [
|
||||
.mqttConfig,
|
||||
.serialConfig,
|
||||
.extnotifConfig,
|
||||
.storeforwardConfig,
|
||||
.rangetestConfig,
|
||||
.telemetryConfig,
|
||||
.cannedmsgConfig,
|
||||
.audioConfig,
|
||||
.remotehardwareConfig,
|
||||
.neighborinfoConfig,
|
||||
.ambientlightingConfig,
|
||||
.detectionsensorConfig,
|
||||
.paxcounterConfig,
|
||||
]
|
||||
|
||||
}
|
||||
|
||||
public enum BackupLocation: SwiftProtobuf.Enum, Swift.CaseIterable {
|
||||
public typealias RawValue = Int
|
||||
|
||||
///
|
||||
/// Backup to the internal flash
|
||||
case flash // = 0
|
||||
|
||||
///
|
||||
/// Backup to the SD card
|
||||
case sd // = 1
|
||||
case UNRECOGNIZED(Int)
|
||||
|
||||
public init() {
|
||||
self = .flash
|
||||
}
|
||||
|
||||
public init?(rawValue: Int) {
|
||||
switch rawValue {
|
||||
case 0: self = .flash
|
||||
case 1: self = .sd
|
||||
default: self = .UNRECOGNIZED(rawValue)
|
||||
}
|
||||
}
|
||||
|
||||
public var rawValue: Int {
|
||||
switch self {
|
||||
case .flash: return 0
|
||||
case .sd: return 1
|
||||
case .UNRECOGNIZED(let i): return i
|
||||
}
|
||||
}
|
||||
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
public static let allCases: [AdminMessage.BackupLocation] = [
|
||||
.flash,
|
||||
.sd,
|
||||
]
|
||||
|
||||
}
|
||||
|
||||
public init() {}
|
||||
}
|
||||
|
||||
#if swift(>=4.2)
|
||||
|
||||
extension AdminMessage.ConfigType: CaseIterable {
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
public static let allCases: [AdminMessage.ConfigType] = [
|
||||
.deviceConfig,
|
||||
.positionConfig,
|
||||
.powerConfig,
|
||||
.networkConfig,
|
||||
.displayConfig,
|
||||
.loraConfig,
|
||||
.bluetoothConfig,
|
||||
.securityConfig,
|
||||
.sessionkeyConfig,
|
||||
.deviceuiConfig,
|
||||
]
|
||||
}
|
||||
|
||||
extension AdminMessage.ModuleConfigType: CaseIterable {
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
public static let allCases: [AdminMessage.ModuleConfigType] = [
|
||||
.mqttConfig,
|
||||
.serialConfig,
|
||||
.extnotifConfig,
|
||||
.storeforwardConfig,
|
||||
.rangetestConfig,
|
||||
.telemetryConfig,
|
||||
.cannedmsgConfig,
|
||||
.audioConfig,
|
||||
.remotehardwareConfig,
|
||||
.neighborinfoConfig,
|
||||
.ambientlightingConfig,
|
||||
.detectionsensorConfig,
|
||||
.paxcounterConfig,
|
||||
]
|
||||
}
|
||||
|
||||
#endif // swift(>=4.2)
|
||||
|
||||
///
|
||||
/// Parameters for setting up Meshtastic for ameteur radio usage
|
||||
public struct HamParameters {
|
||||
public struct HamParameters: Sendable {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
// methods supported on all messages.
|
||||
|
|
@ -1150,7 +1023,7 @@ public struct HamParameters {
|
|||
|
||||
///
|
||||
/// Response envelope for node_remote_hardware_pins
|
||||
public struct NodeRemoteHardwarePinsResponse {
|
||||
public struct NodeRemoteHardwarePinsResponse: Sendable {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
// methods supported on all messages.
|
||||
|
|
@ -1164,15 +1037,6 @@ public struct NodeRemoteHardwarePinsResponse {
|
|||
public init() {}
|
||||
}
|
||||
|
||||
#if swift(>=5.5) && canImport(_Concurrency)
|
||||
extension AdminMessage: @unchecked Sendable {}
|
||||
extension AdminMessage.OneOf_PayloadVariant: @unchecked Sendable {}
|
||||
extension AdminMessage.ConfigType: @unchecked Sendable {}
|
||||
extension AdminMessage.ModuleConfigType: @unchecked Sendable {}
|
||||
extension HamParameters: @unchecked Sendable {}
|
||||
extension NodeRemoteHardwarePinsResponse: @unchecked Sendable {}
|
||||
#endif // swift(>=5.5) && canImport(_Concurrency)
|
||||
|
||||
// MARK: - Code below here is support for the SwiftProtobuf runtime.
|
||||
|
||||
fileprivate let _protobuf_package = "meshtastic"
|
||||
|
|
@ -1203,6 +1067,9 @@ extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat
|
|||
21: .standard(proto: "enter_dfu_mode_request"),
|
||||
22: .standard(proto: "delete_file_request"),
|
||||
23: .standard(proto: "set_scale"),
|
||||
24: .standard(proto: "backup_preferences"),
|
||||
25: .standard(proto: "restore_preferences"),
|
||||
26: .standard(proto: "remove_backup_preferences"),
|
||||
32: .standard(proto: "set_owner"),
|
||||
33: .standard(proto: "set_channel"),
|
||||
34: .standard(proto: "set_config"),
|
||||
|
|
@ -1453,6 +1320,30 @@ extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat
|
|||
self.payloadVariant = .setScale(v)
|
||||
}
|
||||
}()
|
||||
case 24: try {
|
||||
var v: AdminMessage.BackupLocation?
|
||||
try decoder.decodeSingularEnumField(value: &v)
|
||||
if let v = v {
|
||||
if self.payloadVariant != nil {try decoder.handleConflictingOneOf()}
|
||||
self.payloadVariant = .backupPreferences(v)
|
||||
}
|
||||
}()
|
||||
case 25: try {
|
||||
var v: AdminMessage.BackupLocation?
|
||||
try decoder.decodeSingularEnumField(value: &v)
|
||||
if let v = v {
|
||||
if self.payloadVariant != nil {try decoder.handleConflictingOneOf()}
|
||||
self.payloadVariant = .restorePreferences(v)
|
||||
}
|
||||
}()
|
||||
case 26: try {
|
||||
var v: AdminMessage.BackupLocation?
|
||||
try decoder.decodeSingularEnumField(value: &v)
|
||||
if let v = v {
|
||||
if self.payloadVariant != nil {try decoder.handleConflictingOneOf()}
|
||||
self.payloadVariant = .removeBackupPreferences(v)
|
||||
}
|
||||
}()
|
||||
case 32: try {
|
||||
var v: User?
|
||||
var hadOneofValue = false
|
||||
|
|
@ -1796,6 +1687,18 @@ extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat
|
|||
guard case .setScale(let v)? = self.payloadVariant else { preconditionFailure() }
|
||||
try visitor.visitSingularUInt32Field(value: v, fieldNumber: 23)
|
||||
}()
|
||||
case .backupPreferences?: try {
|
||||
guard case .backupPreferences(let v)? = self.payloadVariant else { preconditionFailure() }
|
||||
try visitor.visitSingularEnumField(value: v, fieldNumber: 24)
|
||||
}()
|
||||
case .restorePreferences?: try {
|
||||
guard case .restorePreferences(let v)? = self.payloadVariant else { preconditionFailure() }
|
||||
try visitor.visitSingularEnumField(value: v, fieldNumber: 25)
|
||||
}()
|
||||
case .removeBackupPreferences?: try {
|
||||
guard case .removeBackupPreferences(let v)? = self.payloadVariant else { preconditionFailure() }
|
||||
try visitor.visitSingularEnumField(value: v, fieldNumber: 26)
|
||||
}()
|
||||
case .setOwner?: try {
|
||||
guard case .setOwner(let v)? = self.payloadVariant else { preconditionFailure() }
|
||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 32)
|
||||
|
|
@ -1949,6 +1852,13 @@ extension AdminMessage.ModuleConfigType: SwiftProtobuf._ProtoNameProviding {
|
|||
]
|
||||
}
|
||||
|
||||
extension AdminMessage.BackupLocation: SwiftProtobuf._ProtoNameProviding {
|
||||
public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
||||
0: .same(proto: "FLASH"),
|
||||
1: .same(proto: "SD"),
|
||||
]
|
||||
}
|
||||
|
||||
extension HamParameters: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
||||
public static let protoMessageName: String = _protobuf_package + ".HamParameters"
|
||||
public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
||||
|
|
@ -1980,7 +1890,7 @@ extension HamParameters: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementa
|
|||
if self.txPower != 0 {
|
||||
try visitor.visitSingularInt32Field(value: self.txPower, fieldNumber: 2)
|
||||
}
|
||||
if self.frequency != 0 {
|
||||
if self.frequency.bitPattern != 0 {
|
||||
try visitor.visitSingularFloatField(value: self.frequency, fieldNumber: 3)
|
||||
}
|
||||
if !self.shortName.isEmpty {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
// DO NOT EDIT.
|
||||
// swift-format-ignore-file
|
||||
// swiftlint:disable all
|
||||
//
|
||||
// Generated by the Swift generator plugin for the protocol buffer compiler.
|
||||
// Source: meshtastic/apponly.proto
|
||||
|
|
@ -7,7 +8,6 @@
|
|||
// For information on using the generated types, please see the documentation:
|
||||
// https://github.com/apple/swift-protobuf/
|
||||
|
||||
import Foundation
|
||||
import SwiftProtobuf
|
||||
|
||||
// If the compiler emits an error on this type, it is because this file
|
||||
|
|
@ -26,7 +26,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP
|
|||
/// any SECONDARY channels.
|
||||
/// No DISABLED channels are included.
|
||||
/// This abstraction is used only on the the 'app side' of the world (ie python, javascript and android etc) to show a group of Channels as a (long) URL
|
||||
public struct ChannelSet {
|
||||
public struct ChannelSet: Sendable {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
// methods supported on all messages.
|
||||
|
|
@ -53,10 +53,6 @@ public struct ChannelSet {
|
|||
fileprivate var _loraConfig: Config.LoRaConfig? = nil
|
||||
}
|
||||
|
||||
#if swift(>=5.5) && canImport(_Concurrency)
|
||||
extension ChannelSet: @unchecked Sendable {}
|
||||
#endif // swift(>=5.5) && canImport(_Concurrency)
|
||||
|
||||
// MARK: - Code below here is support for the SwiftProtobuf runtime.
|
||||
|
||||
fileprivate let _protobuf_package = "meshtastic"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
// DO NOT EDIT.
|
||||
// swift-format-ignore-file
|
||||
// swiftlint:disable all
|
||||
//
|
||||
// Generated by the Swift generator plugin for the protocol buffer compiler.
|
||||
// Source: meshtastic/atak.proto
|
||||
|
|
@ -20,7 +21,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP
|
|||
typealias Version = _2
|
||||
}
|
||||
|
||||
public enum Team: SwiftProtobuf.Enum {
|
||||
public enum Team: SwiftProtobuf.Enum, Swift.CaseIterable {
|
||||
public typealias RawValue = Int
|
||||
|
||||
///
|
||||
|
|
@ -130,11 +131,6 @@ public enum Team: SwiftProtobuf.Enum {
|
|||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#if swift(>=4.2)
|
||||
|
||||
extension Team: CaseIterable {
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
public static let allCases: [Team] = [
|
||||
.unspecifedColor,
|
||||
|
|
@ -153,13 +149,12 @@ extension Team: CaseIterable {
|
|||
.darkGreen,
|
||||
.brown,
|
||||
]
|
||||
}
|
||||
|
||||
#endif // swift(>=4.2)
|
||||
}
|
||||
|
||||
///
|
||||
/// Role of the group member
|
||||
public enum MemberRole: SwiftProtobuf.Enum {
|
||||
public enum MemberRole: SwiftProtobuf.Enum, Swift.CaseIterable {
|
||||
public typealias RawValue = Int
|
||||
|
||||
///
|
||||
|
|
@ -233,11 +228,6 @@ public enum MemberRole: SwiftProtobuf.Enum {
|
|||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#if swift(>=4.2)
|
||||
|
||||
extension MemberRole: CaseIterable {
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
public static let allCases: [MemberRole] = [
|
||||
.unspecifed,
|
||||
|
|
@ -250,13 +240,12 @@ extension MemberRole: CaseIterable {
|
|||
.rto,
|
||||
.k9,
|
||||
]
|
||||
}
|
||||
|
||||
#endif // swift(>=4.2)
|
||||
}
|
||||
|
||||
///
|
||||
/// Packets for the official ATAK Plugin
|
||||
public struct TAKPacket {
|
||||
public struct TAKPacket: @unchecked Sendable {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
// methods supported on all messages.
|
||||
|
|
@ -337,7 +326,7 @@ public struct TAKPacket {
|
|||
|
||||
///
|
||||
/// The payload of the packet
|
||||
public enum OneOf_PayloadVariant: Equatable {
|
||||
public enum OneOf_PayloadVariant: Equatable, @unchecked Sendable {
|
||||
///
|
||||
/// TAK position report
|
||||
case pli(PLI)
|
||||
|
|
@ -349,28 +338,6 @@ public struct TAKPacket {
|
|||
/// May be compressed / truncated by the sender (EUD)
|
||||
case detail(Data)
|
||||
|
||||
#if !swift(>=4.1)
|
||||
public static func ==(lhs: TAKPacket.OneOf_PayloadVariant, rhs: TAKPacket.OneOf_PayloadVariant) -> Bool {
|
||||
// The use of inline closures is to circumvent an issue where the compiler
|
||||
// allocates stack space for every case branch when no optimizations are
|
||||
// enabled. https://github.com/apple/swift-protobuf/issues/1034
|
||||
switch (lhs, rhs) {
|
||||
case (.pli, .pli): return {
|
||||
guard case .pli(let l) = lhs, case .pli(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.chat, .chat): return {
|
||||
guard case .chat(let l) = lhs, case .chat(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.detail, .detail): return {
|
||||
guard case .detail(let l) = lhs, case .detail(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public init() {}
|
||||
|
|
@ -382,7 +349,7 @@ public struct TAKPacket {
|
|||
|
||||
///
|
||||
/// ATAK GeoChat message
|
||||
public struct GeoChat {
|
||||
public struct GeoChat: Sendable {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
// methods supported on all messages.
|
||||
|
|
@ -424,7 +391,7 @@ public struct GeoChat {
|
|||
///
|
||||
/// ATAK Group
|
||||
/// <__group role='Team Member' name='Cyan'/>
|
||||
public struct Group {
|
||||
public struct Group: Sendable {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
// methods supported on all messages.
|
||||
|
|
@ -446,7 +413,7 @@ public struct Group {
|
|||
///
|
||||
/// ATAK EUD Status
|
||||
/// <status battery='100' />
|
||||
public struct Status {
|
||||
public struct Status: Sendable {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
// methods supported on all messages.
|
||||
|
|
@ -463,7 +430,7 @@ public struct Status {
|
|||
///
|
||||
/// ATAK Contact
|
||||
/// <contact endpoint='0.0.0.0:4242:tcp' phone='+12345678' callsign='FALKE'/>
|
||||
public struct Contact {
|
||||
public struct Contact: Sendable {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
// methods supported on all messages.
|
||||
|
|
@ -483,7 +450,7 @@ public struct Contact {
|
|||
|
||||
///
|
||||
/// Position Location Information from ATAK
|
||||
public struct PLI {
|
||||
public struct PLI: Sendable {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
// methods supported on all messages.
|
||||
|
|
@ -515,18 +482,6 @@ public struct PLI {
|
|||
public init() {}
|
||||
}
|
||||
|
||||
#if swift(>=5.5) && canImport(_Concurrency)
|
||||
extension Team: @unchecked Sendable {}
|
||||
extension MemberRole: @unchecked Sendable {}
|
||||
extension TAKPacket: @unchecked Sendable {}
|
||||
extension TAKPacket.OneOf_PayloadVariant: @unchecked Sendable {}
|
||||
extension GeoChat: @unchecked Sendable {}
|
||||
extension Group: @unchecked Sendable {}
|
||||
extension Status: @unchecked Sendable {}
|
||||
extension Contact: @unchecked Sendable {}
|
||||
extension PLI: @unchecked Sendable {}
|
||||
#endif // swift(>=5.5) && canImport(_Concurrency)
|
||||
|
||||
// MARK: - Code below here is support for the SwiftProtobuf runtime.
|
||||
|
||||
fileprivate let _protobuf_package = "meshtastic"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
// DO NOT EDIT.
|
||||
// swift-format-ignore-file
|
||||
// swiftlint:disable all
|
||||
//
|
||||
// Generated by the Swift generator plugin for the protocol buffer compiler.
|
||||
// Source: meshtastic/cannedmessages.proto
|
||||
|
|
@ -7,7 +8,6 @@
|
|||
// For information on using the generated types, please see the documentation:
|
||||
// https://github.com/apple/swift-protobuf/
|
||||
|
||||
import Foundation
|
||||
import SwiftProtobuf
|
||||
|
||||
// If the compiler emits an error on this type, it is because this file
|
||||
|
|
@ -22,7 +22,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP
|
|||
|
||||
///
|
||||
/// Canned message module configuration.
|
||||
public struct CannedMessageModuleConfig {
|
||||
public struct CannedMessageModuleConfig: Sendable {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
// methods supported on all messages.
|
||||
|
|
@ -36,10 +36,6 @@ public struct CannedMessageModuleConfig {
|
|||
public init() {}
|
||||
}
|
||||
|
||||
#if swift(>=5.5) && canImport(_Concurrency)
|
||||
extension CannedMessageModuleConfig: @unchecked Sendable {}
|
||||
#endif // swift(>=5.5) && canImport(_Concurrency)
|
||||
|
||||
// MARK: - Code below here is support for the SwiftProtobuf runtime.
|
||||
|
||||
fileprivate let _protobuf_package = "meshtastic"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
// DO NOT EDIT.
|
||||
// swift-format-ignore-file
|
||||
// swiftlint:disable all
|
||||
//
|
||||
// Generated by the Swift generator plugin for the protocol buffer compiler.
|
||||
// Source: meshtastic/channel.proto
|
||||
|
|
@ -36,13 +37,15 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP
|
|||
/// FIXME: Add description of multi-channel support and how primary vs secondary channels are used.
|
||||
/// FIXME: explain how apps use channels for security.
|
||||
/// explain how remote settings and remote gpio are managed as an example
|
||||
public struct ChannelSettings {
|
||||
public struct ChannelSettings: @unchecked Sendable {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
// methods supported on all messages.
|
||||
|
||||
///
|
||||
/// Deprecated in favor of LoraConfig.channel_num
|
||||
///
|
||||
/// NOTE: This field was marked as deprecated in the .proto file.
|
||||
public var channelNum: UInt32 = 0
|
||||
|
||||
///
|
||||
|
|
@ -111,7 +114,7 @@ public struct ChannelSettings {
|
|||
|
||||
///
|
||||
/// This message is specifically for modules to store per-channel configuration data.
|
||||
public struct ModuleSettings {
|
||||
public struct ModuleSettings: Sendable {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
// methods supported on all messages.
|
||||
|
|
@ -132,7 +135,7 @@ public struct ModuleSettings {
|
|||
|
||||
///
|
||||
/// A pair of a channel number, mode and the (sharable) settings for that channel
|
||||
public struct Channel {
|
||||
public struct Channel: Sendable {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
// methods supported on all messages.
|
||||
|
|
@ -170,7 +173,7 @@ public struct Channel {
|
|||
/// cross band routing as needed.
|
||||
/// If a device has only a single radio (the common case) only one channel can be PRIMARY at a time
|
||||
/// (but any number of SECONDARY channels can't be sent received on that common frequency)
|
||||
public enum Role: SwiftProtobuf.Enum {
|
||||
public enum Role: SwiftProtobuf.Enum, Swift.CaseIterable {
|
||||
public typealias RawValue = Int
|
||||
|
||||
///
|
||||
|
|
@ -209,6 +212,13 @@ public struct Channel {
|
|||
}
|
||||
}
|
||||
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
public static let allCases: [Channel.Role] = [
|
||||
.disabled,
|
||||
.primary,
|
||||
.secondary,
|
||||
]
|
||||
|
||||
}
|
||||
|
||||
public init() {}
|
||||
|
|
@ -216,26 +226,6 @@ public struct Channel {
|
|||
fileprivate var _settings: ChannelSettings? = nil
|
||||
}
|
||||
|
||||
#if swift(>=4.2)
|
||||
|
||||
extension Channel.Role: CaseIterable {
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
public static let allCases: [Channel.Role] = [
|
||||
.disabled,
|
||||
.primary,
|
||||
.secondary,
|
||||
]
|
||||
}
|
||||
|
||||
#endif // swift(>=4.2)
|
||||
|
||||
#if swift(>=5.5) && canImport(_Concurrency)
|
||||
extension ChannelSettings: @unchecked Sendable {}
|
||||
extension ModuleSettings: @unchecked Sendable {}
|
||||
extension Channel: @unchecked Sendable {}
|
||||
extension Channel.Role: @unchecked Sendable {}
|
||||
#endif // swift(>=5.5) && canImport(_Concurrency)
|
||||
|
||||
// MARK: - Code below here is support for the SwiftProtobuf runtime.
|
||||
|
||||
fileprivate let _protobuf_package = "meshtastic"
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue