diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index 6adc9c60..872227a2 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -52,3 +52,12 @@ body: attributes: label: Additional comments description: Is there anything else that's important for the team to know? + + - type: checkboxes + id: terms + attributes: + label: Code of Conduct + description: By submitting this issue, you agree to follow our [Code of Conduct](https://meshtastic.org/docs/legal/conduct/). + options: + - label: I agree to follow this project's Code of Conduct + required: true diff --git a/.github/ISSUE_TEMPLATE/feature.yml b/.github/ISSUE_TEMPLATE/feature.yml index a0b401a9..3913159e 100644 --- a/.github/ISSUE_TEMPLATE/feature.yml +++ b/.github/ISSUE_TEMPLATE/feature.yml @@ -31,7 +31,7 @@ body: label: Participation description: (Features without participation go to the backlog.) options: - - label: I am willing to sponsor this feature. + - label: I am willing to pay to sponsor this feature. required: false - label: I am willing to submit a pull request for this feature. required: false @@ -39,3 +39,11 @@ body: attributes: label: Additional comments description: Is there anything else that's important for the team to know? + - type: checkboxes + id: terms + attributes: + label: Code of Conduct + description: By submitting this issue, you agree to follow our [Code of Conduct](https://meshtastic.org/docs/legal/conduct/). + options: + - label: I agree to follow this project's Code of Conduct + required: true diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 00000000..94744d0c --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,22 @@ +name: process stale Issues and PR's +on: + schedule: + - cron: 0 6 * * * + workflow_dispatch: {} + +permissions: + issues: write + pull-requests: write + actions: write + +jobs: + stale_issues: + name: Close Stale Issues + runs-on: ubuntu-latest + + steps: + - name: Stale PR+Issues + uses: actions/stale@v9.0.0 + with: + exempt-issue-labels: 'has sponsor,needs sponsor,help wanted,backlog,security issue' + exempt-pr-labels: 'has sponsor,needs sponsor,help wanted,backlog,security issue' diff --git a/.gitignore b/.gitignore index 77aab2fe..a6d2222f 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ ## User settings xcuserdata/ +SupportingFiles/ ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) *.xcscmblueprint diff --git a/Localizable.xcstrings b/Localizable.xcstrings index 3ed32241..0557a79f 100644 --- a/Localizable.xcstrings +++ b/Localizable.xcstrings @@ -2,31 +2,218 @@ "sourceLanguage" : "en", "strings" : { "" : { - + "localizations" : { + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "" + } + } + } }, "\t%@" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "\t%@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "\t%@" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@" + } + } + } }, " %@" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : " %@" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : " %@" + } + } + } }, " %@" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : " %@" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : " %@" + } + } + } }, " Whether or not use INPUT_PULLUP mode for GPIO pin. Only applicable if the board uses pull-up resistors on the pin" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Да ли желите да користите режим INPUT_PULLUP за GPIO пин. Применљиво само ако плоча користи pull-up отпорнике на пиновима" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "是否为 GPIO 引脚使用输入上拉模式。仅适用于电路板在引脚上使用上拉电阻的情况" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "是否使用 INPUT_PULLUP 模式設定 GPIO 腳位。只有當主機板在該腳位上使用上拉電阻時才適用。" + } + } + } }, ": %@" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : ": %@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : ": %@" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : ": %@" + } + } + } }, ": %d" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : ": %d" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : ": %d" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : ": %d" + } + } + } }, "(Re)define PIN_GPS_EN for your board." : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "(Поново)дефинишите PIN_GPS_EN за своју плочу." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "为你的电路板重新定义 PIN_GPS_EN" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "(重新)為您的主機板定義 PIN_GPS_EN。" + } + } + } }, "%@" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@" + } + } + } + }, + "%@ - %@" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "%1$@ - %2$@" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ - %2$@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ - %2$@" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ - %2$@" + } + } + } }, "%@ - %@ - %@" : { "localizations" : { @@ -35,30 +222,156 @@ "state" : "new", "value" : "%1$@ - %2$@ - %3$@" } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ - %2$@ - %3$@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ - %2$@ - %3$@" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ - %2$@ - %3$@" + } } } }, - "%@ - %d Hops Towards %d Hops Back" : { + "%@ - %@ Towards %@ Back" : { "localizations" : { "en" : { "stringUnit" : { "state" : "new", - "value" : "%1$@ - %2$d Hops Towards %3$d Hops Back" + "value" : "%1$@ - %2$@ Towards %3$@ Back" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ - %2$@ Одлазних скокова %3$@ Долазних скокова" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ - %2$@ Towards %3$@ Back" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ - %2$@ 朝向 %3$@ 返回" } } } }, "%@ - 1 Hop" : { - + "extractionState" : "stale", + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ - 1 Скок" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ - 1 跳" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ - 1 跳" + } + } + } }, "%@ - Direct" : { - + "extractionState" : "stale", + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ - Директно" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ - 直接" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ - 直接連線" + } + } + } }, "%@ - No Response" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ - Keine Antwort" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ - Нема одговора" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ - 没有响应" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ - 無回應" + } + } + } }, "%@ - Not Sent" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ - Nicht gesendet" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ - Није послато" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ - 未发送" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ - 尚未傳送" + } + } + } }, "%@ (%@)" : { "localizations" : { @@ -67,6 +380,24 @@ "state" : "new", "value" : "%1$@ (%2$@)" } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ (%2$@)" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ (%2$@)" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ (%2$@)" + } } } }, @@ -77,6 +408,24 @@ "state" : "new", "value" : "%1$@ %2$@" } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ %2$@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ %2$@" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ %2$@" + } } } }, @@ -87,11 +436,54 @@ "state" : "new", "value" : "%1$@ %2$lld" } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ %2$lld" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ %2$lld" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ %2$lld" + } } } }, "%@ away" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ entfernt" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ удаљено" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ 离开" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ 距離之外" + } + } + } }, "%@ can be up to %@ bytes long." : { "localizations" : { @@ -100,20 +492,115 @@ "state" : "new", "value" : "%1$@ can be up to %2$@ bytes long." } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ може имати до %2$@ бајтова." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ 的长度可达 %2$@ 字节" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ 最長可以有 %2$@ 個位元組。" + } } } }, "%@ Channels?" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ Канали?" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ 频道?" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ 通道?" + } + } + } }, "%@ config data was requested over the admin channel but no response has been returned from the remote node." : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ конфигурациони подаци су затражени преко административног канала, али никакав одговор није враћен са удаљеног чвора." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "已通过管理频道请求 %@ 配置数据,但远程节点未返回任何响应。" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ 組態資料已透過管理通道要求,但遠端節點尚未回覆。" + } + } + } }, "%@ dB" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ dB" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ dB" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ dB" + } + } + } }, "%@ hPa" : { - + "extractionState" : "stale", + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ hPa" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ hPa" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ 百帕" + } + } + } }, "%@, %@" : { "localizations" : { @@ -122,6 +609,24 @@ "state" : "new", "value" : "%1$@, %2$@" } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@, %2$@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@, %2$@" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@, %2$@" + } } } }, @@ -132,83 +637,663 @@ "state" : "new", "value" : "%1$@: %2$lld / %3$lld" } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@: %2$lld / %3$lld" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@: %2$lld / %3$lld" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@: %2$lld / %3$lld" + } } } }, "%@%%" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@%%" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@%%" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@%%" + } + } + } }, "%@°F" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@°F" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@°F" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@°F" + } + } + } }, "%d" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%d" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%d" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "%d" + } + } + } + }, + "%d Hops" : { + "localizations" : { + "en" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%d Hop" + } + }, + "other" : { + "stringUnit" : { + "state" : "new", + "value" : "%d Hops" + } + }, + "zero" : { + "stringUnit" : { + "state" : "translated", + "value" : "Direct" + } + } + } + } + }, + "sr" : { + "variations" : { + "plural" : { + "few" : { + "stringUnit" : { + "state" : "translated", + "value" : "%d скокова" + } + }, + "one" : { + "stringUnit" : { + "state" : "translated", + "value" : "%d скок" + } + }, + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%d скокова" + } + }, + "zero" : { + "stringUnit" : { + "state" : "translated", + "value" : "Директно" + } + } + } + } + }, + "zh-Hans" : { + "variations" : { + "plural" : { + "other" : { + "stringUnit" : { + "state" : "new", + "value" : "%d Hops" + } + }, + "zero" : { + "stringUnit" : { + "state" : "translated", + "value" : "Direct" + } + } + } + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "%d 跳" + } + } + } }, "%d%%" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%d%%" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%d%%" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "%d%%" + } + } + } }, "%lf" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lf" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lf" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lf" + } + } + } }, "%lld" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld" + } + } + } }, "%lld or less hops away" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld oder weniger Hops entfernt" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld или мање скокова" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld 跳或更少距離之外" + } + } + } }, "%lld Readings Total" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Укупно %lld читања" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld 個讀數總計" + } + } + } }, "%lld Total Detection Events" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Укупно %lld догађаја детекције" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld 個偵測事件總計" + } + } + } }, "%lld%%" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld%%" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld%%" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld%%" + } + } + } }, "%llddb Transmit Power" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "%llddb Übertragungsleistung" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%llddb снага преноса" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "发射功率 %llddb" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld dB 傳輸功率" + } + } + } }, "%llddBm Transmit Power" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "%llddBm Übertragungsleistung" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%llddBm снага преноса" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld dBm 傳輸功率" + } + } + } }, "< 1%" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "< 1%" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "< 1%" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "< 1%" + } + } + } }, "🦕 End of life Version 🦖 ☄️" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "🦕 Верзија за крај живота 🦖 ☄" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "🦕 終止生命週期版本 🦖 ☄️" + } + } + } }, "1 byte" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "1 byte" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "1 byte" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "1 個位元組" + } + } + } }, "1 hop away" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "1 hop away" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "1 跳" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "1 跳之外" + } + } + } + }, + "2.4ghz" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "2.4 GHz" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "2.4 GHz" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "2.4 吉赫茲" + } + } + } }, "7" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "7" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "7" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "7" + } + } + } }, "8" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "8" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "8" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "8" + } + } + } }, "25" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "25" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "25" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "25" + } + } + } }, "50" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "50" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "50" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "50" + } + } + } }, "75" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "75" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "75" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "75" + } + } + } }, "100" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "100" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "100" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "100" + } + } + } }, "128 bit" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "128 bit" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "128 bit" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "128 位元" + } + } + } }, "256 bit" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "256 bit" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "256 bit" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "256 位元" + } + } + } }, "A Trace Route was sent, no response has been received." : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Трејсрут је послат, али одговор није примљен." + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "已傳送追蹤路線,但尚未收到回應。" + } + } + } }, "about" : { "localizations" : { @@ -254,6 +1339,12 @@ "value" : "Om" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "О" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -312,6 +1403,12 @@ "value" : "Om Meshtastic" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "О Мештастику" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -327,29 +1424,125 @@ } }, "Accuracy %@" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Genauigkeit %@" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Прецизност %@" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "準確度 %@" + } + } + } }, "Ack SNR: %@ dB" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ack SNR: %@ dB" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "確認信號雜訊比:%@ dB" + } + } + } }, "Ack Time: %@" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ack време: %@" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "確認時間: %@" + } + } + } }, "Acknowledged by another node" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Потврђен од стране другог чвора" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "已獲其他節點確認" + } + } + } }, "Actions" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aktionen" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Акције" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "動作" + } + } + } }, "Active" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aktiv" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Активан" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "活動中" + } + } + } }, "activity" : { "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Activity" + "value" : "Aktivität" } }, "en" : { @@ -388,6 +1581,12 @@ "value" : "Activity" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Активност" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -403,22 +1602,124 @@ } }, "Activity" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aktivität" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Активност" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "活動" + } + } + } }, "Add Channel" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Додај канал" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "新增通道" + } + } + } }, "Add Channels" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Додај канале" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "新增通道" + } + } + } }, "Add to favorites" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zu Favoriten hinzufügen" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Додај у омиљене" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "加入最愛" + } + } + } }, "Additional help" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Додатна помоћ" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "其他帮助" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "進階幫助" + } + } + } }, "Address" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Адреса" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "地址" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "地址" + } + } + } }, "admin" : { "extractionState" : "migrated", @@ -465,6 +1766,12 @@ "value" : "Administratör" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Админ" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -480,9 +1787,35 @@ } }, "Admin & Direct Message Keys" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Schlüssel für Administrator und Direktnachrichten" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Админ и кључеви директних порука" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "管理员 & 私信密钥" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "管理員與直接訊息金鑰" + } + } + } }, "admin.log" : { + "comment" : "On Serbian language Admin and Administrator are the same as in English, but in sentences like this we use the longer version always.", "extractionState" : "manual", "localizations" : { "de" : { @@ -527,6 +1860,12 @@ "value" : "Administratörsmeddelandelogg" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Дневник администраторских порука" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -542,21 +1881,117 @@ } }, "Administration" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Администрација" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "管理员" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "管理" + } + } + } }, "Advanced" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Напредно" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "高级" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "進階" + } + } + } }, "Advanced Device GPS" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Напредне поставке GPS уређаја" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "高级设备 GPS" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "進階裝置 GPS" + } + } + } }, "Advanced GPIO Options" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Напредне GPIO опције" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "高级 GPIO 选项" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "進階 GPIO 選項" + } + } + } }, "Advanced Position Flags" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Напредне поставке позиционих заставица" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "高级位置标志" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "進階位置標記" + } + } + } }, "ago" : { + "comment" : "Three hours ago = Три сата пре", "extractionState" : "manual", "localizations" : { "de" : { @@ -601,6 +2036,12 @@ "value" : "sedan" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "пре" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -659,6 +2100,12 @@ "value" : "Sändningstid" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Време емитовања" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -674,61 +2121,380 @@ } }, "Airtime" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Време емитовања" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "广播时间" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "通話時間" + } + } + } }, "Airtime %@%%" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Време емитовања %@%%" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "广播时间 %@%%" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "通話時間 %@%%" + } + } + } }, "Alert" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Узбуна" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "警報" + } + } + } }, "Alert GPIO buzzer when receiving a bell" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Упозорите GPIO зујалицу када примите звоно" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "收到铃声时发出警报 GPIO 蜂鸣器" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "收到鈴聲時警報 GPIO 蜂鳴器" + } + } + } }, "Alert GPIO buzzer when receiving a message" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Упозорите GPIO зујалицу када примите поруку" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "收到訊息時警報 GPIO 蜂鳴器" + } + } + } }, "Alert GPIO vibra motor when receiving a bell" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Упозорите GPIO вибра мотор када примите звоно" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "收到铃声时提醒 GPIO 振动电机" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "收到鈴聲時警報 GPIO 震動馬達" + } + } + } }, "Alert GPIO vibra motor when receiving a message" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Упозорите GPIO вибра мотор када примите поруку" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "收到訊息時警報 GPIO 震動馬達" + } + } + } }, "Alert when receiving a bell" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Упозори када примиш звоно" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "收到铃声时发出警报" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "收到鈴聲時警報" + } + } + } }, "Alert when receiving a message" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Упозори када примиш поруку" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "收到訊息時警報" + } + } + } }, "All" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Alle" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Сви" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "全部" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "全部" + } + } + } }, "All device and app data will be deleted." : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Сви подаци о уређају и апликацији ће бити избрисани." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "所有设备以及 App 数据都会被删除。" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "所有裝置和應用程式資料都將被刪除。" + } + } + } }, "Allow incoming device control over the insecure legacy admin channel." : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Erlaubt die eingehende Gerätesteuerung über den unsicheren Legacy-Admin-Kanal." + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Дозволите контролу долазног уређаја над небезбедним старим администраторским каналом." + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "允許透過不安全的舊版管理通道進行設備控制。" + } + } + } }, "Allow Position Requests" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Дозволи захтеве позиција" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "允許位置請求" + } + } + } }, "Alt" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Висина" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "替代" + } + } + } }, "Altitude" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Höhe" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Висина" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "高度" + } + } + } }, "Altitude %@" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Höhe %@" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Висина %@" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "高度 %@" + } + } + } }, "Altitude Geoidal Separation" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Висинска геоидна сепарација" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "大地水準面高度分離" + } + } + } }, "Altitude is Mean Sea Level" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Надморска висина је средњи ниво мора" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "高度為平均海平面" + } + } + } }, "Always point north" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Immer nach Norden zeigen" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Увек усмеравајте на север" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "始終指向北方" + } + } + } }, "always.on" : { "extractionState" : "migrated", @@ -775,6 +2541,12 @@ "value" : "Alltid på" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Увек укључен" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -833,6 +2605,12 @@ "value" : "Omgivningsbelysning" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Амбијентално осветљење" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -891,6 +2669,12 @@ "value" : "Konfiguration av omgivningsbelysning" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Подешавања амбијенталног осветљења" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -906,25 +2690,164 @@ } }, "An open source, off-grid, decentralized, mesh network that runs on affordable, low-power radios." : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ein quelloffenes, netzunabhängiges, dezentrales Mesh-Netzwerk, das auf kostengünstigen, stromsparenden Funkgeräten läuft." + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Отворена, off-grid, децентрализована, меш мрежа која ради на приступачним радио уређајима мале снаге." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "这是一个开源、离网、分布式 Mesh 网络,可在价格低廉的低功率无线电设备上运行。" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "一個開源、離網、去中心化的無線網路,使用經濟實惠且低功耗的無線電運行。" + } + } + } }, "Any missed messages will be delivered again." : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Све пропуштене поруке ће бити поново испоручене." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "任何错过的信息都会再次发送。" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "任何遺漏的訊息都會再次傳遞。" + } + } + } }, "App Data" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Подаци апликације" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "App 数据" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "應用程式資料" + } + } + } }, "App Files" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Фајлови апликације" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "App 文件" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "應用程式檔案" + } + } + } }, "App Settings" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Подешавања апликације" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "App 设置" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "應用程式設定" + } + } + } }, "Apple Apps" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Епл апликације" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "Apple Apps" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "蘋果應用程式" + } + } + } }, "Approximate Location" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ungefährer Standort" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Приближна локација" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "近似位置" + } + } + } }, "appsettings" : { "localizations" : { @@ -970,10 +2893,16 @@ "value" : "Appinställningar" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Подешавања апликације" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "通用设置" + "value" : "App 设置" } }, "zh-Hant-TW" : { @@ -990,7 +2919,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "New Node Notifications" + "value" : "Mitteilungen über neue Knoten" } }, "en" : { @@ -1029,6 +2958,12 @@ "value" : "New Node Notifications" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Обавештења о новим чворовима" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -1088,6 +3023,12 @@ "value" : "Dela plats" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Подели информације о локацији" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -1147,6 +3088,12 @@ "value" : "Smart position" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Паметно позиционирање" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -1162,10 +3109,54 @@ } }, "Are you sure you want to delete this message?" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Да ли си сигуран да желиш да обришеш ову поруку?" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "你确定删除这条消息么?" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "您確定要刪除此訊息嗎?" + } + } + } }, "Are you sure you want to factory reset the node?" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bist du sicher dass du den Knoten auf die Werkseinstellungen zurücksetzen willst?" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Да ли си стигуран да желиш да вратиш овај чвор на фабричка подешавања?" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "你确定要初始化这个节点么?" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "您確定要將節點還原至工廠設定嗎?" + } + } + } }, "are.you.sure" : { "localizations" : { @@ -1211,6 +3202,12 @@ "value" : "Är du säker?" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Да ли си сигуран?" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -1220,7 +3217,7 @@ "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "是否確定?" + "value" : "您確定嗎?" } } } @@ -1231,7 +3228,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "ASCII fähig" + "value" : "ASCII-fähig" } }, "en" : { @@ -1270,6 +3267,12 @@ "value" : "ASCII-kompatibel" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "ASCII способан" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -1284,6 +3287,29 @@ } } }, + "australia.new.zealand" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Australia / New Zealand" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Аустралија / Нови Зеланд" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "澳洲 / 新西蘭" + } + } + } + }, "automatic.detection" : { "extractionState" : "migrated", "localizations" : { @@ -1329,6 +3355,12 @@ "value" : "Automatisk upptäckt" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Аутоматска детекција" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -1344,10 +3376,48 @@ } }, "Automatically toggles to the next page on the screen like a carousel, based the specified interval." : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Аутоматски се пребацује на следећу страницу на екрану као карусел, на основу наведеног интервала." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "根据指定的时间间隔,像旋转木马一样自动切换到屏幕上的下一页。" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "根據指定的間隔,自動切換到螢幕上的下一頁,例如輪播。" + } + } + } }, "Available modem presets, default is Long Fast." : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Доступна унапред подешена подешавања модема, подразумевана је Long Fast." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "可用的调制解调器预置,默认为 “Long Fast”。" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "可用的數據機預設值,預設為長快模式。" + } + } + } }, "available.radios" : { "localizations" : { @@ -1393,6 +3463,12 @@ "value" : "Tillgängliga radioapparater" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Доступни радио уређаји" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -1408,25 +3484,165 @@ } }, "Backup Database" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Резервна база података" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "备份数据库" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "備份資料庫" + } + } + } }, "Bad" : { - + "extractionState" : "stale", + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Лош" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "坏" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "不良" + } + } + } }, "Bandwidth" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bandbreite" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Проток" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "带宽" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "頻寬" + } + } + } }, "Bar" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bar" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bar" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "欄位" + } + } + } }, "Bar Series" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bar серија" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bar Series" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "欄位系列" + } + } + } }, "Barometric Pressure" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Барометарски притисак" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "气压" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "氣壓" + } + } + } }, "Battery Level %" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ниво батерије у %" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "电池电量 %" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "電池電量 %" + } + } + } }, "battery.level" : { "localizations" : { @@ -1472,6 +3688,12 @@ "value" : "Batterinivå" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ниво батерије" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -1487,13 +3709,70 @@ } }, "Baud" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Baud" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "波特率" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "波特率" + } + } + } }, "BLE RSSI: %lld" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "BLE RSSI: %lld" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "BLE RSSI: %lld" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "BLE RSSI:%lld" + } + } + } }, "BLE: %@" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "BLE: %@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "BLE: %@" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "BLE:%@" + } + } + } }, "ble.connection.timeout %d %@" : { "extractionState" : "migrated", @@ -1540,16 +3819,51 @@ "value" : "Anslutningen misslyckades efter %d försök att ansluta till %@. Du kan behöva glömma din enhet under Inställningar > Bluetooth." } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Веза није успела након %d покушаја да се повеже са %@. Можда ћете морати да заборавите уређај у Подешавања > Блутут." + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "尝试连接%d失败,你可能需要在系统设置的蓝牙选项中忽略该电台。" + "value" : "尝试连接%d失败,你可能需要在系统设置的蓝牙选项中忽略该设备。" } }, "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "嘗試連接%d失敗,你可能需要在系统設定的藍芽選項中忽略該電台。" + "value" : "經過 %d 次嘗試連接 %@,連接失敗。您可能需要在 Settings > Bluetooth(設定 > 藍牙)下忘記裝置。" + } + } + } + }, + "ble.errorcode.6" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "The connection has timed out unexpectedly." + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Веза је неочекивано истекла." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "连接意外超时。" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "連線逾時。" } } } @@ -1560,7 +3874,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "%@ Die App wird automatisch zum präferierten Gerät wiederverbinden, sobald es in Reichweite kommt." + "value" : "%@ Die App wird automatisch wieder zum präferierten Gerät verbinden, sobald es in Reichweite kommt." } }, "en" : { @@ -1599,16 +3913,51 @@ "value" : "%@ Appen kommer automatiskt att återansluta till den föredragna radion om den kommer inom räckhåll igen." } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ Апликација ће се аутоматски поново повезати са жељеним радиом ако се врати у домет." + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "%@ 如果在首选电台的旁边,App 将会自动重连。" + "value" : "%@ 如果在默认电台的旁边,App 将会自动重连。" } }, "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "%@ 如果在首選電台的旁邊,App 將會自動重連。" + "value" : "%@ 當偏好無線電回歸範圍內時,應用程式將自動重新連接。" + } + } + } + }, + "ble.errorcode.14" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Peer removed pairing information." + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Радио уређај је уклонио информације о упаривању." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "同伴删除了配对信息。" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "對等裝置已移除配對資訊。" } } } @@ -1619,7 +3968,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "%@ Dieser fehler kann üblicherweise behoben werden, in dem man unter Einstellungen > Bluetooth die Verbindung manuell löscht und sich erneut mit dem Gerät verbindet." + "value" : "%@ Dieser Fehler kann üblicherweise behoben werden, indem man unter Einstellungen > Bluetooth die Verbindung manuell löscht und sich erneut mit dem Gerät verbindet." } }, "en" : { @@ -1658,6 +4007,12 @@ "value" : "%@ Detta fel kan vanligtvis inte åtgärdas utan att glömma enheten under Inställningar > Bluetooth och återansluta till radion." } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ Ова грешка обично не може да се поправи без заборављања уређаја испод подешавања > Блутут и поново повезивање са радиом." + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -1667,7 +4022,7 @@ "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "%@ 這個錯誤通常無法自動修復,你需要在系統設定的藍芽選項中忽略該電台並重新配對。" + "value" : "%@ 此錯誤通常需要在「設定」>「藍牙」中忘記裝置,然後重新連接到無線電才能修復。" } } } @@ -1678,7 +4033,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "%@ Bitte versuche es erneut. achte sorgfältig auf die richtige PIN." + "value" : "%@ Bitte versuche es erneut. Achte sorgfältig auf die richtige PIN." } }, "en" : { @@ -1717,6 +4072,12 @@ "value" : "%@ Försök att ansluta igen och kontrollera PIN-koden noggrant." } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ Покушајте поново да се повежете и пажљиво проверите ПИН." + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -1726,7 +4087,7 @@ "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "%@ 請再次嘗試連接並仔細檢查 PIN 碼。" + "value" : "%@ 請再嘗試連線一次,並仔細檢查 PIN。" } } } @@ -1775,6 +4136,12 @@ "value" : "BLE-namn" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "BLE назив" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -1833,6 +4200,12 @@ "value" : "Bluetooth" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Блутут" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -1891,6 +4264,12 @@ "value" : "Bluetooth-konfiguration" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Блутут подешавања" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -1949,6 +4328,12 @@ "value" : "Fast PIN" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Фиксни ПИН" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -2008,6 +4393,12 @@ "value" : "Ingen PIN (Bara fungerar)" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Нема ПИН-а (само ради)" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -2067,6 +4458,12 @@ "value" : "Slumpmässig PIN" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Насумичан ПИН" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -2125,6 +4522,12 @@ "value" : "Bluetooth är avstängt" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Блутут је искључен" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -2183,6 +4586,12 @@ "value" : "Parläge" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Мод упаривања" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -2241,6 +4650,12 @@ "value" : "BLE-PIN måste vara 6 siffror lång." } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "BLE пин мора имати 6 цифара." + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -2250,22 +4665,98 @@ "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "藍芽 PIN 碼必須是 6 位數字。" + "value" : "BLE PIN 必須為 6 位數長。" } } } }, "Broadcast Interval" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Интервал емитовања" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "广播间隔" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "廣播間隔" + } + } + } }, "Button GPIO" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Дугме GPIO" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "按钮 GPIO" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "按鈕 GPIO" + } + } + } }, "Buy Complete Radios" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Купите готове радио уређаје" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "购买完整的电台" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "購買全部無線電設備" + } + } + } }, "Buzzer GPIO" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Звучни сигнал GPIO" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "蜂鸣器 GPIO" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "蜂鳴器 GPIO" + } + } + } }, "bytes" : { "extractionState" : "migrated", @@ -2312,6 +4803,12 @@ "value" : "Bytes" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Бајтова" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -2327,10 +4824,48 @@ } }, "Call Sign" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Позивни знак" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "呼号" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "呼叫信號" + } + } + } }, "Call Sign must not be empty" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Позивни знак не може бити празан" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "呼号不能为空" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "呼叫信號不能為空。" + } + } + } }, "cancel" : { "localizations" : { @@ -2376,6 +4911,12 @@ "value" : "Avbryt" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Откажи" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -2391,7 +4932,32 @@ } }, "Cancel" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Abbrechen" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Откажи" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "取消" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "取消" + } + } + } }, "canned.messages" : { "localizations" : { @@ -2437,6 +5003,12 @@ "value" : "Fördefinierade meddelanden" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Унапред припремљене поруке" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -2495,6 +5067,12 @@ "value" : "Konfiguration av fördefinierade meddelanden" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Подешавања унапред припремљених порука" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -2554,10 +5132,16 @@ "value" : "M5 Stack Card KB / RAK Keypad" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "M5 стек картица KB / RAK тастатура" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "M5Stack 卡片键盘 / RAK 键盘" + "value" : "M5 Stack 卡片键盘 / RAK 键盘" } }, "zh-Hant-TW" : { @@ -2613,6 +5197,12 @@ "value" : "Manuell konfiguration" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ручна конфигурација" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -2672,10 +5262,16 @@ "value" : "RAK Rotary Encoder-modul" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "RAK Rotary енкодер модул" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "RAK 旋转编码器" + "value" : "RAK 编码器" } }, "zh-Hant-TW" : { @@ -2687,10 +5283,48 @@ } }, "Carousel Interval" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Интервал карусела" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "輪播間隔" + } + } + } }, "Categories" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kategorien" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Категорије" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "分类" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "類別" + } + } + } }, "channel" : { "localizations" : { @@ -2736,6 +5370,12 @@ "value" : "Kanal" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Канал" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -2751,49 +5391,318 @@ } }, "Channel" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kanal" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Канал" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "频道" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "通道" + } + } + } }, "Channel 0 Included" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Канал 0 укључен" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "包含频道 0" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "通道 0 已包含" + } + } + } }, "Channel 1 Included" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Канал 1 укључен" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "包含频道 1" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "通道 1 已包含" + } + } + } }, "Channel 2 Included" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Канал 2 укључен" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "包含频道 2" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "通道 2 已包含" + } + } + } }, "Channel 3 Included" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Канал 3 укључен" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "包含频道 3" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "通道 3 已包含" + } + } + } }, "Channel 4 Included" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Канал 4 укључен" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "包含频道 4" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "通道 4 已包含" + } + } + } }, "Channel 5 Included" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Канал 5 укључен" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "包含频道 5" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "通道 5 已包含" + } + } + } }, "Channel 6 Included" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Канал 6 укључен" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "包含频道 6" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "通道 6 已包含" + } + } + } }, "Channel 7 Included" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Канал 7 укључен" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "包含频道 7" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "通道 7 已包含" + } + } + } }, "channel details" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "детаљи канала" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "频道详情" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "通道詳細資訊" + } + } + } }, "Channel Name" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Назив канала" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "频道名称" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "通道名稱" + } + } + } }, "Channel number must be between 0 and 7." : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Број канала мора бити између 0 и 7." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "频道编号必须介于 0 和 7 之间。" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "通道編號必須介於 0 和 7 之間。" + } + } + } }, "Channel Role" : { - - }, - "Channel URL" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Улога канала" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "频道角色" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "通道角色" + } + } + } }, "Channel Utilization %@%% " : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Искоришћеност канала %@%%" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "频道利用率 %@%% " + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "通道使用率 %@%%" + } + } + } }, "channel.role.disabled" : { "extractionState" : "migrated", @@ -2840,6 +5749,12 @@ "value" : "Inaktiverad" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Онемогућено" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -2899,6 +5814,12 @@ "value" : "Primär" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Примарни" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -2958,6 +5879,12 @@ "value" : "Sekundär" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Секундарни" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -3016,6 +5943,12 @@ "value" : "Kanalutnyttjande" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Искоришћеност канала" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -3074,6 +6007,12 @@ "value" : "Kanaler" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Канали" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -3089,13 +6028,115 @@ } }, "Channels being added from the QR code did not save. When adding channels the names must be unique." : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Канали који се додају из КР кода нису сачувани. Приликом додавања канала имена морају бити јединствена." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "通过二维码添加的频道无法保存。添加频道时,名称必须唯一。" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "從 QR 码添加的頻道未保存。添加頻道時,名稱必須唯一。" + } + } + } + }, + "Chart" : { + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Графукон" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "图表" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "圖表" + } + } + } }, "CHG" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "ПУЊ" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "變更" + } + } + } + }, + "china" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "China" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Кина" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "CHG" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "中國" + } + } + } }, "Clear" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Очисти" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "清除" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "清除" + } + } + } }, "clear.app.data" : { "localizations" : { @@ -3141,6 +6182,12 @@ "value" : "Rensa appdata" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Очисти податке апликације" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -3199,6 +6246,12 @@ "value" : "Rensa" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Очисти" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -3214,19 +6267,114 @@ } }, "Client" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Клијент" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "客户端" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "客戶端" + } + } + } }, "Client History" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Историја клијената" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "客户端历史" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "客戶端歷史記錄" + } + } + } }, "Client History Request Sent" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Захтев за историју клијента је послат" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "已发送客户端历史记录请求" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "客戶端歷史記錄請求已發送" + } + } + } }, "Client options" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Опције клијента" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "客户端选项" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "客戶端選項" + } + } + } }, "Clockwise Rotary Event" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ротациони догађај у смеру казаљке на сату" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "顺时针旋转活动" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "順時針旋轉事件" + } + } + } }, "close" : { "localizations" : { @@ -3272,6 +6420,12 @@ "value" : "Stäng" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Затвори" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -3287,10 +6441,54 @@ } }, "Coding Rate" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Стопа кодирања" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "编码率" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "編碼率" + } + } + } }, "Color" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Farbe" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Боја" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "颜色" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "顏色" + } + } + } }, "communicating" : { "localizations" : { @@ -3336,16 +6534,44 @@ "value" : "Kommunicerar med enheten..." } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Комуницирање са уређајем. ." + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "与电台进行通讯中..." + "value" : "与设备进行通讯中..." } }, "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "與電台進行通訊中..." + "value" : "與裝置通訊中..." + } + } + } + }, + "Config" : { + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Конфигурација" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "配置" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "組態" } } } @@ -3388,6 +6614,12 @@ "value" : "När aktiverad räknar PAX-räknarmodulen antalet personer som passerar med WiFi och Bluetooth. Både WiFi och Bluetooth måste vara aktiverade för att PAX-räknaren ska fungera." } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Када је омогућен, модул бројача пролазника броји број људи који пролазе користећи ВајФај и Блутут. И ВајФај и Блутут морају бити онемогућени да би бројач пролазника радио." + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -3397,7 +6629,7 @@ "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "啟用後,人流計數器模組將透過 WiFi 和藍牙計算經過的人數。必須停用 WiFi 和藍牙才能讓 PAX 計數器正常工作。" + "value" : "啟用 PAX 計數器模組後,它會使用 Wi-Fi 和藍牙計算通過的人數。要使 PAX 計數器正常工作,必須禁用 Wi-Fi 和藍牙。" } } } @@ -3440,6 +6672,12 @@ "value" : "PAX Räknare" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Бројач пролазника" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -3492,6 +6730,12 @@ "value" : "PAX Räknare Konfiguration" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Подешавања бројача пролазника" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -3544,6 +6788,12 @@ "value" : "Uppdateringsintervall" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Интервал ажурирања" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -3596,6 +6846,12 @@ "value" : "Hur ofta vi kan skicka ett meddelande till mesh-nätverket när personer upptäcks." } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Колико често можемо послати поруку мрежи када се открију људи." + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -3605,7 +6861,7 @@ "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "當檢測到人員時,我們可以多久發送一次訊息到網狀網路。" + "value" : "偵測到人員時,我們可以多頻繁地向網格發送訊息?" } } } @@ -3648,10 +6904,10 @@ "value" : "Multiplikator" } }, - "zh-Hans" : { + "sr" : { "stringUnit" : { "state" : "translated", - "value" : "ADC 放大" + "value" : "Мултипликатор" } }, "zh-Hant-TW" : { @@ -3700,10 +6956,10 @@ "value" : "ADC-överskrivning" } }, - "zh-Hans" : { + "sr" : { "stringUnit" : { "state" : "translated", - "value" : "ADC 修改" + "value" : "Преписивање ADC-а" } }, "zh-Hant-TW" : { @@ -3717,12 +6973,6 @@ "config.power.ls.secs" : { "extractionState" : "manual", "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Light Sleep Interval" - } - }, "en" : { "stringUnit" : { "state" : "translated", @@ -3753,6 +7003,12 @@ "value" : "Intervall för Ljussömn" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Интервал благог спавања" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -3770,12 +7026,6 @@ "config.power.min.wake.secs" : { "extractionState" : "manual", "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Minimum Wake Interval" - } - }, "en" : { "stringUnit" : { "state" : "translated", @@ -3806,6 +7056,12 @@ "value" : "Minsta Väckningsintervall" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Минимални интервал будног стања" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -3825,7 +7081,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Power Saving" + "value" : "Stromsparen" } }, "en" : { @@ -3858,6 +7114,12 @@ "value" : "Strömsparläge" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Уштеда енергије" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -3874,12 +7136,6 @@ }, "config.power.saving.description" : { "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Will sleep everything as much as possible, for the tracker and sensor role this will also include the lora radio. Don't use this setting if you want to use your device with the phone apps or are using a device without a user button." - } - }, "en" : { "stringUnit" : { "state" : "translated", @@ -3910,16 +7166,22 @@ "value" : "Sätter allt i viloläge så mycket som möjligt, för spårnings- och sensorläge kommer detta också inkludera LoRa-radion. Använd inte denna inställning om du vill använda din enhet med mobilappar eller använder en enhet utan en användarknapp." } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Спаваће све што је више могуће, за улогу трагача и сензора ово ће укључивати и лора радио. Не користите ово подешавање ако желите да користите свој уређај са мобилним апликацијама или користите уређај без корисничког дугмета." + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "尽可能将所有部件都置于休眠状态,对于跟踪器和传感器功能,这还包括 LoRa 无线电。如果您要使用手机应用程序或者使用没有用户按钮的设备,请不要使用这个设置。" + "value" : "尽可能让所有设备处于睡眠状态,对于跟踪器和传感器来说,这也包括 LoRa 无线电。如果您想将电台与手机 App 一起使用,或使用没有用户按钮的电台,请不要使用此设置。" } }, "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "為了追蹤器和感測器的角色,這將包括將 LoRa 無線電設備盡可能地進入睡眠模式。如果您想要使用手機應用程式操作您的設備,或者使用沒有用戶按鈕的設備,請不要使用此設定。" + "value" : "將盡可能使所有功能進入睡眠狀態,對於追蹤器和感測器角色,這也包括 LoRa 無線電。如果您希望使用手機應用程式或使用沒有使用者按鈕的裝置,請勿使用此設定。" } } } @@ -3929,7 +7191,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Battery" + "value" : "Batterie" } }, "en" : { @@ -3962,6 +7224,12 @@ "value" : "Batteri" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Батерија" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -3982,7 +7250,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Sleep" + "value" : "Schlafmodus" } }, "en" : { @@ -4015,6 +7283,12 @@ "value" : "Sömn" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Стане спавања" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -4034,7 +7308,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Power" + "value" : "Strom" } }, "en" : { @@ -4067,6 +7341,12 @@ "value" : "Ström" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Снага" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -4086,7 +7366,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "After" + "value" : "Nach" } }, "en" : { @@ -4119,10 +7399,10 @@ "value" : "Efter" } }, - "zh-Hans" : { + "sr" : { "stringUnit" : { "state" : "translated", - "value" : "After" + "value" : "Након" } }, "zh-Hant-TW" : { @@ -4138,7 +7418,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Shutdown on Power Loss" + "value" : "Herunterfahren bei Stromunterbruch" } }, "en" : { @@ -4171,6 +7451,12 @@ "value" : "Stäng av vid Strömförlust" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Искључи уређај при губитку напајања" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -4190,7 +7476,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Power Config" + "value" : "Stromkonfiguration" } }, "en" : { @@ -4223,6 +7509,12 @@ "value" : "Strömkonfiguration" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Подешавања напајња" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -4243,7 +7535,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Bluetooth Off After" + "value" : "Bluetooth Aus nach" } }, "en" : { @@ -4276,6 +7568,12 @@ "value" : "Bluetooth Stängs Av Efter" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Блутут се искључује након" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -4296,7 +7594,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "RTTTL Ringtone" + "value" : "RTTTL Klingelton" } }, "en" : { @@ -4329,6 +7627,12 @@ "value" : "RTTTL Ringsignal" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "RTTTL мелодија звона" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -4345,12 +7649,6 @@ }, "config.ringtone.description" : { "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ringtone Transfer Language(RTTTL) Ringtone String used by supported buzzers in external notifications." - } - }, "en" : { "stringUnit" : { "state" : "translated", @@ -4381,6 +7679,12 @@ "value" : "Ringsignalöverföringsspråk (RTTTL) Ringsignalsträng som används av stödda buzzers i externa notifikationer." } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Језик преноса мелдоије звона (RTTTL) Стринг мелодије звона који користе подржани звучни сигнали у спољним обавештењима." + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -4390,19 +7694,13 @@ "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "RTTTL 鈴聲字串(Ringtone Transfer Language)被用於外部通知中支援的蜂鳴器。" + "value" : "鈴聲傳輸語言 (RTTTL) 鈴聲字串,用於外部通知中支援的蜂鳴器。" } } } }, "config.ringtone.label" : { "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ringtone Transfer Language" - } - }, "en" : { "stringUnit" : { "state" : "translated", @@ -4433,6 +7731,12 @@ "value" : "Språk för Överföring av Ringsignal" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Језик преноса мелодије звона" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -4452,7 +7756,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Ringtone Config" + "value" : "Klingelton Konfiguration" } }, "en" : { @@ -4485,6 +7789,12 @@ "value" : "Ringsignalskonfiguration" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Конфигурација звона" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -4504,7 +7814,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Nach dem ändern der Einstellungen wird das Gerät neu starten." + "value" : "Nach dem Ändern der Einstellungen wird das Gerät neu starten." } }, "en" : { @@ -4543,31 +7853,113 @@ "value" : "Efter att konfigurationsvärdena sparats kommer noden att starta om." } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Након што сачувате вредности конфигурације, чвор ће се поново покренути." + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "电台将会在配置保存后重启。" + "value" : "节点将会在保存配置后重启。" } }, "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "電台將會在設定儲存後重啟。" + "value" : "設定值儲存後,節點將會重新啟動。" } } } }, "Configuration for: %@" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Konfiguration für: %@" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Конфигурација за: %@" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "組態設定:%@" + } + } + } }, "Configuration Presets" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Унапред подешене конфигурације" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "配置预设" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "組態預設值" + } + } + } }, "Configure" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Konfigurieren" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Конфигуриши" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "組態" + } + } + } }, "Connect to a Node" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Verbunden mit einem Knoten" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Повежите се са чвором" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "連接到節點" + } + } + } }, "connected" : { "localizations" : { @@ -4613,10 +8005,16 @@ "value" : "Bluetooth Ansluten" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Блутут повезан" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "已连接到电台" + "value" : "蓝牙已连接" } }, "zh-Hant-TW" : { @@ -4628,7 +8026,26 @@ } }, "Connected Node %@" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Verbunden mit Knoten %@" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Повезани чвор %@" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "已連接到節點 %@" + } + } + } }, "connected.radio" : { "localizations" : { @@ -4674,10 +8091,16 @@ "value" : "Ansluten Radio" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Повезани радио" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "已连接的电台" + "value" : "电台已连接" } }, "zh-Hant-TW" : { @@ -4732,6 +8155,12 @@ "value" : "Ansluter..." } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Повезујем се . ." + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -4741,13 +8170,38 @@ "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "連接中..." + "value" : "連線中..." } } } }, "Connection Attempt %lld of 10" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Verbindungsversuch %lld von 10" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Покушај повезивања %lld од 10" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "连接尝试 %lld,共 10 次" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "連線嘗試次數 %lld / 10 次" + } + } + } }, "contacts" : { "extractionState" : "manual", @@ -4794,6 +8248,12 @@ "value" : "Kontakter" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Контакти" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -4853,6 +8313,12 @@ "value" : "Kontakter (%@)" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Контакти (%@)" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -4868,29 +8334,148 @@ } }, "Control Type" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Тип контроле" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "控制类型" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "控制類型" + } + } + } }, "Controls the blinking LED on the device. For most devices this will control one of the up to 4 LEDS, the charger and GPS LEDs are not controllable." : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Контролише трептајући ЛЕД на уређају. За већину уређаја ово ће контролисати један од до максималних 4 ЛЕД, ЛЕД пуњења и ГПС ЛЕД диоде се не могу контролисати." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "控制设备上闪烁的 LED。 对大多数设备而言,这将控制最多 4 个 LED 中的一个,充电指示灯和 GPS 状态灯无法控制。" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "控制裝置上的閃爍燈。對於大多數裝置,這將控制最多 4 個 LED 中的一個,充電器和 GPS 燈無法控制。" + } + } + } }, "Convex Hull" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Konvexe Hülle" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Конвексна љуштура" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "凸包" + } + } + } }, "Coordinate" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Koordinate" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Координате" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "座標" + } + } + } }, "Coordinate %@, %@" : { "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Koordinate %1$@, %2$@" + } + }, "en" : { "stringUnit" : { "state" : "new", "value" : "Coordinate %1$@, %2$@" } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Координате %1$@, %2$@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "坐标 %1$@, %2$@" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "座標 %1$@, %2$@" + } } } }, "Coordinates:" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Koordinaten:" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Координате:" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "座標:" + } + } + } }, "copy" : { "localizations" : { @@ -4936,6 +8521,12 @@ "value" : "Kopiera" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Копирај" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -4951,106 +8542,280 @@ } }, "Could not find node" : { - - }, - "Counter Clockwise Rotary Event" : { - - }, - "Create Waypoint" : { - - }, - "Created: %@" : { - - }, - "current" : { - "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Current" + "value" : "Knoten nicht gefunden" } }, - "en" : { + "sr" : { "stringUnit" : { "state" : "translated", - "value" : "Current" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Actuel" - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "נוכחי" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Bieżący" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Atual" - } - }, - "se" : { - "stringUnit" : { - "state" : "translated", - "value" : "Aktuell" + "value" : "Није могуће наћи чвор" } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "当前" + "value" : "无法找到节点" } }, "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "目前" + "value" : "找不到節點" + } + } + } + }, + "Counter Clockwise Rotary Event" : { + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ротациони догађај у смеру супротном од казаљке на сату" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "逆時針旋轉事件" + } + } + } + }, + "Create Waypoint" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wegpunkt erstellen" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Креирајте путну тачку" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "建立航點" + } + } + } + }, + "Created: %@" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Erstellt: %@" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Креирано : %@" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "建立時間: %@" } } } }, "Current Firmware Version: %@" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aktuelle Firmware Version: %@" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Тренутна верзија фирмвера: %@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "当前固件版本号:%@" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "目前韌體版本: %@" + } + } + } }, "Current Firmware Version: %@, Latest Firmware Version: %@" : { "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aktuelle Firmware Version: %1$@, neuste Firmware Version %2$@" + } + }, "en" : { "stringUnit" : { "state" : "new", "value" : "Current Firmware Version: %1$@, Latest Firmware Version: %2$@" } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Тренутна верзија фирмвера: %1$@, најновија верзија фирмвера: %2$@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "当前固件版本号:%1$@,最新固件版本号:%2$@" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "目前韌體版本:%1$@,最新韌體版本:%2$@" + } } } }, "Current: %lld" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aktuell: %lld" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Тренутно: %lld" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "目前: %lld" + } + } + } }, - "Currently the reccomended 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." : { - + "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" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Тренутно препоручени начин за ажурирање ЕСП32 уређаја је коришћење веб флешера на десктоп рачунару из прегледача заснованог на хрому. Не ради на мобилним уређајима или преко BLE-а." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "目前,更新 ESP32 设备的推荐方法是在电脑上使用基于 Chrome 浏览器的 Web Flasher。该方法不适用于移动设备或通过 BLE 进行更新。" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "目前建議更新 ESP32 裝置的方式是使用基於 Chrome 的桌面電腦瀏覽器上的網頁閃爍程式。它不適用於行動裝置或透過藍牙低功耗 (BLE) 傳輸。" + } + } + } }, "Date" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Datum" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Датум" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "日期" + } + } + } }, "Debug" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Дебагуј" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "Debug" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "調試" + } + } + } }, "Debug Logs" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Fehlersuchprotokolle" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Дебаг логови" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "調試日誌" + } + } + } }, "Debug Logs%@" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Debug логови%@" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "調試日誌%@" + } + } + } }, "default" : { "extractionState" : "migrated", @@ -5097,6 +8862,12 @@ "value" : "Standard" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Подразумевано" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -5112,7 +8883,55 @@ } }, "Default" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Standard" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Подразумевано" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "預設" + } + } + } + }, + "default.128x64.screen.layout" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Default 128x64 screen layout" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Подразумевани изглед екрана 128x64" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "默认 128x64 屏幕布局" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "預設 128x64 螢幕佈局" + } + } + } }, "delete" : { "localizations" : { @@ -5158,6 +8977,12 @@ "value" : "Ta bort" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Обриши" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -5173,43 +8998,248 @@ } }, "Delete all environment metrics?" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Желите ли да избришете све показатеље окружења?" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "删除所有环境指标?" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "刪除所有環境指標?" + } + } + } }, "Delete all map tiles?" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Избрисати све плочице мапе?" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "删除所有瓦片地图?" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "刪除所有地圖圖塊?" + } + } + } }, "Delete all positions?" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Избрисати све позиције?" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "刪除所有位置?" + } + } + } }, "Delete Message" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Обриши поруку" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "刪除訊息" + } + } + } }, "Delete Messages" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Обриши поруке" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "刪除訊息" + } + } + } }, "Delete Node" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Knoten löschen" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Обриши чвор" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "刪除節點" + } + } + } }, "Delete Node?" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Knoten löschen?" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Обрисати чвор?" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "刪除節點?" + } + } + } }, "Description" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Опис" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "描述" + } + } + } }, "Description must be less than 100 bytes" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Опис мора бити испод 100 бајтова" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "描述必须少于 100 字节" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "描述必須少於 100 個位元組。" + } + } + } }, "Detection" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Откривање" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "偵測" + } + } + } }, "Detection event" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Догађај откривања" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "偵測事件" + } + } + } }, "Detection Sensor Log" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Логови сензора откривања" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "偵測感應器日誌" + } + } + } }, "Detection sensor messages are received as text messages. If you enable notifications you will recieve a notification for each detection message received and a corresponding unread message badge." : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Поруке сензора за откривање се примају као текстуалне поруке. Ако омогућите обавештења, добићете обавештење за сваку примљену поруку за откривање и одговарајућу значку непрочитане поруке." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "检测传感器信息以文本信息的形式接收。如果启用通知功能,则每收到一条检测信息都会收到一条通知,并显示相应的未读信息。" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "偵測感應器訊息會以文字訊息的形式接收。如果您啟用通知,則每次收到偵測訊息時都會收到通知,並顯示相應的未讀訊息徽章。" + } + } + } }, "detection.sensor" : { "localizations" : { @@ -5255,6 +9285,12 @@ "value" : "Detektionssensor" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Сензор откривања" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -5300,6 +9336,24 @@ "state" : "translated", "value" : "Konfiguration av Detektionssensor" } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Подешавања ензора откривања" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "检测传感器配置" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "偵測感應器組態" + } } } }, @@ -5334,11 +9388,48 @@ "state" : "translated", "value" : "Logg för Detektionssensor" } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Логови сензора откривања" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "检测传感器日志" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "偵測感應器日誌" + } } } }, "Developers" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Програмери" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "开发者" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "開發人員" + } + } + } }, "device" : { "localizations" : { @@ -5384,10 +9475,16 @@ "value" : "Enhet" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Уређај" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "电台" + "value" : "设备" } }, "zh-Hant-TW" : { @@ -5399,25 +9496,170 @@ } }, "Device GPS" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Geräte-GPS" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "GPS уређај" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "设备 GPS" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "裝置 GPS" + } + } + } }, "Device is managed by a mesh administrator, the user is unable to access any of the device settings." : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Уређајем управља администратор мреже, корисник не може да приступи ниједном подешавању уређаја." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "设备由 Mesh 管理员管理,用户无法访问任何设备设置。" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "此裝置由網格管理員管理,使用者無法存取任何裝置設定。" + } + } + } }, "Device Metrics" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Метрика уређаја" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "设备指标" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "裝置指標" + } + } + } }, "Device Metrics Log" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Логови метрике уређаја" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "设备指标日志" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "裝置指標日誌" + } + } + } }, "Device Model: %@" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gerätemodell: %@" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Модел уређаја: %@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "设备模型:%@" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "裝置型號:%@" + } + } + } }, "Device Role" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Улога уређаја" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "设备角色" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "裝置角色" + } + } + } }, "Device Screen" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Екран уређаја" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "设备屏幕" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "裝置螢幕" + } + } + } }, "device.config" : { "localizations" : { @@ -5463,10 +9705,16 @@ "value" : "Enhetskonfiguration" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Подешавања уређаја" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "电台配置" + "value" : "设备配置" } }, "zh-Hant-TW" : { @@ -5482,7 +9730,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Device Configuration" + "value" : "Gerätekonfiguration" } }, "en" : { @@ -5515,6 +9763,12 @@ "value" : "Enhetsinställningar" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Подешавања уређаја" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -5531,12 +9785,6 @@ }, "device.metrics.delete" : { "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Delete all device metrics?" - } - }, "en" : { "stringUnit" : { "state" : "translated", @@ -5573,10 +9821,16 @@ "value" : "Ta bort alla enhetsmätvärden?" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Избришите све метрике уређаја?" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "删除所有电台指标?" + "value" : "删除所有设备指标?" } }, "zh-Hant-TW" : { @@ -5631,10 +9885,16 @@ "value" : "Logg för Enhetsmätvärden" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Логови метрике уређаја" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "电台指标日志" + "value" : "设备指标日志" } }, "zh-Hant-TW" : { @@ -5690,10 +9950,16 @@ "value" : "Appansluten eller fristående meddelandeenhet." } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Апликација повезана или самостални уређај за размену порука." + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "标准模式 - App 可以连接到电台进行收发操作,并且会自动转发 Mesh 网络中其他节点的消息。" + "value" : "连接 App 或独立的消息发送设备。" } }, "zh-Hant-TW" : { @@ -5710,7 +9976,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : " Used for nodes that \"only speak when spoken to\" Turns all of the routine broadcasts but allows for ad-hoc communication. Still rebroadcasts, but with local only rebroadcast mode (known meshes only). Can be used for private operation or to dramatically reduce airtime / power consumption." + "value" : "Gerät, das nur bei Bedarf sendet, um nicht entdeckt zu werden oder Strom zu sparen." } }, "en" : { @@ -5749,10 +10015,16 @@ "value" : "Enhet som endast sänder ut när det behövs för stealth eller energibesparing." } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Уређај који емитује само по потреби ради прикривености или уштеде енергије." + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "用于\"只有在被请求时才发言\"的节点。关闭所有常规广播,但允许临时通信。仍然会进行转发,但采用本地转发模式(仅限已知的网络)。可用于私密操作或大幅减少空中时间/功耗。" + "value" : "只在需要时才广播的设备,以达到隐蔽或省电的目的。" } }, "zh-Hant-TW" : { @@ -5769,7 +10041,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Client Leise - Das selbe wie Client, außer das die Pakete nicht über diesen Node weitergeleitet werden. Nimmt nicht am Mesh-Routing teil." + "value" : "Gerät, das keine Pakete von anderen Geräten weiterleitet." } }, "en" : { @@ -5808,10 +10080,16 @@ "value" : "Enhet som inte vidarebefordrar paket från andra enheter." } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Уређај који не прослеђује пакете примљене од других уређаја." + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "静默模式 - 与标准模式类似,App 可以连接到电台进行收发操作,但不会转发 Mesh 网络中其他节点的消息。" + "value" : "不转发其他设备数据包的设备。" } }, "zh-Hant-TW" : { @@ -5828,7 +10106,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Broadcasts location as message to default channel regularly for to assist with device recovery." + "value" : "Sendet den Standort regelmäßig als Nachricht an den Standardkanal, um die Suche nach dem Gerät zu unterstützen." } }, "en" : { @@ -5867,16 +10145,424 @@ "value" : "Sänder regelbundet ut plats som meddelande till standardkanalen för att underlätta återhämtning av enheten." } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Редовно емитује локацију као поруку подразумеваном каналу ради помоћи при проналаску уређаја." + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "定期向默认通道广播位置信息,以帮助寻回设备。" + "value" : "定期向默认信道发送位置信息,以协助设备恢复。" } }, "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "遺失物模式 - 用於自動頻繁地向網狀網路發送一條包含設備當前位置的短信:\"I'm lost! Position: lat / long\"" + "value" : "定期透過預設通道廣播位置資訊,以協助設備找回。" + } + } + } + }, + "device.role.name.client" : { + "extractionState" : "manual", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Client" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Client" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Клијент" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "客户端" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "客戶端" + } + } + } + }, + "device.role.name.clientHidden" : { + "extractionState" : "manual", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Client - Versteckt" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Client Hidden" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Скривени клијент" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "客户端隐藏" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "客戶端隱藏" + } + } + } + }, + "device.role.name.clientMute" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Client Mute" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Клијент мутиран" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "客户端静默" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "客戶端靜音" + } + } + } + }, + "device.role.name.lostAndFound" : { + "extractionState" : "manual", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tracker" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lost and Found" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Изгубљено и нађено" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "失物招领" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "遺失與取回" + } + } + } + }, + "device.role.name.repeater" : { + "extractionState" : "manual", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Repeater" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Repeater" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Поновљач" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "中继" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "中繼器" + } + } + } + }, + "device.role.name.router" : { + "extractionState" : "manual", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Router" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Router" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Рутер" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "路由" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "路由器" + } + } + } + }, + "device.role.name.routerClient" : { + "extractionState" : "manual", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Router & Client" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Router & Client" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Рутер и клијент" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "路由 & 客户端" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "路由器與客戶端" + } + } + } + }, + "device.role.name.routerlate" : { + "extractionState" : "manual", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Router mit Verzögerung" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Router Late" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "路由器延遲" + } + } + } + }, + "device.role.name.sensor" : { + "extractionState" : "manual", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sensor" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sensor" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Сензор" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "传感器" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "感應器" + } + } + } + }, + "device.role.name.tak" : { + "extractionState" : "manual", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "TAK" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "TAK" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "TAK" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "TAK" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "TAK" + } + } + } + }, + "device.role.name.takTracker" : { + "extractionState" : "manual", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "TAK Tracker" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "TAK Tracker" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "ТАК Трекер" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "TAK 追踪器" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "TAK 追蹤器" + } + } + } + }, + "device.role.name.tracker" : { + "extractionState" : "manual", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tracker" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tracker" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Трекер" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "追踪器" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "追蹤器" } } } @@ -5886,8 +10572,8 @@ "localizations" : { "de" : { "stringUnit" : { - "state" : "needs_review", - "value" : "Repeater - Mesh packets will prefer to be routed over this node. This role eliminates unnecessary overhead such as NodeInfo, DeviceTelemetry, and any other mesh packet, resulting in the device not appearing as part of the network. Please see Rebroadcast Mode for additional settings specific to this role." + "state" : "translated", + "value" : "Infrastruktur-Knoten nur auf einem Turm oder einer Bergspitze. Nicht für Dächer oder mobile Knoten verwenden. Übermittelt Nachrichten mit minimalem Mehraufwand. Nicht sichtbar in der Knotenliste." } }, "en" : { @@ -5926,16 +10612,22 @@ "value" : "Infrastrukturnod för att utöka nätverkstäckningen genom att vidarebefordra meddelanden med minimal overhead. Syns inte i Noder-listan." } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Инфраструктурни чвор само на торњу или врху планине. Није намењен за кровове или мобилне чворове. Прослеђује поруке уз минимално оптерећење. Није видљив у листи чворова." + } + }, "zh-Hans" : { "stringUnit" : { - "state" : "needs_review", - "value" : "中继模式 - Mesh 网络数据包将优先通过此节点路由。此模式可消除不必要的开销,如节点信息、设备遥测和任何其他 Mesh 数据包,从而使设备不显示为 Mesh 网络的一部分。有关此角色的其他特定设置,请参阅转播模式。" + "state" : "translated", + "value" : "仅用于塔顶或山顶的基础设施节点。 不得用于屋顶或移动节点。以最小的开销中继信息。在节点列表中不可见。" } }, "zh-Hant-TW" : { "stringUnit" : { - "state" : "needs_review", - "value" : "中繼模式 - Mesh 網路數據包將優先通過此中繼點路由。此模式可消除不必要的開銷,如 NodeInfo、DeviceTelemetry 和任何其他 Mesh 數據包,從而使設備不顯示為 Mesh 網路的一部分。有關此角色的其他特定設置,請參閱轉播模式。" + "state" : "translated", + "value" : "僅用於高塔或山頂的基礎架構節點。不適用於屋頂或移動節點。以最小的開銷中繼訊息。在節點清單中不可見。" } } } @@ -5945,8 +10637,8 @@ "localizations" : { "de" : { "stringUnit" : { - "state" : "needs_review", - "value" : "Router - Mesh Pakete werden bevorzugt über diesen Node gerouted. Dieser Node wird nicht von einer Client App benutzt. WLAN, Bluetooth und Display sind aus." + "state" : "translated", + "value" : "Router - Mesh Pakete werden bevorzugt über diesen Knoten gerouted. Dieser Knoten wird nicht von einer Client App benutzt. WLAN, Bluetooth und Display sind aus." } }, "en" : { @@ -5985,16 +10677,22 @@ "value" : "Infrastrukturnod för att utöka nätverkstäckningen genom att vidarebefordra meddelanden. Synlig i Noder-listan." } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Инфраструктурни чвор само на торњу или врху планине. Не користи се за кровове или мобилне чворове. Потребна му је изузетна покривеност. Видљиво на листи чворова." + } + }, "zh-Hans" : { "stringUnit" : { - "state" : "needs_review", - "value" : "纯路由模式 - 自动转发 Mesh 网络中其他节点的消息,中继模式下屏幕会熄灭,Wi-Fi 和蓝牙将会进入睡眠模式,App 将无法连接到电台进行收发操作。" + "state" : "translated", + "value" : "仅用于塔顶或山顶的基础设施节点。 不得用于屋顶或移动节点。 需要特殊的覆盖范围。在节点列表中可见。" } }, "zh-Hant-TW" : { "stringUnit" : { - "state" : "needs_review", - "value" : "纯路由模式 - 自動轉發 Mesh 網路中其他中繼點的消息,中繼模式下螢幕會熄滅,Wi-Fi 和藍芽將會進入睡眠模式,App 將無法連接到電台進行收發操作。" + "state" : "translated", + "value" : "僅用於高塔或山頂的基礎架構節點。不適用於屋頂或移動節點。需要超凡的覆蓋範圍。在節點清單中可見。" } } } @@ -6004,8 +10702,8 @@ "localizations" : { "de" : { "stringUnit" : { - "state" : "needs_review", - "value" : "Router Client - Mesh Pakete werden bevorzugt über diesen Node gerouted. Der Router Client kann parallel auch von einer Client-App genutzt werden." + "state" : "translated", + "value" : "Router Client - Mesh Pakete werden bevorzugt über diesen Knoten gerouted. Der Router Client kann parallel auch von einer Client-App genutzt werden." } }, "en" : { @@ -6044,10 +10742,16 @@ "value" : "Kombination av både ROUTER och CLIENT. Inte för mobila enheter." } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Застарело. Користи клијент ролу." + } + }, "zh-Hans" : { "stringUnit" : { - "state" : "needs_review", - "value" : "路由客户端模式 - 优先转发 Mesh 网络中其他节点的消息,App 也可以连接到电台进行收发操作。" + "state" : "translated", + "value" : "已废弃的角色,使用客户端。" } }, "zh-Hant-TW" : { @@ -6058,13 +10762,36 @@ } } }, + "device.role.routerlate" : { + "extractionState" : "manual", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Infrastrukturknoten, der Pakete immer nur einmal weiterleitet, aber erst nach allen anderen Betriebsarten, um eine zusätzliche Abdeckung für lokale Cluster zu gewährleisten. Sichtbar in der Liste der Knoten." + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Infrastructure node that always rebroadcasts packets once but only after all other modes, ensuring additional coverage for local clusters. Visible in Nodes list." + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "基礎架構節點,會在所有其他模式都執行完畢後,將封包重新廣播一次,以確保本地群集獲得額外的覆蓋範圍。在節點清單中可見。" + } + } + } + }, "device.role.sensor" : { "extractionState" : "migrated", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Broadcasts telemetry packets as priority." + "value" : "Sendet Telemetriepakete mit Priorität." } }, "en" : { @@ -6103,6 +10830,12 @@ "value" : "Sänder ut telemetripaket som prioritet." } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Емитује телеметријске пакете као приоритет." + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -6123,7 +10856,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Optimized for ATAK system communication, reduces routine broadcasts." + "value" : "Optimiert für ATAK-Systemkommunikation, verringert die Anzahl der Routineübertragungen." } }, "en" : { @@ -6162,6 +10895,12 @@ "value" : "Optimerad för kommunikation med ATAK-systemet, minskar rutinutsändningar." } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Оптимизован за комуникацију са ATAK системом, смањује рутинске емисије." + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -6171,7 +10910,7 @@ "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "TAK模式 - 優化了 ATAK 系統通訊,減少常規廣播。" + "value" : "優化 ATAK 系統通訊,減少常規廣播。" } } } @@ -6182,7 +10921,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Enables automatic TAK PLI broadcasts and reduces routine broadcasts." + "value" : "Aktiviert automatische TAK-PLI-Übertragungen und verringert die Anzahl der Routineübertragungen." } }, "en" : { @@ -6221,6 +10960,12 @@ "value" : "Aktiverar automatiska TAK PLI-utsändningar och minskar rutinutsändningar." } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Омогућава аутоматске TAK PLI емисије и смањује рутинске емисије." + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -6241,7 +10986,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Tracker - For use with devices intended as a GPS tracker. Position packets sent from this device will be higher priority, with position broadcasting every two minutes. Smart Position Broadcast will default to off." + "value" : "Sendet GPS-Positionspakete mit Priorität." } }, "en" : { @@ -6280,6 +11025,12 @@ "value" : "Sänder ut GPS-positionspaket som prioritet." } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Емитује пакете са GPS позицијом као приоритет." + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -6295,19 +11046,114 @@ } }, "Dilution of precision (DOP) PDOP used by default" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Разређење прецизности (DOP) PDOP се користи као подразумевано" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "預設使用精度稀釋 DOP(PDOP)" + } + } + } }, "Direct" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Direkt" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Директно" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "直频" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "直接連線" + } + } + } }, "Direct Message Help" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Помоћ за директне поруке" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "私信帮助" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "直接訊息說明" + } + } + } }, "Direct messages are using the new public key infrastructure for encryption. Requires firmware version 2.5 or greater." : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Директне поруке користе нову инфраструктуру јавних кључева за енкрипцију. Захтева верзију фирмвера 2.5 или новију." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "私聊使用新的公钥基础设施进行加密。需要 2.5 或更高版本的固件。" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "私人訊息現在使用新的公鑰基礎設施加密。需要韌體版本 2.5 或更高版本。" + } + } + } }, "Direct messages are using the shared key for the channel." : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Директне поруке користе дељени кључ за канал." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "私聊使用频道的共享密钥。" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "私人訊息正在使用通道的共享金鑰。" + } + } + } }, "direct.messages" : { "localizations" : { @@ -6353,10 +11199,16 @@ "value" : "Direktmeddelanden" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Директне поруке" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "直频消息" + "value" : "私聊" } }, "zh-Hant-TW" : { @@ -6368,7 +11220,32 @@ } }, "Disabled" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ausgeschaltet" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Онемогућено" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "禁用" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "停用" + } + } + } }, "disconnect" : { "localizations" : { @@ -6414,6 +11291,12 @@ "value" : "Koppla från" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Прекините везу" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -6433,7 +11316,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Dismiss Keyboard" + "value" : "Tastatur ausblenden" } }, "en" : { @@ -6472,10 +11355,16 @@ "value" : "Stäng" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Отпусти" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "隐藏键盘" + "value" : "收起键盘" } }, "zh-Hant-TW" : { @@ -6530,10 +11419,16 @@ "value" : "Skärm" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Приказ" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "屏幕(电台屏幕)" + "value" : "显示" } }, "zh-Hant-TW" : { @@ -6545,22 +11440,73 @@ } }, "Display Fahrenheit" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Приказ фаренхајта" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "展示华氏度" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "顯示華氏溫度" + } + } + } }, "Display Mode" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Режим приказа" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "显示模式" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "顯示模式" + } + } + } }, "Display Units" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Јединице приказа" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "显示单位" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "顯示單位" + } + } + } }, "display.config" : { "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Display Config" - } - }, "en" : { "stringUnit" : { "state" : "translated", @@ -6597,6 +11543,12 @@ "value" : "Skärmkonfiguration" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Подешавања приказа" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -6656,6 +11608,12 @@ "value" : "Distans" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Раздаљина" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -6671,28 +11629,180 @@ } }, "Distance" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Distanz" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Раздаљина" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "距离" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "距離" + } + } + } }, "Documentation" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Документација" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "文档" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "文件說明" + } + } + } }, "Double Tap as Button" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Двоструки додир као дугме" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "双击作为按钮" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "雙擊作為按鈕" + } + } + } }, "Downlink Enabled" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Дозвољен даунлинк" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "启用下载" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "下行鏈路已啟用" + } + } + } }, "Drag & Drop Firmware Update" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ажурирање фирмвера методом превуци-и-испусти" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "拖放升级固件" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "拖放韌體更新" + } + } + } }, "Drag & Drop Firmware Update Documentation" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Документација ажурирања фирмвера методом превуци-и-испусти" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "拖放升级固件文档" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "拖放韌體更新文件說明" + } + } + } }, "Drag & Drop is the recommended way to update firmware for NRF devices. If your iPhone or iPad is USB-C it will work with your regular USB-C charging cable, for lightning devices you need the Apple Lightning to USB camera adaptor." : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Превуци-и-испусти је препоручен начин за ажурирање фирмвера на NRF уређајима. Ако ваш iPhone или iPad има USB-C, радиће са вашим уобичајеним USB-C каблом за пуњење. За уређаје са Lightning портом потребан је Apple Lightning to USB адаптер за камеру." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "拖放是更新 NRF 设备固件的推荐方式。如果您的 iPhone 或 iPad 是 USB-C 接口,则可以使用普通的 USB-C 充电线;如果是 Lightning 设备,则需要使用 Apple Lightning to USB 摄像头适配器。" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "拖放是更新 NRF 裝置韌體的推薦方式。如果您 iPhone 或 iPad 是 USB-C 型號,可以使用您的正常 USB-C 充電線;對於 Lightning 設備,您需要 Apple Lightning 轉 USB 攝像頭轉接器。" + } + } + } }, "Drop Pin in Maps" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Постави ознаку на мапама" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "地圖中標記位置" + } + } + } }, "echo" : { "localizations" : { @@ -6738,6 +11848,12 @@ "value" : "Eko" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ехо" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -6753,10 +11869,36 @@ } }, "Editing Waypoint" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Уређивање путне тачке" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "編輯航點" + } + } + } }, "Elev. Gain" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Повећање надморске висине" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "升高增益" + } + } + } }, "email.address" : { "extractionState" : "manual", @@ -6803,6 +11945,12 @@ "value" : "E-postadress" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Имејл адреса" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -6818,13 +11966,70 @@ } }, "Emoji" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Емоџи" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "Emoji" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "表情符號" + } + } + } }, "Empty" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Празно" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "空" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "空白" + } + } + } }, "Enable Notifications" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Омогући обавештења" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "启用通知" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "啟用通知" + } + } + } }, "enabled" : { "localizations" : { @@ -6870,6 +12075,12 @@ "value" : "Aktiverad" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Омогућено" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -6885,22 +12096,130 @@ } }, "Enables devices with native I2S audio output to use the RTTTL over speaker like a buzzer. T-Watch S3 and T-Deck for example have this capability." : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Омогућава уређајима са изворним I2S аудио излазом да користе РТТТЛ преко звучника као звучник. Т-Ватцх СКСНУМКС и Т-Децк на пример имају ову могућност." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "使具有本地 I2S 音频输出的设备能够通过扬声器使用 RTTTL,就像使用蜂鸣器一样。例如,T-Watch S3 和 T-Deck 就具有这种功能。" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "啟用具有原生 I2S 音頻輸出的裝置,可以使用揚聲器上的 RTTTL 鈴聲,例如蜂鳴器。T-Watch S3 和 T-Deck 具有此功能。" + } + } + } }, "Enables the detection sensor module, it needs to be enabled on both the node with the sensor, and any nodes that you want to receive detection sensor text messages or view the detection sensor log and chart." : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Омогућава модул сензора детекције. Потребно је да буде омогућен и на чвору са сензором, и на свим чворовима које желите да примате текстуалне поруке сензора детекције или да видите дневник и графикон сензора детекције." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "启用检测传感器模块,需要在装有传感器的节点和要接收检测传感器文本信息或查看检测传感器日志和图表的任何节点上启用该模块。" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "啟用偵測感應器模組,需要在具有感應器的節點和任何要接收偵測感應器文字訊息或查看偵測感應器日誌和圖表的節點上都啟用。" + } + } + } }, "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" : "启用存储和转发模块。客户端和路由器设备都必须启用存储和转发功能。" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "啟用「儲存並轉發」模組。客戶端和路由器設備都需要啟用「儲存並轉發」。" + } + } + } }, "Enabling Ethernet will disable the bluetooth connection to the app." : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Омогућавање етернета ће онемогућити блутут везу са апликацијом." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "启用以太网将禁用应用程序的蓝牙连接。" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "啟用以太網路將會停用與應用程式的藍牙連線。" + } + } + } }, "Enabling WiFi will disable the bluetooth connection to the app." : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Омогућавање ВајФаја ће онемогућити блутут везу са апликацијом." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "启用 WiFi 将禁用应用程序的蓝牙连接。" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "啟用 Wi-Fi 將會停用與應用程式的藍牙連線。" + } + } + } }, "Encoder Press Event" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Догађај притиска енкодера" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "編碼器按下事件" + } + } + } }, "encrypted" : { "localizations" : { @@ -6946,6 +12265,12 @@ "value" : "Krypterad" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Шифровано" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -6961,52 +12286,402 @@ } }, "Encrypted" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Verschlüsselt" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Шифровано" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "加密" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "已加密" + } + } + } }, "Encryption Enabled" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Омогућено шифровање" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "启用加密" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "加密已啟用" + } + } + } }, "Enter DFU Mode" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "DFÜ-Modus aktivieren" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Уђите у DFU режим" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "进入 DFU 模式" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "進入DFU模式" + } + } + } }, "environment" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "окружење" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "环境" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "環境" + } + } + } }, "Environment" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Umgebung" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Окружење" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "环境" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "環境" + } + } + } }, "Environment Metrics Log" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Дневник метрика окружења" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "环境指标日志" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "環境指標日誌" + } + } + } }, "Erase all app data?" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Alle App-Daten löschen?" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Избрисати све податке апликације?" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "擦除所有 App 数据?" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "清除所有應用程式資料?" + } + } + } }, "Erase all device and app data?" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Alle Geräte- und App-Daten löschen?" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Избрисати све податке уређаја и апликације?" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "擦除所有设备和 App 数据?" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "清除所有裝置和應用程式資料?" + } + } + } }, "Error: %@" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Грешка: %@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "错误:%@" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "錯誤:%@" + } + } + } }, "ESP 32 OTA update is a work in progress, click the button below to send your device a reboot into ota admin message." : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "ESP32 OTA ажурирање је у развоју, кликните на дугме испод да бисте послали уређају поруку за поновно покретање у OTA администраторски режим." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "ESP 32 OTA 更新正在进行中,请单击下面的按钮向您的设备发送重新启动进入 OTA 管理信息。" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "ESP 32 韌體更新功能仍在開發中,點擊下面的按鈕即可將設備重新啟動進入 OTA 管理訊息。" + } + } + } }, "ESP32 Device Firmware Update" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ажурирање фирмвера за ESP32 уређај" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "ESP32 设备固件升级" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "ESP32 裝置韌體更新" + } + } + } }, "Ethernet Options" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Етернет опције" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "以太網選項" + } + } + } + }, + "european.union.433mhz" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "European Union 433MHz" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Европска унија 433MHz" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "歐盟 433MHz" + } + } + } + }, + "european.union.868mhz" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "European Union 868MHz" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Европска унија 868MHz" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "歐盟 868MHz" + } + } + } }, "Exchange Positions" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Размени локације" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "交換位置" + } + } + } }, "Expire" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Истиче" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "過期" + } + } + } }, "Expires" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Истиче" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "過期" + } + } + } }, "Expires: %@" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Истиче: %@" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "過期時間: %@" + } + } + } }, "export" : { "localizations" : { @@ -7052,6 +12727,12 @@ "value" : "Export" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Извоз" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -7110,6 +12791,12 @@ "value" : "Extern Notifikation" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Спољна обавештења" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -7168,6 +12855,12 @@ "value" : "Konfiguration av Extern Notifikation" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Подешавање спољних обавештења" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -7183,50 +12876,267 @@ } }, "Factory Reset" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Werkseinstellungen" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ресетовање на фабричка подешавања" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "出廠重置" + } + } + } }, "Factory reset your device and app? " : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gerät und App auf Werkseinstellungen zurücksetzen?" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Вратите уређај и апликацију на фабричка подешавања?" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "是否要將您的裝置和應用程式還原到出廠設定?" + } + } + } }, "Failed to encode message content" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Неуспело кодирање садржаја поруке" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "訊息編碼失敗" + } + } + } }, "Failed to get a valid position to exchange" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Добијање важеће позиције за размену није успело" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "取得有效交換位置失敗" + } + } + } }, "Failed to get a valid position to exchange." : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Добијање важеће позиције за размену није успело." + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "取得有效交換位置失敗。" + } + } + } }, "Favorite" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Favorit" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Омиљени" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "最愛" + } + } + } }, "Favorites" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Favoriten" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Омиљени" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "最愛" + } + } + } }, "Favorites and nodes with recent messages show up at the top of the contact list." : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Омиљени чворови и чворови са недавно примљеним порукама појављују се на врху листе контаката." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "收藏夹和有最近信息的节点会显示在联系人列表的顶部。" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "最近有訊息的收藏聯絡人和節點會顯示在聯絡人清單頂部。" + } + } + } }, "Fetch the latest position of a cetain node" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Letzte Position eines Knotens holen" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Преузмите најновију позицију одређеног чвора" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "取得特定節點的最新位置" + } + } + } }, "Fifteen Minutes" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Петнаест минута" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "十五分鐘" + } + } + } }, "File Storage" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Складиште података" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "檔案儲存" + } + } + } }, "Find a contact" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kontakt suchen" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Пронађи контакт" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "尋找聯絡人" + } + } + } }, "Find a node" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Einen Knoten finden" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Пронађи чвор" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "尋找節點" + } + } + } }, "finish" : { "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Finish" + "value" : "Beenden" } }, "en" : { @@ -7265,6 +13175,12 @@ "value" : "Avsluta" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Заврши" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -7280,13 +13196,82 @@ } }, "Firmware" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Firmware" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Фирмвер" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "固件" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "韌體" + } + } + } }, "Firmware update docs" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Документи за ажурирање фирмвера" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "固件升级文档" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "韌體更新文件" + } + } + } }, "Firmware Updates" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Firmwareaktualisierungen" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ажурирања фирмвера" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "固件升级" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "韌體更新" + } + } + } }, "firmware.version" : { "localizations" : { @@ -7332,6 +13317,12 @@ "value" : "Firmwareversion" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Верзија фирмвера" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -7391,58 +13382,275 @@ "value" : "Okänd Firmwareversion upptäckt, kan inte ansluta till enheten." } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Откривена је неподржана верзија фирмвера, није могуће повезати са уређајем." + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "检测到不支持的固件版本,无法连接到电台。" + "value" : "检测到不支持的固件版本,无法连接到设备。" } }, "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "檢測到不支援的韌體版本,無法連接到電台。" + "value" : "偵測到不支援的韌體版本,無法連接到裝置。" } } } }, "First heard" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Прво откривање" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "首次聽到" + } + } + } }, "Five Minutes" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Fünf Minuten" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Пет минута" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "五分鐘" + } + } + } }, "Fixed Position" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Фиксна локација" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "固定位置" + } + } + } }, "Flip Screen" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Окрени екран" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "翻轉螢幕" + } + } + } }, "Flip screen vertically" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Окрени екран вертикално" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "垂直翻轉螢幕" + } + } + } }, "For all Mqtt functionality other than the map report you must also set uplink and downlink for each channel you want to bridge over Mqtt." : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "За сву MQTT функционалност осим извештаја на мапи, такође морате подесити uplink и downlink за сваки канал који желите да прележете преко MQTT-а.”" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "对于除地图报告外的所有 MQTT 功能,您还必须为希望通过 MQTT 桥接的每个信道设置上行和下行链路。" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "除了地圖報告以外的所有 MQTT 功能,您還必須為要透過 MQTT 橋接的每個通道設定上行和下行鏈路。" + } + } + } }, "For everyone" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Für alle" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "За све" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "對所有人" + } + } + } }, "For me" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Für mich" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "За мене" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "對我來說" + } + } + } }, "Frequency" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Frequenz" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Фреквенција" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "頻率" + } + } + } }, "Frequency Override" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Измена фреквенције" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "頻率覆寫" + } + } + } }, "Frequency Slot" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Фреквенцијски слот" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "頻率時段" + } + } + } }, "Friendly name" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Пријатељски назив" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "友好名称" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "友善名稱" + } + } + } }, "Friendly name used to format message sent to mesh. Example: A name \"Motion\" would result in a message \"Motion detected\"" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Пријатељски назив који се користи за форматирање поруке послате на мрежу. На пример: Назив „Motion” довео би до поруке „Motion detected”." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "用于格式化发送到 Mesh 网络的信息的友好名称。例如名称为 “运动”时,发送的信息为 “检测到运动”。" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "用於格式化傳送到網格的消息的友善名稱。例如:\"Motion\" 這個名稱會導致訊息顯示為 \"偵測到移動\" 。" + } + } + } }, "gas" : { "extractionState" : "manual", @@ -7489,6 +13697,12 @@ "value" : "Gas" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Гас" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -7506,12 +13720,6 @@ "gas.resistance" : { "extractionState" : "manual", "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Gas Resistance" - } - }, "en" : { "stringUnit" : { "state" : "translated", @@ -7548,6 +13756,12 @@ "value" : "Gasmotstånd" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Отпорност на гас" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -7606,6 +13820,12 @@ "value" : "Generera QR-kod" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Генерисање QR кода" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -7621,49 +13841,304 @@ } }, "Get custom waterproof solar and detection sensor router nodes, aluminium desktop nodes and rugged handsets." : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Набавите прилагођене водоотпорне соларне и детекционе сензорске рутер чворове, алуминијумске десктоп чворове и издржљиве мобилне уређаје." + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "取得客製防水太陽能和偵測感應器路由節點、鋁製桌面節點以及堅固的手機。" + } + } + } }, "Get Node Position" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Knotenposition ermitteln" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Добави позицију чвора" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "获取节点位置" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "取得節點位置" + } + } + } }, "Get NRF DFU from the App Store" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Преузмите NRF DFU из App Store-а" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "从 App Store 获取 NRF DFU" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "從 App Store 取得 NRF DFU" + } + } + } }, "Get the latest alpha firmware" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Добави најновији алфа фирмвер" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "获取最新测试版固件" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "取得最新的 Alpha 版韌體" + } + } + } }, "Get the latest stable firmware" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Добави најновији стабилни фирмвер" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "获取最新稳定版固件" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "取得最新的穩定版韌體" + } + } + } }, "GPIO" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "GPIO" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "GPIO" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "GPIO" + } + } + } }, "GPIO Output Duration" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Трајање GPIO излаза" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "GPIO 輸出持續時間" + } + } + } }, "GPIO pin for rotary encoder A port." : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "GPIO пин за A порт ротационог енкодера." + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "旋轉編碼器A埠的GPIO引腳。" + } + } + } }, "GPIO pin for rotary encoder B port." : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "GPIO пин за Б порт ротационог енкодера." + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "旋轉編碼器B埠的GPIO引腳。" + } + } + } }, "GPIO pin for rotary encoder Press port." : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "GPIO пин за порт клика ротационог енкодера." + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "旋轉編碼器按下埠的 GPIO 引腳。" + } + } + } }, "GPIO Pin to monitor" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "GPIO пин за надгледање" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "要監控的 GPIO 腳位" + } + } + } }, "GPS EN GPIO" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "GPS EN GPIO" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "GPS EN GPIO" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "GPS EN GPIO" + } + } + } }, "GPS Format" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "GPS формат" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "GPS 格式" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "GPS 格式" + } + } + } }, "GPS Receive GPIO" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "GPS пријем GPIO" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "GPS Receive GPIO" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "GPS 接收 GPIO" + } + } + } }, "GPS Transmit GPIO" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "GPS предаја GPIO" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "GPS Transmit GPIO" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "GPS 傳輸 GPIO" + } + } + } }, "gpsformat.dec" : { "extractionState" : "migrated", @@ -7710,6 +14185,12 @@ "value" : "Decimalgrader" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Формат децималних степени" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -7769,6 +14250,12 @@ "value" : "Grader Minuter Sekunder" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Степени Минути Секунде" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -7828,6 +14315,12 @@ "value" : "Militärt rutnätsreferenssystem" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Војни референтни систем мреже" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -7887,6 +14380,12 @@ "value" : "Öppen Platskod (även känd som Pluskoder)" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Отворени код локације (тј. Плус кодови)" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -7946,6 +14445,12 @@ "value" : "Ordnance Survey Rutnätsreferens" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Референца мреже Орданс Сурвеја" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -8005,6 +14510,12 @@ "value" : "Universal Transversal Mercator" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Универзални трансверзални Меркаторов пројекат" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -8022,6 +14533,12 @@ "gpsmode.disabled" : { "extractionState" : "migrated", "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ausgeschaltet" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -8051,12 +14568,36 @@ "state" : "translated", "value" : "Inaktiverad" } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Онемогућен" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "禁用" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "停用" + } } } }, "gpsmode.enabled" : { "extractionState" : "migrated", "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Eingeschaltet" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -8086,6 +14627,24 @@ "state" : "translated", "value" : "Aktiverad" } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Омогућен" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "启用" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "已啟用" + } } } }, @@ -8121,23 +14680,124 @@ "state" : "translated", "value" : "Inte närvarande" } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Није пристуно" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "不存在" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "不存在" + } } } }, "Group Message" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gruppennachricht" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Групна порука" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "群聊" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "群組訊息" + } + } + } }, "Gusts %@" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Јаки удари ветра %@" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "陣風 %@" + } + } + } }, "Hardware" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Хардвер" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "硬件" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "硬體" + } + } + } }, "Heading" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Смер" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "方向" + } + } + } }, "Heading: %@" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Смер: %@" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "標題: %@" + } + } + } }, "heard" : { "extractionState" : "migrated", @@ -8184,6 +14844,12 @@ "value" : "Hörd" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Чуо" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -8243,6 +14909,12 @@ "value" : "Senast Hörd" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Прво откривање" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -8258,70 +14930,470 @@ } }, "Help with App Development" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Помози при развоју апликације" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "帮助开发应用程序" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "協助應用程式開發" + } + } + } }, "Hide alerts" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Сакриј упозорења" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "隱藏警報" + } + } + } }, "Hide Alerts" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Сакриј алертове" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "隱藏警報" + } + } + } }, "HIGH" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "HOCH" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "ВИСОК" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "高" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "高" + } + } + } }, "History Return Max" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Максимални повратак историје" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "歷史紀錄最大數" + } + } + } }, "History Return Window" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Временски прозор поврата историје" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "歷史紀錄返回視窗" + } + } + } }, "Hops Away" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hops Entfernt" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Скокови удаљености" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "跳數距離" + } + } + } }, "Hops Away %d" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hops Entfernt %d" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Удаљено %d скокова" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "跳數距離 %d" + } + } + } }, "Hops Away:" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hops Entfernt:" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Скокови удаљености:" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "跳數距離:" + } + } + } }, "Hops Away: %d" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hops Entfernt: %d" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Скокови удаљености: %d" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "跳數距離: %d" + } + } + } }, "Hour" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Stunde" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Сат" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "小时" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "小時" + } + } + } }, "Hourly Duty Cycle" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Натпросечни циклус дужности по сату" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "每小時佔空比" + } + } + } }, "How long the screen remains on after the user button is pressed or messages are received." : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Колико дуго екран остаје укључен након притиска корисничког дугмета или пријема порука." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "按下用户按钮或收到消息后屏幕保持亮屏的时间。" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "使用者按鈕被按下或收到訊息後,螢幕保持開啟的時間長度。" + } + } + } }, "How often device metrics are sent out over the mesh. Default is 30 minutes." : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Колико често се метрике уређаја шаљу преко мреже. Подразумевано је 30 минута." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "设备指标通过网格发送的频率。默认为 30 分钟。" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "設備指標透過網格發送頻率。預設為 30 分鐘。" + } + } + } }, "How often power metrics are sent out over the mesh. Default is 30 minutes." : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Колико често се метрике снаге шаљу преко мреже. Подразумевано је 30 минута." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "通过网格发送功率指标的频率。默认为 30 分钟。" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "設備電力指標透過網格發送頻率。預設為 30 分鐘。" + } + } + } }, "How often sensor metrics are sent out over the mesh. Default is 30 minutes." : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Колико често се метрике сензора шаљу преко мреже. Подразумевано је 30 минута." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "通过网格发送传感器指标的频率。默认为 30 分钟。" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "感測器指標透過網格發送頻率。預設為 30 分鐘。" + } + } + } }, "How often should we try to get a GPS position." : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Колико често треба да покушамо да добијемо GPS позицију." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "尝试获取 GPS 定位的频率。" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "我們應該多久嘗試取得一次 GPS 位置?" + } + } + } }, "How often to send detection sensor state to mesh regardless of detection. Default is Never." : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Колико често да пошаљете стање сензора детекције у мрежу, без обзира на детекцију. Подразумевано је да се не шаље никада." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "无论是否检测到,向网格发送检测传感器状态的频率。默认为从不。" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "設定偵測感應器狀態何時傳送到網格,無論是否偵測到事件。預設為從不傳送。" + } + } + } }, "How to update Firmware" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wie wird die Firmware aktualisiert" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Како да ажурираш фирмвер" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "如何升级固件" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "如何更新韌體" + } + } + } }, "Hum" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Влажност" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "嗡嗡聲" + } + } + } }, "Humidity" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Luftfeuchtigkeit" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Влажност" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "濕度" + } + } + } }, "HUMIDITY" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "LUFTFEUCHTIGKEIT" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "ВЛАЖНОСТ" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "濕度" + } + } + } }, "hybrid" : { "extractionState" : "migrated", @@ -8368,6 +15440,12 @@ "value" : "Hybrid" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Хибридни" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -8377,7 +15455,7 @@ "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "混合" + "value" : "I'll wait for the English text you'd like me to translate. Once I receive it, I can provide the translation in zh-Hant-TW (Chinese Simplified). Please go ahead and share the text!" } } } @@ -8427,6 +15505,12 @@ "value" : "Hybrid Flygöversikt" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Хибридни надлет" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -8436,43 +15520,296 @@ "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "混合視圖" + "value" : "Sorry, I didn't understand what you said. Could you please repeat the text you'd like me to translate?" } } } }, "IAQ" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "IAQ" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "IAQ" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "空氣品質" + } + } + } }, "IAQ " : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "IAQ" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "IAQ " + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "空氣品質" + } + } + } }, "IAQ %lld" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "IAQ %lld" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "IAQ %lld" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "空氣品質 %lld" + } + } + } }, "Icon" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Иконица" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "图标" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "圖示" + } + } + } }, "If DOP is set, use HDOP / VDOP values instead of PDOP" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ако је DOP постављен, користите HDOP / VDOP вредности уместо PDOP-а" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "如果设置了 DOP,则使用 HDOP / VDOP 值而不是 PDOP" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "如果已設定 DOP,請使用 HDOP / VDOP 值,而不是 PDOP。" + } + } + } }, "If enabled, the 'output' Pin will be pulled active high, disabled means active low." : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ако је омогућено, 'output' пин ће бити активиран на високом нивоу, а ако је онемогућено, биће активиран на ниском нивоу." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "如果启用,“输出 ”引脚将被拉高,禁用则表示拉低。" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "如果啟用,'output' 腳位將會被拉高電平;若停用,則表示低電平。" + } + } + } }, "If it is hard to access your device's reset button enter DFU mode here." : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ако је тешко приступити дугмету за ресетовање уређаја, уђите у DFU режим овде." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "如果难以访问设备的重置按钮,请在此进入 DFU 模式。" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "如果難以存取裝置的重設按鈕,請在此處進入DFU模式。" + } + } + } }, "If set, any packets you send will be echoed back to your device." : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ако је подешено, сви пакети које пошаљете ће бити враћени (ехо) назад на ваш уређај." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "如果设置了,您发送的任何数据包都会回传到设备。" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "如果設定,您發送的所有資料包都會回傳到您的設備。" + } + } + } }, "If the default region topic is too busy you can choose a more local topic." : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ако је подразумевана тема региона превише заузета можете изабрати више локалну тему." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "如果默认区域话题太忙,您可以选择一个更本地化的话题。" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "如果預設區域主題過於忙碌,您可以選擇一個更本地化的主題。" + } + } + } }, "Ignore MQTT" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Игнориши MQTT" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "忽略 MQTT" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "忽略 MQTT" + } + } + } + }, + "Ignore Node" : { + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Игнориши чвор" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "忽略节点" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "忽略節點" + } + } + } + }, + "Ignored" : { + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Игнорисан" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "忽略" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "忽略" + } + } + } }, "Import Route" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Увозна рута" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "导入路线" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "匯入路由" + } + } + } }, "include" : { "localizations" : { @@ -8518,6 +15855,12 @@ "value" : "Inkludera" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Укључите" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -8533,12 +15876,12 @@ } }, "incomplete" : { - "extractionState" : "migrated", + "extractionState" : "manual", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Incomplete" + "value" : "Unvollständig" } }, "en" : { @@ -8571,6 +15914,12 @@ "value" : "Incomplete" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Недовршен" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -8585,11 +15934,72 @@ } } }, + "india" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "India" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Индија" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "印度" + } + } + } + }, "Indoor Air Quality" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Квалитет ваздуха у затвореном простору" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "室内空气质量" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "室內空氣品質" + } + } + } }, "Indoor Air Quality (IAQ)" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Квалитет ваздуха у затвореном простору (IAQ)" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "室内空气质量 (IAQ)" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "室內空氣品質 (IAQ)" + } + } + } }, "inputevent.back" : { "extractionState" : "migrated", @@ -8636,6 +16046,12 @@ "value" : "Bakåt" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Назад" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -8695,6 +16111,12 @@ "value" : "Avbryt" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Откажи" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -8754,6 +16176,12 @@ "value" : "Ner" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Доле" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -8763,7 +16191,7 @@ "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "下" + "value" : "我是專業的翻譯助手,我可以幫你將英文內容翻譯成中文(繁體)- Taiwanese。您想進行翻譯嗎?" } } } @@ -8813,6 +16241,12 @@ "value" : "Vänster" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Лево" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -8872,6 +16306,12 @@ "value" : "Ingen" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ништа" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -8931,6 +16371,12 @@ "value" : "Höger" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Десно" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -8990,6 +16436,12 @@ "value" : "Välj" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Изабери" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -9049,6 +16501,12 @@ "value" : "Upp" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Горе" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -9064,7 +16522,26 @@ } }, "Inputs" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Улази" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "输入" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "輸入" + } + } + } }, "interval.eighteen.hours" : { "extractionState" : "migrated", @@ -9072,7 +16549,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Eighteen Hours" + "value" : "Achtzehn Stunden" } }, "en" : { @@ -9111,6 +16588,12 @@ "value" : "Arton Timmar" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Осамнаест сати" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -9128,6 +16611,12 @@ "interval.eventytwo.hours" : { "extractionState" : "manual", "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Двадесет и два сата" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -9137,7 +16626,7 @@ "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "七十二小時" + "value" : "間隔 24 小時" } } } @@ -9148,7 +16637,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Fünfzehn Minutes" + "value" : "Fünfzehn Minuten" } }, "en" : { @@ -9187,6 +16676,12 @@ "value" : "Femton Minuter" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Петнаест минута" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -9246,6 +16741,12 @@ "value" : "Femton Sekunder" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Петнаест секунди" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -9266,7 +16767,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Five Hours" + "value" : "Fünf Stunden" } }, "en" : { @@ -9305,6 +16806,12 @@ "value" : "Fem Timmar" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Пет сати" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -9325,7 +16832,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Fünf Minutes" + "value" : "Fünf Minuten" } }, "en" : { @@ -9364,6 +16871,12 @@ "value" : "Fem Minuter" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Пет минута" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -9423,6 +16936,12 @@ "value" : "Fem Sekunder" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Пет секунди" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -9443,7 +16962,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Forty Eight Hours Hours" + "value" : "Achtundvierzig Stunden" } }, "en" : { @@ -9481,6 +17000,24 @@ "state" : "translated", "value" : "Fyrtioåtta Timmar" } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Четртесет и осам сати" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "四十八小时" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "四十八小時" + } } } }, @@ -9490,7 +17027,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Forty Five Seconds" + "value" : "Fündundvierzig Sekunden" } }, "en" : { @@ -9529,6 +17066,12 @@ "value" : "Fyrtiofem Sekunder" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Четрдесет и пет секунди" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -9549,7 +17092,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Four Hours" + "value" : "Vier Stunden" } }, "en" : { @@ -9588,6 +17131,12 @@ "value" : "Fyra Timmar" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Четири сата" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -9608,7 +17157,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Four Seconds" + "value" : "Vier Sekunden" } }, "en" : { @@ -9647,6 +17196,12 @@ "value" : "Fyra Sekunder" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Четири секунде" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -9706,6 +17261,12 @@ "value" : "En Timme" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Један сат" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -9765,6 +17326,12 @@ "value" : "En Minut" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Један минут" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -9824,6 +17391,12 @@ "value" : "En Sekund" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Један секунд" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -9844,7 +17417,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Seventy Two Hours" + "value" : "Zweiundsiebzig Stunden" } }, "en" : { @@ -9882,6 +17455,24 @@ "state" : "translated", "value" : "Sjuttiotvå Timmar" } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Седамдесет и два сата" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "七十二小时" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "七十二小時" + } } } }, @@ -9930,6 +17521,12 @@ "value" : "Sex Timmar" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Шест сати" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -9950,7 +17547,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Zehn Minutes" + "value" : "Zehn Minuten" } }, "en" : { @@ -9989,6 +17586,12 @@ "value" : "Tio Minuter" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Десет минута" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -10048,6 +17651,12 @@ "value" : "Tio Sekunder" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Десет секунди" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -10068,7 +17677,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Dreißig Minutes" + "value" : "Dreißig Minuten" } }, "en" : { @@ -10107,6 +17716,12 @@ "value" : "Trettio Minuter" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Пола сата" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -10166,6 +17781,12 @@ "value" : "Trettio Sekunder" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Тридесет секунди" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -10186,7 +17807,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Thirty Six Hours" + "value" : "Sechsunddreissig Stunden" } }, "en" : { @@ -10225,6 +17846,12 @@ "value" : "Trettiosex Timmar" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Тридесет и шест сати" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -10245,7 +17872,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Three Hours" + "value" : "Drei Stunden" } }, "en" : { @@ -10284,6 +17911,12 @@ "value" : "Tre Timmar" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Три сата" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -10304,7 +17937,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Three Seconds" + "value" : "Drei Sekunden" } }, "en" : { @@ -10343,6 +17976,12 @@ "value" : "Tre Sekunder" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Три секунде" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -10402,6 +18041,12 @@ "value" : "Tolv Timmar" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Дванаест сати" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -10461,6 +18106,12 @@ "value" : "Tjugo Sekunder" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Двадесет секунди" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -10520,6 +18171,12 @@ "value" : "Tjugofem Sekunder" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Двадесет пет секунди" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -10534,6 +18191,52 @@ } } }, + "Channel" : { + + }, + "Channel 0 Included" : { + + }, + "Channel 1 Included" : { + + }, + "Channel 2 Included" : { + + }, + "Channel 3 Included" : { + + }, + "Channel 4 Included" : { + + }, + "Channel 5 Included" : { + + }, + "Channel 6 Included" : { + + }, + "Channel 7 Included" : { + + }, + "channel details" : { + + }, + "Channel Name" : { + + }, + "Channel number must be between 0 and 7." : { + + }, + "Channel Role" : { + + }, + "Channel URL" : { + + }, + "Channel Utilization %@%% " : { + + }, + "channel.role.disabled" : { "interval.twentyfour.hours" : { "extractionState" : "migrated", "localizations" : { @@ -10579,6 +18282,12 @@ "value" : "Tjugofyra Timmar" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Двадесет четири сата" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -10599,7 +18308,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Two Hours" + "value" : "Zwei Stunden" } }, "en" : { @@ -10638,6 +18347,12 @@ "value" : "Två Timmar" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Два сата" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -10697,6 +18412,12 @@ "value" : "Två Minuter" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Два минута" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -10756,6 +18477,12 @@ "value" : "Två Sekunder" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Две секунде" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -10770,37 +18497,167 @@ } } }, - "interval.tyeight.hours" : { + "inverted.top.bar.for.2.color.display" : { "extractionState" : "manual", "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Inverted top bar for 2 Color display" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Обрнута горња трака за екран у 2 боје" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "四十八小时小时" + "value" : "倒置顶栏,用于双色显示" } }, "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "四十八小时小時" + "value" : "二色顯示器倒置頂部工具列" + } + } + } + }, + "japan" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Japan" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Јапан" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "日本" } } } }, "JSON Enabled" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "JSON омогућен" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "启用 JSON" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "JSON 已啟用" + } + } + } }, "JSON mode is a limited, unencrypted MQTT output for locally integrating with home assistant" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "JSON режим је ограничен, нешифрован MQTT излаз за локалну интеграцију са Home Assistant-ом." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "JSON 模式是一种有限的、未加密的 MQTT 输出,用于与家庭助理进行本地集成" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "JSON 模式是一種有限且未加密的 MQTT 輸出,用於本地與家庭助理整合。" + } + } + } }, "Key" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Schlüssel" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Кључ" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "Key" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "金鑰" + } + } + } }, "Key Mapping" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Мапирање кључева" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "按鍵對應" + } + } + } }, "Key Size" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Schlüsselgröße" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Величина кључа" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "金鑰大小" + } + } + } }, "keyboard.type" : { "extractionState" : "manual", @@ -10847,6 +18704,12 @@ "value" : "Tangentbordstyp" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Тип тастатуре" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -10861,51 +18724,345 @@ } } }, + "korea" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Korea" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Кореја" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "韓國" + } + } + } + }, "Last heard" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zuletzt gehört" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Последње откривање" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "最后听到" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "上次聽到" + } + } + } }, "Latitude" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Breitengrad" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ширина" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "纬度" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "緯度" + } + } + } }, "LED Heartbeat" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "LED срчани откуцаји" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "LED 心跳" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "LED 心跳" + } + } + } }, "LED State" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "LED статус" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "LED 状态" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "LED 狀態" + } + } + } }, "Legacy Administration" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Alte Administrationsart" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Стари начин администрације" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "傳統管理" + } + } + } }, "Licensed Operator" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Лиценцирани оператор" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "持证操作员" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "授權運營商" + } + } + } }, "Limit all periodic broadcast intervals especially telemetry and position. If you need to increase hops, do it on nodes at the edges, not the ones in the middle. MQTT is not advised when you are duty cycle restricted because the gateway node is then doing all the work." : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ограничите све периодичне интервале емитовања, посебно телеметрију и позицију. Ако је потребно повећати број скокова, то радите на чворовима на ивицама, а не на оним у средини. MQTT није препоручен када сте ограничени циклусом дужности јер у том случају чвор-рутер ради сав посао." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "限制所有周期性广播间隔,尤其是遥测和位置。如果需要增加跳数,请在边缘节点而不是中间节点上进行。在占空比受限的情况下,不建议使用 MQTT,因为网关节点会承担所有工作。" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "限制所有週期性廣播間隔,特別是遙測和位置資訊。如果您需要增加跳數,請在邊緣節點上進行,而不是中間節點。當您受到工作週期限制時,不建議使用 MQTT,因為此時網關節點將承担所有工作量。" + } + } + } }, "Line Series" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Линијска серија" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "線條系列" + } + } + } }, "Loading Logs. . ." : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Учитавам логове. . ." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "加载日志. . ." + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "載入日誌..." + } + } + } }, "Location" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Standort" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Локација:" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "位置" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "位置" + } + } + } }, "Location:" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Standrot:" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Локација:" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "位置:" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "位置:" + } + } + } }, "Locked" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gesperrt" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Закључан" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "锁定" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "鎖定" + } + } + } }, "Log Levels" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Нивои логова" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "日志等级" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "日誌層級" + } + } + } }, "log.category" : { "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Category" + "value" : "Kategorie" } }, "en" : { @@ -10938,6 +19095,12 @@ "value" : "Category" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Категорија" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -10990,6 +19153,12 @@ "value" : "Level" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ниво" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -11009,7 +19178,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Message" + "value" : "Nachricht" } }, "en" : { @@ -11042,6 +19211,12 @@ "value" : "Message" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Порука" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -11062,7 +19237,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Process" + "value" : "Prozess" } }, "en" : { @@ -11095,6 +19270,12 @@ "value" : "Process" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Процес" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -11148,6 +19329,12 @@ "value" : "Subsystem" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Подсистем" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -11167,7 +19354,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Time" + "value" : "Zeit" } }, "en" : { @@ -11200,6 +19387,12 @@ "value" : "Time" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Време" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -11258,6 +19451,12 @@ "value" : "Loggning" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Логовање" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -11273,22 +19472,223 @@ } }, "Logs" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Логови" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "日志" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "日誌" + } + } + } }, "Logs:" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Логови:" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "日志:" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "日誌:" + } + } + } }, "Long Name" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Langer Name" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Дуго име" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "长名称" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "完整名稱" + } + } + } }, "Long Name: %@" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Langer Name: %@" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Дуго име: %@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "长名称: %@" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "完整名稱: %@" + } + } + } }, "Long press to favorite or mute the contact or delete a conversation." : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Дугим притиском на чвор из листе означите као омиљени или искључите звук контакта или обришите разговор." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "长按可收藏联系人或将其静音或删除对话。" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "長按可將聯繫人設為最愛或靜音,或刪除對話。" + } + } + } + }, + "long.range.fast" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Long Range - Fast" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Дугачки домет - Брзо" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "長距離 - 快速" + } + } + } + }, + "long.range.moderate" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Long Range - Moderate" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Дугачки домет - Умерено" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "長距離 - 中速" + } + } + } + }, + "long.range.slow" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Long Range - Slow" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Дугачки домет - Споро" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "長距離 - 慢速" + } + } + } }, "Longitude" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Längengrad" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Дужина" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "经度" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "經度" + } + } + } }, "lora" : { "localizations" : { @@ -11334,6 +19734,12 @@ "value" : "LoRa" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "LoRA" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -11392,6 +19798,12 @@ "value" : "LoRa Konfiguration" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "LoRA подешавања" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -11406,11 +19818,205 @@ } } }, + "lora.signal.strength.bad" : { + "extractionState" : "manual", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Schlechte Signalstärke" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bad" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Лош" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "不良" + } + } + } + }, + "lora.signal.strength.fair" : { + "extractionState" : "manual", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ordentliche Signalstärke" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Fair" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Прихватљив" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "公平" + } + } + } + }, + "lora.signal.strength.good" : { + "extractionState" : "manual", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gute Signalstärke" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Good" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Добар" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "很好" + } + } + } + }, + "lora.signal.strength.none" : { + "extractionState" : "manual", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Keine Verbindung" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "None" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Без" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "無" + } + } + } + }, "LOW" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "НИЗАК" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "低" + } + } + } + }, + "malaysia.433mhz" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Malaysia 433MHz" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Малезија 433MHz" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "馬來西亞 433MHz" + } + } + } + }, + "malaysia.919mhz" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Malaysia 919MHz" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Малезија 919MHz" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "馬來西亞 919MHz" + } + } + } }, "Managed Device" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Управљани уређај" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "管理设备" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "受控裝置" + } + } + } }, "map" : { "localizations" : { @@ -11456,6 +20062,12 @@ "value" : "Mesh Karta" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Мапа меша" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -11471,26 +20083,102 @@ } }, "Map Options" : { - - }, - "Map Publish Interval" : { - - }, - "Map Report" : { - - }, - "Map Tile Data" : { - - }, - "map.centering" : { - "extractionState" : "manual", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Centering" + "value" : "Kartenoptionen" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Опције мапе" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "地图选项" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "地圖選項" + } + } + } + }, + "Map Publish Interval" : { + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Интервал објављивања мапе" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "地图发布间隔" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "地圖發布間隔" + } + } + } + }, + "Map Report" : { + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Извештај мапе" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "地图报告" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "地圖報告" + } + } + } + }, + "Map Tile Data" : { + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Подаци плочица мапе" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "瓦片地图数据" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "地圖圖塊資料" + } + } + } + }, + "map.centering" : { + "extractionState" : "manual", + "localizations" : { "en" : { "stringUnit" : { "state" : "translated", @@ -11527,6 +20215,12 @@ "value" : "Centreringsläge" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Режим центрирања" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -11541,74 +20235,9 @@ } } }, - "map.recentering" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Automatic Re-centering" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Automatic Re-centering" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Recentrage automatique" - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "מרכז מפה אוטומטית" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Automatyczne Wyśrodkowywanie" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Re-centralização Automática" - } - }, - "se" : { - "stringUnit" : { - "state" : "translated", - "value" : "Automatisk Centrering" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "自动重新居中" - } - }, - "zh-Hant-TW" : { - "stringUnit" : { - "state" : "translated", - "value" : "自動重新居中" - } - } - } - }, "map.tiles.delete" : { "extractionState" : "migrated", "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Delete Cached Map Tiles" - } - }, "en" : { "stringUnit" : { "state" : "translated", @@ -11645,10 +20274,16 @@ "value" : "Radera Alla Kartplattor" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Обриши све плочице мапе" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "删除已缓存的地图区块" + "value" : "删除所有瓦片地图" } }, "zh-Hant-TW" : { @@ -11665,7 +20300,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "kartentyp" + "value" : "Kartentyp" } }, "en" : { @@ -11704,6 +20339,12 @@ "value" : "Standardtyp" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Подразумевани тип" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -11763,6 +20404,12 @@ "value" : "Använd Äldre Mesh Karta" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Користите легаси мрежну мапу" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -11822,6 +20469,12 @@ "value" : "Spårningsläge för användare" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Мод праћења корисника" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -11842,7 +20495,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Follow" + "value" : "Folgen" } }, "en" : { @@ -11881,6 +20534,12 @@ "value" : "Följ" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Прати" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -11901,7 +20560,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Follow with heading" + "value" : "Folgen mit Steuerkurs" } }, "en" : { @@ -11940,6 +20599,12 @@ "value" : "Följ med riktning" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Прати са правцем" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -11960,7 +20625,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "None" + "value" : "Keiner" } }, "en" : { @@ -11999,6 +20664,12 @@ "value" : "Ingen" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ни један" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -12013,15 +20684,74 @@ } } }, + "medium.range.fast" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Medium Range - Fast" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Средњи домет - Брзо" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "中距離 - 快速" + } + } + } + }, + "medium.range.slow" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Medium Range - Slow" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Средњи домет - Споро" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "中距離 - 慢速" + } + } + } + }, "Mesh activity update" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ажурирање активности мреже" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "網狀活動更新" + } + } + } }, "mesh.live.activity" : { "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Mesh Live Activity" + "value" : "Mesh Live Aktivität" } }, "en" : { @@ -12060,6 +20790,12 @@ "value" : "Mesh Live Aktivitet" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Активности мреже уживо" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -12118,6 +20854,12 @@ "value" : "Mesh-logg" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Логови мреже" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -12135,12 +20877,6 @@ "mesh.log.ambientlighting.config %@" : { "extractionState" : "migrated", "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ambient Lighting module config received: %@" - } - }, "en" : { "stringUnit" : { "state" : "translated", @@ -12177,6 +20913,12 @@ "value" : "Konfiguration för omgivningsbelysningsmodulen mottagen: %@" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Примљена конфигурација модула амбијенталног осветљења: %@" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -12236,6 +20978,12 @@ "value" : "Bluetooth-konfiguration mottagen: %@" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Примљена конфигурација блутута: %@" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -12253,12 +21001,6 @@ "mesh.log.cannedmessage.config %@" : { "extractionState" : "migrated", "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Canned Message module config received: %@" - } - }, "en" : { "stringUnit" : { "state" : "translated", @@ -12295,6 +21037,12 @@ "value" : "Konfiguration för modulen med fördefinierade meddelanden mottagen: %@" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Конфигурација модула за унапред припремљене поруке примљена: %@" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -12312,12 +21060,6 @@ "mesh.log.cannedmessages.messages.get %@" : { "extractionState" : "migrated", "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Requested Canned Messages Module Messages for node: %@" - } - }, "en" : { "stringUnit" : { "state" : "translated", @@ -12354,6 +21096,12 @@ "value" : "Begärda meddelanden för modulen med fördefinierade meddelanden för nod: %@" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Захтеване поруке модула за унапред припремљене поруке за чвор: %@" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -12371,12 +21119,6 @@ "mesh.log.cannedmessages.messages.received %@" : { "extractionState" : "migrated", "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Canned Messages Messages Received For: %@" - } - }, "en" : { "stringUnit" : { "state" : "translated", @@ -12413,6 +21155,12 @@ "value" : "Mottagna meddelanden för fördefinierade meddelanden För: %@" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Примљене поруке за унапред припремљене поруке за: %@" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -12427,74 +21175,9 @@ } } }, - "mesh.log.channel.received %d %@" : { - "extractionState" : "migrated", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Channel %d received from: %@" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Channel %d received from: %@" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Canal %d reçu de : %@" - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "ערוץ %d התקבל מ-%@" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Odebrano kanał %d od: %@" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Canal %d recebido de: %@" - } - }, - "se" : { - "stringUnit" : { - "state" : "translated", - "value" : "Kanal %d mottagen från: %@" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "Channel %d received from: %@" - } - }, - "zh-Hant-TW" : { - "stringUnit" : { - "state" : "translated", - "value" : "Channel %d received from: %@" - } - } - } - }, "mesh.log.channel.sent %@ %d" : { "extractionState" : "migrated", "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Sent a Channel for: %@ Channel Index %d" - } - }, "en" : { "stringUnit" : { "state" : "translated", @@ -12531,6 +21214,12 @@ "value" : "Skickade en kanal för: %@ Kanalindex %d" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Послат је канал за: %@ Индекс канала %d" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -12548,12 +21237,6 @@ "mesh.log.detectionsensor.config %@" : { "extractionState" : "migrated", "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Detection Sensor module config received: %@" - } - }, "en" : { "stringUnit" : { "state" : "translated", @@ -12590,6 +21273,12 @@ "value" : "Konfiguration för detektionssensormodulen mottagen: %@" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Конфигурација модула за сензор детекције примљена: %@" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -12599,7 +21288,7 @@ "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "收到偵測感應器模組配置:%@" + "value" : "偵測感應器模組設定已接收:%@" } } } @@ -12610,7 +21299,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Geräte Konfiguration empfangen: %@" + "value" : "Gerätekonfiguration empfangen: %@" } }, "en" : { @@ -12649,6 +21338,12 @@ "value" : "Enhetskonfiguration mottagen: %@" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Примљена конфигурација уређаја: %@" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -12669,7 +21364,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Device Metadata received from: %@" + "value" : "Device Metadata empfangen von: %@" } }, "en" : { @@ -12708,6 +21403,12 @@ "value" : "Metadata för enhet mottagen från: %@" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Метаподаци уређаја примљени од: %@" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -12717,7 +21418,7 @@ "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "從 %@ 收到設備元數據管理消息" + "value" : "裝置元數據來自:%@" } } } @@ -12767,6 +21468,12 @@ "value" : "Begär metadata för enhet för %@" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Захтевање метаподатака уређаја за %@" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -12826,6 +21533,12 @@ "value" : "Skärmkonfiguration mottagen: %@" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Примљена конфигурација приказа: %@" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -12843,12 +21556,6 @@ "mesh.log.externalnotification.config %@" : { "extractionState" : "migrated", "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "External Notification module config received: %@" - } - }, "en" : { "stringUnit" : { "state" : "translated", @@ -12885,6 +21592,12 @@ "value" : "Konfiguration för modulen för externa notifikationer mottagen: %@" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Конфигурација модула за екстерне нотификације примљена: %@" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -12894,7 +21607,7 @@ "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "收到外部通知模組配置:%@" + "value" : "外部通知模組設定已接收:%@" } } } @@ -12905,7 +21618,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "LoRa config received: %@" + "value" : "LoRa config empfangen: %@" } }, "en" : { @@ -12944,6 +21657,12 @@ "value" : "LoRa-konfiguration mottagen: %@" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Конфигурација LoRA примљена: %@" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -12964,7 +21683,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Sent a LoRa.Config for: %@" + "value" : "LoRa.Config gesendet für: %@" } }, "en" : { @@ -13003,6 +21722,12 @@ "value" : "Skickade en LoRa.Konfiguration för: %@" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Послата LoRA конфигурација за: %@" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -13012,7 +21737,7 @@ "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "發送LoRa配置給:%@" + "value" : "已為 %@ 傳送 LoRa.Config" } } } @@ -13023,7 +21748,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "MQTT module config received: %@" + "value" : "MQTT Modulkonfiguration empfangen: %@" } }, "en" : { @@ -13062,16 +21787,22 @@ "value" : "MQTT-modulkonfiguration mottagen: %@" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Конфигурација MQTT модула примљена: %@" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "MQTT module config received: %@" + "value" : "收到 MQTT 模块配置:%@" } }, "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "收到MQTT模組配置:%@" + "value" : "MQTT 模組設定已接收:%@" } } } @@ -13082,7 +21813,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "MyInfo received: %@" + "value" : "MyInfo empfangen: %@" } }, "en" : { @@ -13121,6 +21852,12 @@ "value" : "Min info mottagen: %@" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Моје информације примљене: %@" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -13130,7 +21867,7 @@ "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "收到我的資訊:%@" + "value" : "我的資訊已接收:%@" } } } @@ -13141,7 +21878,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Netzwerk onfiguration empfangen: %@" + "value" : "Netzwerkkonfiguration empfangen: %@" } }, "en" : { @@ -13180,6 +21917,12 @@ "value" : "Nätverkskonfiguration mottagen: %@" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Конфигурација мреже примљена: %@" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -13200,7 +21943,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Node info empfangen für: %@" + "value" : "Knoteninformation empfangen für: %@" } }, "en" : { @@ -13239,6 +21982,12 @@ "value" : "Nodinformation mottagen för: %@" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Информације о чвору примљене за: %@" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -13292,6 +22041,12 @@ "value" : "PAX-räknarmeddelande mottaget från: %@" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Порука PAX бројача примљена од: %@" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -13301,7 +22056,7 @@ "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "為 %@ 收到PAX計數器消息" + "value" : "PAX 計數器訊息來自:%@" } } } @@ -13326,6 +22081,18 @@ "state" : "translated", "value" : "PAX-räknarkonfiguration mottagen: %@" } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Конфигурација PAX бројача примљена: %@" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "PAX 計數器設定已接收:%@" + } } } }, @@ -13335,7 +22102,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Positions Konfiguration empfangen: %@" + "value" : "Positionskonfiguration empfangen: %@" } }, "en" : { @@ -13374,6 +22141,12 @@ "value" : "Positionskonfiguration mottagen: %@" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Конфигурација позиције примљена: %@" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -13383,7 +22156,7 @@ "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "收到位置配置:%@" + "value" : "位置設定已接收:%@" } } } @@ -13394,7 +22167,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Positionspaket empfangen von Node: %@" + "value" : "Position empfangen von Knoten: %@" } }, "en" : { @@ -13433,6 +22206,12 @@ "value" : "Positionspaket mottaget från nod: %@" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Пакет позиције примљен од чвора: %@" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -13467,6 +22246,18 @@ "state" : "translated", "value" : "Strömkonfiguration mottagen: %@" } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Конфигурација напајања примљена: %@" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "電源設定已接收:%@" + } } } }, @@ -13515,10 +22306,16 @@ "value" : "Konfiguration för räckviddstestmodulen mottagen: %@" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Конфигурација модула теста домета примљена: %@" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "Range Test module config received: %@" + "value" : "收到范围测试模块配置: %@" } }, "zh-Hant-TW" : { @@ -13535,7 +22332,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "RTTTL Ringtone config received: %@" + "value" : "RTTTL Klingeltonkonfiguration empfangen: %@" } }, "en" : { @@ -13574,6 +22371,12 @@ "value" : "Konfiguration för RTTTL-ringsignal mottagen: %@" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Конфигурација RTTTL мелодије примљена: %@" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -13583,7 +22386,7 @@ "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "收到RTTTL鈴聲配置:%@" + "value" : "RTTTL鈴聲設定已接收:%@" } } } @@ -13633,6 +22436,12 @@ "value" : "Routing mottagen för RequestID: %@ Ack Status: %@" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Рутирање примљено за ИД захтева: %@ Статус потврде: %@" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -13692,6 +22501,12 @@ "value" : "Seriekonfigurationsmodul mottagen: %@" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Конфигурација серијског модула примљена: %@" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -13701,7 +22516,7 @@ "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "收到串列模組配置:%@" + "value" : "序列通訊模組設定已接收:%@" } } } @@ -13712,7 +22527,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Sent a Position Packet from the Apple device GPS to node: %@" + "value" : "Position von Apple Gerät an Knoten gesendet: %@" } }, "en" : { @@ -13751,6 +22566,12 @@ "value" : "Skickade ett positionspaket från Apple-enhetens GPS till nod: %@" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Позициони пакет послат са Епл уређаја на чвор: %@" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -13768,12 +22589,6 @@ "mesh.log.storeforward.config %@" : { "extractionState" : "migrated", "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Store & Forward module config received: %@" - } - }, "en" : { "stringUnit" : { "state" : "translated", @@ -13810,6 +22625,12 @@ "value" : "Konfiguration för Store & Forward-modulen mottagen: %@" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Конфигурација модула за чување и прослеђивање примљена: %@" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -13819,7 +22640,7 @@ "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "收到儲存與轉發模組配置:%@" + "value" : "儲存與轉發模組設定已接收:%@" } } } @@ -13869,6 +22690,12 @@ "value" : "Telemetrimodulkonfiguration mottagen: %@" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Конфигурација модула телеметрије примљена: %@" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -13878,7 +22705,7 @@ "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "收到遠測模組設定: %@" + "value" : ",我們可以一起工作!您想要我幫你翻譯什麼內容?請給我提供英文原文和您的期望翻譯結果,我將盡力幫助您。" } } } @@ -13928,6 +22755,12 @@ "value" : "Telemetri mottagen för: %@" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Телеметрија примљена за: %@" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -13987,6 +22820,12 @@ "value" : "Meddelande mottaget från textmeddelandeappen." } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Порука примљена из апликације за текстуалне поруке." + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -13996,7 +22835,7 @@ "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "從文字消息應用程序收到消息。" + "value" : "從簡訊應用程式接收訊息。" } } } @@ -14046,6 +22885,12 @@ "value" : "Misslyckades med att skicka meddelande, inte korrekt ansluten till %@" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Слање поруке није успело, није правилно повезано са: %@" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -14105,6 +22950,12 @@ "value" : "Skickade meddelande %@ från %@ till %@" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Порука послата %@ са %@ на %@" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -14120,12 +22971,12 @@ } }, "mesh.log.traceroute.received.direct %@" : { - "extractionState" : "migrated", + "extractionState" : "manual", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Traceroute Anforderung an node gesendet: %@ wurde direkt empfangen." + "value" : "Traceroute Anforderung an Knoten gesendet: %@ wurde direkt empfangen." } }, "en" : { @@ -14164,6 +23015,12 @@ "value" : "Spårruttförfrågan skickad till nod: %@ mottogs direkt." } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Захтев за тражење путања послат на чвор: %@ је примљен директно." + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -14173,7 +23030,7 @@ "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "直接收到發送至節點的追蹤路由請求:%@" + "value" : "已將追蹤路線請求傳送至節點:%@,並直接收到回應。" } } } @@ -14223,6 +23080,12 @@ "value" : "Spårruttförfrågan returnerade: %@" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Захтев за тражење путања враћен: %@" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -14232,7 +23095,7 @@ "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "返回的追蹤路由請求:%@" + "value" : "追蹤路線請求已返回:%@" } } } @@ -14243,7 +23106,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Sende Traceroute Anforderung zu Mode: %@" + "value" : "Sende Traceroute Anforderung zu Knoten: %@" } }, "en" : { @@ -14282,6 +23145,12 @@ "value" : "Skickade en spårruttförfrågan till nod: %@" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Захтев за тражење путања послат на чвор: %@" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -14291,7 +23160,7 @@ "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "發送追蹤路由請求至節點:%@" + "value" : "已將追蹤路線請求傳送至節點:%@" } } } @@ -14299,12 +23168,6 @@ "mesh.log.wantconfig %@" : { "extractionState" : "migrated", "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Issuing Want Config to %@" - } - }, "en" : { "stringUnit" : { "state" : "translated", @@ -14341,6 +23204,12 @@ "value" : "Utfärdar Want Config till %@" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Издавање захтева за конфигурацију на: %@" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -14361,7 +23230,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Waypoint Packet received from node: %@" + "value" : "Wegpunkt von Knoten empfangen: %@" } }, "en" : { @@ -14400,6 +23269,12 @@ "value" : "Vägpunktspaket mottaget från nod: %@" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Пакет са тачкама пута примљен од чвора: %@" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -14409,7 +23284,7 @@ "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "從節點收到航點封包:%@" + "value" : "航點封包來自節點:%@" } } } @@ -14420,7 +23295,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Sent a Waypoint Packet from: %@" + "value" : "Wegpunkt gesendet von: %@" } }, "en" : { @@ -14459,6 +23334,12 @@ "value" : "Skickade en vägpunktspaket från: %@" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Пакет са тачкама пута послат од: %@" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -14468,16 +23349,48 @@ "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "從 %@ 發送航點封包" + "value" : "已從節點:%@ 傳送航點封包" } } } }, "Meshtastic Node %@ has shared channels with you" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Meshtastic Knoten %@ hat Kanäle mit dir geteilt" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Meshtastic чвор %@ је поделио канале са вама." + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "Meshtastic 節點 %@ 與您共享通道。" + } + } + } }, "Meshtastic® Copyright Meshtastic LLC" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Meshtastic® Ауторска права Meshtastic LLC" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "Meshtastic® 版權所有 Meshtastic LLC" + } + } + } }, "message" : { "localizations" : { @@ -14523,6 +23436,12 @@ "value" : "Meddelande" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Порука" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -14538,13 +23457,64 @@ } }, "Message" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nachricht" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Порука" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "訊息" + } + } + } }, "Message content exceeds 200 bytes." : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nachrichteninhalt überschreitet 200 Bytes." + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Садржај поруке премашује 200 бајтова." + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "訊息內容超過 200 個位元組。" + } + } + } }, "Message Status Options" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Опције статуса поруке" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "訊息狀態選項" + } + } + } }, "message.details" : { "localizations" : { @@ -14590,6 +23560,12 @@ "value" : "Meddelandedetaljer" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Детаљи поруке" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -14648,6 +23624,12 @@ "value" : "Meddelanden" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Поруке" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -14663,22 +23645,140 @@ } }, "Messages" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nachrichten" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Поруке" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "訊息" + } + } + } }, "Messages separate with |" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nachrichten getrennt mit |" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Поруке се раздвајају са |" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "訊息以 | 分隔" + } + } + } + }, + "Metric" : { + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Метрика" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "指標" + } + } + } }, "Minimum Distance" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Minimum Distanz" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Минимум раздаљине" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "最小距離" + } + } + } }, "Minimum Interval" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Minimum Intervall" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Минимални интервал" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "最小間隔" + } + } + } }, "Minimum time between detection broadcasts" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Минимално време између емитовања детекције" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "偵測廣播之間的最小間隔" + } + } + } }, "Mininum time between detection broadcasts. Default is 45 seconds." : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Минимално време између емитовања детекције. Подразумевано је 45 секунди." + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "偵測廣播之間的最小間隔。預設為 45 秒。" + } + } + } }, "mode" : { "localizations" : { @@ -14724,6 +23824,12 @@ "value" : "Läge" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Мод" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -14738,6 +23844,28 @@ } } }, + "Model" : { + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Модел" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "模型" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "模型" + } + } + } + }, "module.configuration" : { "localizations" : { "de" : { @@ -14782,6 +23910,12 @@ "value" : "Modulkonfiguration" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Конфигурација модула" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -14840,6 +23974,12 @@ "value" : "MQTT" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "MQTT" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -14855,7 +23995,26 @@ } }, "MQTT" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "MQTT" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "MQTT" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "MQTT" + } + } + } }, "mqtt.clientproxy" : { "localizations" : { @@ -14901,6 +24060,12 @@ "value" : "MQTT-klientproxy" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "MQTT посредник клијента" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -14920,7 +24085,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "MQTT Config" + "value" : "MQTT Konfiguration" } }, "en" : { @@ -14959,6 +24124,12 @@ "value" : "MQTT-konfiguration" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "MQTT подешавања" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -14979,7 +24150,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Connect to MQTT" + "value" : "Verbunden mit MQTT" } }, "en" : { @@ -15018,6 +24189,12 @@ "value" : "Anslut till MQTT" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Повежи се на MQTT" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -15038,7 +24215,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Disconnect from MQTT" + "value" : "Trennen von MQTT" } }, "en" : { @@ -15077,6 +24254,12 @@ "value" : "Koppla från MQTT" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Развежи се од MQTT" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -15135,6 +24318,12 @@ "value" : "Användarnamn" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Корисничко име" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -15150,10 +24339,36 @@ } }, "Must be a single emoji" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Мора бити један емотикон" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "必須是單個表情符號" + } + } + } }, "Nag timeout" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Период чекања је истекао" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "偵測逾時" + } + } + } }, "name" : { "localizations" : { @@ -15199,6 +24414,12 @@ "value" : "Namn" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Име" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -15214,13 +24435,76 @@ } }, "Name" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Name" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Име" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "名称" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "名稱" + } + } + } }, "Name must be less than 30 bytes" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Name muss kürzer als 30 Bytes sein" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Име мора бити краће од 30 бајтова" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "名称必须少于 30 字节" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "名稱長度必須少於 30 個位元組。" + } + } + } }, "Nearby Topics" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Теме у окружењу" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "附近主題" + } + } + } }, "network" : { "localizations" : { @@ -15266,6 +24550,12 @@ "value" : "Nätverk" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Мрежа" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -15281,10 +24571,48 @@ } }, "Network Status Orange" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Статус мреже: Наранџаст" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "网络状态 橙色" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "網路狀態橘色" + } + } + } }, "Network Status Red" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Статус мреже: Црвен" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "网络状态 红色" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "網路狀態紅色" + } + } + } }, "network.config" : { "localizations" : { @@ -15330,6 +24658,12 @@ "value" : "Nätverkskonfiguration" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Конфигурација мреже" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -15345,22 +24679,193 @@ } }, "Never" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nie" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Никада" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "永遠不" + } + } + } + }, + "New Node" : { + "extractionState" : "manual", + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Нови чвор" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "新增節點" + } + } + } + }, + "New Node has been discovered" : { + "extractionState" : "manual", + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Откривен је нови чвор" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "已發現新的節點" + } + } + } + }, + "new.zealand.865mhz" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "New Zealand 865MHz" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Нови зеланд 865MHz" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "新西蘭 865MHz" + } + } + } }, "Newer firmware is available" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Neuere Firmware ist verfügbar" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Нова верзија фирмвера је доступна" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "有固件可以更新" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "有較新的韌體可供使用" + } + } + } }, "No Connected Node" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kein verbundener Knoten" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Нема повезаног чвора" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "沒有已連接的節點" + } + } + } }, "No Device Metrics" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Нема метрика уређаја." + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "沒有裝置指標" + } + } + } }, "No Environment Metrics" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Нема метрика окружења" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "没有环境指标" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "沒有環境指標" + } + } + } }, "No Positions" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Keine Positionen" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Нема позиција" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "沒有位置" + } + } + } }, "no.nodes" : { "extractionState" : "manual", @@ -15368,7 +24873,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Keine Meshtastic Nodes gefunden" + "value" : "Keine Meshtastic Knoten gefunden" } }, "en" : { @@ -15407,6 +24912,12 @@ "value" : "Inga Meshtastic-noder hittades" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Нема пронађених Мештастик чворова" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -15422,7 +24933,26 @@ } }, "Node" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Knoten" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Чвор" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "節點" + } + } + } }, "Node Core Data Backup %@/%@ - %@ - %@" : { "localizations" : { @@ -15431,23 +24961,130 @@ "state" : "new", "value" : "Node Core Data Backup %1$@/%2$@ - %3$@ - %4$@" } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Резервна копија података језгра чвора %1$@/%2$@ - %3$@ - %4$@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "节点核心数据备份 %1$@/%2$@ - %3$@ - %4$@" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "節點核心資料備份 %1$@/%2$@ - %3$@ - %4$@" + } } } }, "Node does not have positions" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Knoten hat keine Position" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Чвор нема позиције" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "節點沒有位置" + } + } + } }, "Node History" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Knoten Historie" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Историја чвора" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "節點歷史記錄" + } + } + } }, "Node Info Broadcast Interval" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Интервал емитовања информација о чвору" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "節點資訊廣播間隔" + } + } + } }, "Node Map" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Knotenkarte" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Мапа чворова" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "節點對應圖" + } + } + } }, "Node Number" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Knotennummer" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Број чвора" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "節點編號" + } + } + } }, "nodelist.filter.distance %@" : { "extractionState" : "migrated", @@ -15455,7 +25092,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "up to %@ away" + "value" : "bis zu %@ entfernt" } }, "en" : { @@ -15494,6 +25131,12 @@ "value" : "upp till %@ bort" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "удаљено до максималних %@" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -15513,7 +25156,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Nodes" + "value" : "Knoten" } }, "en" : { @@ -15546,6 +25189,12 @@ "value" : "Noder" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Чворови" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -15566,7 +25215,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Nodes (%@)" + "value" : "Knoten (%@)" } }, "en" : { @@ -15605,6 +25254,12 @@ "value" : "Noder (%@)" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Чворови (%@)" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -15620,7 +25275,20 @@ } }, "Not a valid route file" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Није валидна датотека путања" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "無效路由檔案" + } + } + } }, "not.connected" : { "localizations" : { @@ -15666,34 +25334,135 @@ "value" : "Ingen enhet ansluten" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Нема повезаних уређаја" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "未连接到电台" + "value" : "设备未连接" } }, "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "未連接到電台" + "value" : "沒有連接的裝置" } } } }, "Notes" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Knoten" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Белешке" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "筆記" + } + } + } }, "Num: %@" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Anzahl: %@" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Број: %@" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "數量: %@" + } + } + } }, "Number of hops" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Anzahl Hops" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Број хопова" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "跳數" + } + } + } }, "Number of records" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Anzahl Einträge" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Број записа" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "記錄數" + } + } + } }, "Number of satellites" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Anzahl Satelliten" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Број сателита" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "衛星數量" + } + } + } }, "numbers.punctuation" : { "extractionState" : "manual", @@ -15740,6 +25509,12 @@ "value" : "Siffror och skiljetecken" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Бројеви и интерпункција" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -15799,6 +25574,12 @@ "value" : "Av" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Искључен" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -15858,6 +25639,12 @@ "value" : "Offline" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ван мреже" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -15873,13 +25660,76 @@ } }, "OK" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ok" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "ОК" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "好" + } + } + } }, "Ok to MQTT" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Позитиван за MQTT" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ok to MQTT" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "可以 MQTT" + } + } + } }, "OLED Type" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "OLED Typ" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Тип OLED-а" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "OLED 类型" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "OLED 類型" + } + } + } }, "on.boot" : { "extractionState" : "migrated", @@ -15926,6 +25776,12 @@ "value" : "Endast vid uppstart" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Само при покретању" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -15941,25 +25797,181 @@ } }, "Onboarding for licensed operators requires firmware 2.0.20 or greater. Make sure to refer to your local regulations and contact the local amateur frequency coordinators with questions." : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Увођење за лиценциране оператере захтева фирмвер верзије 2.0.20 или новије. Уверите се да се придржавате локалних прописа и обратите се локалним координаторима за аматерске фреквенције са питањима." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "业余无线电使用需要固件 2.0.20 或更高版本。请务必参考当地法规,并联系当地业余频率协调人员咨询相关问题。" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "取得執照的作業員登錄需要韌體版本 2.0.20 或更高版本。請務必參考當地法規,並聯繫當地業餘無線電頻率協調員以解決任何問題。" + } + } + } }, "One Hour" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Eine Stunde" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Један сат" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "一小時" + } + } + } }, "One Minute" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Eine Minute" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Једна минута" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "一分鐘" + } + } + } }, "Online" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Online" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "На мрежи" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "線上" + } + } + } }, "Open Settings" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Einstellungen öffnen" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Отвори подешавања" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "打开设置" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "開啟設定" + } + } + } + }, + "optimized.for.2.color.displays" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Optimized for 2 color displays" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Оптимизовано за двобојне дисплеје" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "優化二色顯示器使用" + } + } + } }, "Optional fields to include when assembling position messages. the more fields are included, the larger the message will be - leading to longer airtime and a higher risk of packet loss" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Опциони поља за укључивање при склапању порука о позицији. Што више поља је укључено, порука ће бити већа, што доводи до дужег времена емитовања и већег ризика од губитка пакета" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "包含的字段越多,信息就越大,导致通讯时间更长,丢包风险更高" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "組裝位置訊息時可加入的選用欄位。包含的欄位越多,訊息就越大,導致空氣中傳輸時間更長,且封包遺失風險更高。" + } + } + } }, "Optional GPIO" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Опциони GPIO" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "可選 GPIO" + } + } + } }, "options" : { "localizations" : { @@ -16005,6 +26017,12 @@ "value" : "Alternativ" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Опције" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -16020,34 +26038,194 @@ } }, "Options" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Optionen" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Опције" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "选项" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "選項" + } + } + } }, "OS Log Entry Details" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Детаљи уноса ОС дневника" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "作業系統日誌條目詳細資料" + } + } + } }, "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 设备" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "此NRF設備不支援OTA更新。" + } + } + } }, "OTA Updates are not supported on your platform." : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "ОТА ажурирања нису подржана на вашој платформи." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "OTA 更新不支持你的平台" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "您的平台不支援 OTA 更新。" + } + } + } }, "Other data sources" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Остали извори података" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "其他數據來源" + } + } + } }, "Output live debug logging over serial, view and export position-redacted device logs over Bluetooth." : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ausgabe von Echtzeit-Fehlersuchprotokollen über die serielle Schnittstelle, Anzeige und Export von positionskorrigierten Geräteprotokollen über Bluetooth." + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Излаз дебаговања уживо преко серијског интерфејса, прегледајте и извозите логове уређаја са редукованим позицијама преко блутута." + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "透過串列傳輸輸出即時除錯日誌,並透過藍牙檢視和匯出已刪除位置裝置日誌。" + } + } + } }, "Output pin buzzer GPIO " : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Излазни пин за зујалицу GPIO" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "蜂鳴器輸出引腳 GPIO" + } + } + } }, "Output pin GPIO" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Излазни пин GPIO" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "蜂鳴器輸出引腳 GPIO" + } + } + } }, "Output pin vibra GPIO" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Излазни пин за вибрацију GPIO" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "振動馬達輸出引腳 GPIO" + } + } + } }, "Override automatic OLED screen detection." : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Премаши аутоматско откривање OLED екрана." + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "覆寫自動 OLED 螢幕偵測。" + } + } + } }, "password" : { "localizations" : { @@ -16093,6 +26271,12 @@ "value" : "Lösenord" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Лозинка" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -16151,6 +26335,12 @@ "value" : "Pausa" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Паузирај" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -16184,6 +26374,18 @@ "state" : "translated", "value" : "BLE" } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "БЛЕ" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "BLE" + } } } }, @@ -16206,6 +26408,18 @@ "state" : "translated", "value" : "Inga loggar för PAX-räknare" } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Нема логова PAX бројача" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "沒有 PAX 計數器日誌" + } } } }, @@ -16228,6 +26442,18 @@ "state" : "translated", "value" : "Radera all paxdata?" } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Избриши све PAX податке?" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "刪除所有 PAX 資料?" + } } } }, @@ -16250,6 +26476,18 @@ "state" : "translated", "value" : "PAX-räknarens logg" } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Логови PAX бројача" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "PAX 計數器日誌" + } } } }, @@ -16272,6 +26510,18 @@ "state" : "translated", "value" : "Totalt PAX" } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Укупно PAX" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "總 PAX 數量" + } } } }, @@ -16294,11 +26544,111 @@ "state" : "translated", "value" : "WiFi" } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "ВајФај" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wi-Fi" + } } } }, "Perform a factory reset on the node you are connected to" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Verbundenen Knoten auf Werkseinstellungen zurücksetzen" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Изврши фабричко ресетовање чвора на који сте повезани" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "對您連線的節點執行出廠重設。" + } + } + } + }, + "philippines.433mhz" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Philippines 433MHz" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Филипини 433MHz" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "菲律賓 433MHz" + } + } + } + }, + "philippines.868mhz" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Philippines 868MHz" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Филипини 868MHz" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "菲律賓 868MHz" + } + } + } + }, + "philippines.915mhz" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Philippines 915MHz" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Филипини 915MHz" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "菲律賓 915MHz" + } + } + } }, "phone.gps" : { "localizations" : { @@ -16344,6 +26694,12 @@ "value" : "Telefon-GPS" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "GPS телефона" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -16403,37 +26759,162 @@ "value" : "Hur ofta din telefon skickar din plats till enheten, platsuppdateringar till mesh-nätverket hanteras av enheten." } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Колико често ваш телефон шаље вашу локацију уређају, ажурирања локације на мрежу се управљају од стране уређаја." + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "电台通过手机获取定位的时间间隔,但是向 Mesh 网络中刷新定位的时间间隔由电台控制。" + "value" : "电台通过手机获取定位的时间间隔,但是向 Mesh 网络中发送定位的时间间隔由电台控制。" } }, "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "電台通過手機獲得定位的時間間隔,但是向 Mesh 網路中更新定位的時間間隔由電台控制。" + "value" : "您的手機將多頻繁地向裝置傳送位置資訊,裝置會管理位置更新到網格的頻率。" } } } }, "Pin %lld" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Пин %lld" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "鎖定 %lld" + } + } + } }, "Pin A" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Пин А" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "鎖定 A" + } + } + } }, "Pin B" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Пин Б" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "鎖定 B" + } + } + } }, "PKI based node administration, requires firmware version 2.5+" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "PKI-basierte Knotenadministration, benötigt Firmware Version 2.5+" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Администрација чвора заснована на PKI захтева фирмвер верзију 2.5 или новију" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "基于 PKI 的节点管理,需要 2.5 以上版本的固件" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "基於 PKI 的節點管理,需要韌體版本 2.5 或以上。" + } + } + } }, "Please connect to a radio to configure settings." : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Молимо вас да се повежете на радио да бисте конфигурисали подешавања." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "请连接电台以修改配置。" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "請連接到無線電以設定參數。" + } + } + } + }, + "please.set.a.region" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Please set a region" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Молимо изаберите регион" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "請設定區域" + } + } + } }, "Points of Interest" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Тачке интересовања" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "興趣點" + } + } + } }, "position" : { "localizations" : { @@ -16479,6 +26960,12 @@ "value" : "Position" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Позиција" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -16494,25 +26981,122 @@ } }, "Position Exchange Failed" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Неуспела размена позиција" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "位置交換失敗" + } + } + } }, "Position Exchange Requested" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Захтевана размена позиција" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "位置交換請求中" + } + } + } }, "Position Flags" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Заставице позиције" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "位置標記" + } + } + } }, "Position Log" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Логови позиција" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "位置日誌" + } + } + } }, "Position Log %lld Points" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Дневник позиција %lld тачака" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "位置日誌 %lld 個點" + } + } + } }, "Position Packet" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Пакети позиција" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "位置封包" + } + } + } }, "Position Sent" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Position gesendet" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Позиција послата" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "位置已傳送" + } + } + } }, "position.config" : { "localizations" : { @@ -16558,6 +27142,12 @@ "value" : "Positionskonfiguration" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Подешавања позиције" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -16575,6 +27165,12 @@ "position.precision %@" : { "extractionState" : "migrated", "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Innerhalb %@" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -16592,32 +27188,206 @@ "state" : "translated", "value" : "Inom %@" } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "У кругу %@" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "在 %@ 以內" + } } } }, "Positions Enabled" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Позиционирање укључено" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "启用定位" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "位置已啟用" + } + } + } }, "Positions will be provided by your device GPS, if you select disabled or not present you can set a fixed position." : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Позиције ће бити обезбеђене путем GPS-а вашег уређаја. Ако одаберете опцију „онемогућено“ или „није присутно“, можете подесити фиксну позицију." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "位置将由设备 GPS 提供,如果选择禁用或不存在,则可以设置固定位置。" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "裝置位置將由您的裝置 GPS 提供。如果您選擇停用或未呈現,您可以設定固定位置。" + } + } + } }, "Power Metrics" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Мерни подаци о снази" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "電力指標" + } + } + } }, "Power Off" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Искључи" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "關機" + } + } + } }, "Power Options" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Опције снаге" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "電力選項" + } + } + } }, "Power Screen" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Снага екрана" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "電力螢幕" + } + } + } + }, + "power.metrics.delete" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Delete all power metrics?" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "刪除所有電力指標?" + } + } + } + }, + "power.metrics.log" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Power Metrics Log" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "電力指標日誌" + } + } + } }, "Powered" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Angeschaltet" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Напајано" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "電力供應" + } + } + } }, "Precise Location" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Genaue Position" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Прецизне локације" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "精確位置" + } + } + } }, "preferred.radio" : { "extractionState" : "manual", @@ -16664,10 +27434,16 @@ "value" : "Föredragen Radio" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Преферирани радио" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "首选电台" + "value" : "默认电台" } }, "zh-Hant-TW" : { @@ -16679,40 +27455,279 @@ } }, "Presets" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Унапред подешено" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "预设" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "預設設定" + } + } + } }, "Press Pin" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Притисни пин" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "按壓圖釘" + } + } + } }, "PRESSURE" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "DRUCK" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "ПРИТИСАК" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "氣壓" + } + } + } }, "Primary" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Основни" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "主要" + } + } + } }, "Primary Admin Key" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Erster Admin-Schlüssel" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Основни административни кључ" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "一级管理员密钥" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "主要管理金鑰" + } + } + } }, "Primary GPIO" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Основни GPIO" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "主要 GPIO" + } + } + } }, "Private Key" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Privater Schlüssel" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Приватни кључ" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "私钥" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "私密金鑰" + } + } + } }, "Project information" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Информације о пројекту" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "项目信息" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "專案資訊" + } + } + } }, "Public Key" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Öffentlicher Schlüssel" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Јавни кључ" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "公钥" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "公用金鑰" + } + } + } }, "Public Key Encryption" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Шифровање јавним кљулем" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "公钥加密" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "公用金鑰加密" + } + } + } }, "Public Key Mismatch" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Неслагање јавних кључева" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "公钥不匹配" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "公用金鑰不符" + } + } + } }, "PWD" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "PWD" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "電力供應區" + } + } + } + }, + "Radio Disconnected" : { + "extractionState" : "manual", + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Радио веза је прекинута" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "無線電已斷線" + } + } + } }, "radio.configuration" : { "localizations" : { @@ -16758,6 +27773,12 @@ "value" : "Radioinställningar" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Конфигурација радио уређаја" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -16816,6 +27837,12 @@ "value" : "Räckviddstest" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Тест домета" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -16875,6 +27902,12 @@ "value" : "Blockera räckviddstest" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Тест домета блока" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -16933,6 +27966,12 @@ "value" : "Konfiguration av räckviddstest" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Конфигурација теста домета" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -16952,7 +27991,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Reboot" + "value" : "Neustart" } }, "en" : { @@ -16991,6 +28030,12 @@ "value" : "Starta om" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Поновно покретање" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -17006,14 +28051,33 @@ } }, "Reboot Node?" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Knoten neustarten?" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Поново покрени чвор?" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "重新啟動節點?" + } + } + } }, "reboot.node" : { "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Node neustarten?" + "value" : "Knoten neustarten?" } }, "en" : { @@ -17052,6 +28116,12 @@ "value" : "Starta om nod?" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Поново покрени чвор?" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -17061,16 +28131,48 @@ "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "重啟中繼點" + "value" : "重新啟動節點?" } } } }, "Rebroadcast Mode" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Режим реемитовања" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "转播模式" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "重新廣播模式" + } + } + } }, "Receive data (rxd) GPIO pin" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Пријемни податак (rxd) GPIO пин" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "接收資料 (rxd) GPIO 腳位" + } + } + } }, "received.ack" : { "localizations" : { @@ -17116,6 +28218,12 @@ "value" : "Mottaget kvitto" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Примљен ACK" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -17125,7 +28233,7 @@ "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "收到確認" + "value" : "我可以幫你做到那個工作!但是请提供英文原稿,我 sẽ將其轉換成 zh-Hant-TW。" } } } @@ -17174,6 +28282,12 @@ "value" : "Mottagarkvitto" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Прималац ACK" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -17189,17 +28303,80 @@ } }, "Recording route" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Route aufzeichnen" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Снимање руте" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "錄製路線" + } + } + } }, "Refresh device metadata" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Освежи метаподатке уређаја" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "更新裝置元數據" + } + } + } }, "Region" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Region" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Регион" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "区域" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "區域" + } + } + } }, "relativetimeofday.afternoon" : { "extractionState" : "migrated", "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nachmittag" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -17211,12 +28388,30 @@ "state" : "translated", "value" : "Tarde" } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Пре подне" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "下午" + } } } }, "relativetimeofday.evening" : { "extractionState" : "migrated", "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Abend" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -17228,12 +28423,30 @@ "state" : "translated", "value" : "Noite" } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Вече" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "傍晚" + } } } }, "relativetimeofday.midday" : { "extractionState" : "migrated", "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mittag" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -17245,12 +28458,30 @@ "state" : "translated", "value" : "Meio-dia" } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Подне" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "中午" + } } } }, "relativetimeofday.morning" : { "extractionState" : "migrated", "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Morgen" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -17262,12 +28493,30 @@ "state" : "translated", "value" : "Manhã" } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Јутро" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "早晨" + } } } }, "relativetimeofday.nighttime" : { "extractionState" : "migrated", "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nacht" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -17279,29 +28528,166 @@ "state" : "translated", "value" : "Noite" } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ноћ" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "夜間時間" + } } } }, "Release Notes" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Белешке о издању" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "版本註釋" + } + } + } }, "Remote administration for: %@" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Даљинска администрација за: %@" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "遠端管理:%@" + } + } + } }, "Remote Legacy Admin: %@" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Администрација застарелих система на даљину: %@" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "遠端傳統管理員:%@" + } + } + } }, "Remote PKI Admin: %@" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Администрација PKI на даљину: %@" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "遠端 PKI 管理員:%@" + } + } + } }, "Remove" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Entfernen" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Уклони" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "移除" + } + } + } }, "Remove from favorites" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Von Favoriten entfernen" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Уклони из омиљених" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "從最愛中移除" + } + } + } + }, + "Remove from ignored" : { + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Уклони из игнорисаних" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "从忽略中删除" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "從忽略清單中移除" + } + } + } }, "Replace Channels" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Замени канале" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "取代通道" + } + } + } }, "reply" : { "localizations" : { @@ -17347,6 +28733,12 @@ "value" : "Svara" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Одговори" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -17362,35 +28754,175 @@ } }, "Request Legacy Admin: %@" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Захтевај администрацију застарелих система: %@" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "請求傳統管理員:%@" + } + } + } }, "Request PKI Admin: %@" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Захтевај PKI администрацију: %@" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "請求 PKI 管理員:%@" + } + } + } }, "Requires that there be an accelerometer on your device." : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Захтева да уређај има акцелерометар." + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "您的裝置需要具備加速度感測器。" + } + } + } }, "Reset App Settings" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "App-Einstellungen zurücksetzen" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ресетовање подешавања апликације" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "重置 App 设置" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "重設應用程式設定" + } + } + } }, "Reset NodeDB" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Knotendatenbank zurücksetzen" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ресетовање базе чворова (NodeDB)" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "重置節點資料庫" + } + } + } }, "Restart" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Neustarten" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Поновно покретање" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "重新啟動" + } + } + } }, "Restart to the node you are connected to" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Verbundenen Knoten neustarten" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Поновно покретање на чвор на који сте повезани" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "重新啟動至您已連線的節點。" + } + } + } }, "restore" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wiederherstellen" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Обнова" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "還原" + } + } + } }, "resume" : { "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Resume" + "value" : "Fortsetzen" } }, "en" : { @@ -17429,6 +28961,12 @@ "value" : "Återuppta" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Настави" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -17444,14 +28982,39 @@ } }, "Review the app" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "App bewerten" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Оцените апликацију" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "审查应用程序" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "檢視應用程式" + } + } + } }, "ringtone" : { "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Ringtone" + "value" : "Klingelton" } }, "en" : { @@ -17490,6 +29053,12 @@ "value" : "Ringsignal" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Мелодија звона" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -17510,7 +29079,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Ringtone Config" + "value" : "Klingelton Konfiguration" } }, "en" : { @@ -17549,6 +29118,12 @@ "value" : "Ringsignalsinställningar" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Подешавање мелодије звона" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -17564,31 +29139,178 @@ } }, "Role" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Rolle" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Улога" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "角色" + } + } + } }, "Role: %@" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Rolle: %@" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Улога: %@" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "角色:%@" + } + } + } }, "Roles" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Rollen" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Улоге" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "角色" + } + } + } }, "Root Topic" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Корен тема" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "根主題" + } + } + } }, "Rotary 1" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ротациони 1" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "旋转一次" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "旋鈕 1" + } + } + } }, "Route Back: %@" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Путања назад: %@" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "路由返回:%@" + } + } + } }, "Route Lines" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Линије руте" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "路由線路" + } + } + } }, "Route recording paused" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Снимање руте паузирано" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "路由錄製暫停" + } + } + } }, "Route: %@" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Route: %@" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Рута: %@" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "路由:%@" + } + } + } }, "route.recorder" : { "localizations" : { @@ -17634,6 +29356,12 @@ "value" : "Ruttinspelare" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Снимач руте" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -17649,17 +29377,43 @@ } }, "Router" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Рутер" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "路由器" + } + } + } }, "Router Options" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Опције рутера" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "路由器選項" + } + } + } }, "routes" : { "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Routes" + "value" : "Routen" } }, "en" : { @@ -17698,6 +29452,12 @@ "value" : "Rutter" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Руте" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -17715,6 +29475,12 @@ "routes.activitytype.biking" : { "extractionState" : "migrated", "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Biken" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -17726,12 +29492,36 @@ "state" : "translated", "value" : "Passeio de Bicicleta" } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Вожња бицикле" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "自行车" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "騎自行車" + } } } }, "routes.activitytype.driving" : { "extractionState" : "migrated", "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Fahren" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -17743,12 +29533,36 @@ "state" : "translated", "value" : "Conduzir" } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Вожња аута" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "驾驶" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "駕駛" + } } } }, "routes.activitytype.filename.biking" : { "extractionState" : "migrated", "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "biken" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -17760,12 +29574,36 @@ "state" : "translated", "value" : "Passeio de Bicicleta" } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "тура бициклом" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "自行车旅行" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "騎自行車遊覽" + } } } }, "routes.activitytype.filename.driving" : { "extractionState" : "migrated", "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "fahren" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -17777,12 +29615,36 @@ "state" : "translated", "value" : "Conduzir" } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "вожња" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "驾驶" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "駕駛" + } } } }, "routes.activitytype.filename.hiking" : { "extractionState" : "migrated", "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "wandern" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -17794,6 +29656,24 @@ "state" : "translated", "value" : "Caminhar na Montanha" } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "планинарње" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "徒步" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "健行" + } } } }, @@ -17811,12 +29691,36 @@ "state" : "translated", "value" : "Caminhar overland" } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Вожња преко копна" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "越野" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "越野駕駛" + } } } }, "routes.activitytype.filename.skiing" : { "extractionState" : "migrated", "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "skitour" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -17828,12 +29732,36 @@ "state" : "translated", "value" : "Passeio de esqui" } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "ски тура" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "滑雪之旅" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "滑雪之旅" + } } } }, "routes.activitytype.filename.walking" : { "extractionState" : "migrated", "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "gehen" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -17845,12 +29773,36 @@ "state" : "translated", "value" : "Caminhar" } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "шетња" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "步行" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "漫步" + } } } }, "routes.activitytype.hiking" : { "extractionState" : "migrated", "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wandern" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -17862,6 +29814,24 @@ "state" : "translated", "value" : "Caminhada na Montanha" } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Планинарење" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "徒步" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "健行" + } } } }, @@ -17879,12 +29849,36 @@ "state" : "translated", "value" : "Overlanding" } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Оверлендинг" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "越野" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "越野露營" + } } } }, "routes.activitytype.skiing" : { "extractionState" : "migrated", "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Skifahren" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -17896,12 +29890,36 @@ "state" : "translated", "value" : "Esqui" } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Скијање" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "滑雪" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "滑雪" + } } } }, "routes.activitytype.walking" : { "extractionState" : "migrated", "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gehen" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -17913,6 +29931,24 @@ "state" : "translated", "value" : "Caminhada" } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Шетња" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "步行" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "漫步" + } } } }, @@ -17961,6 +29997,12 @@ "value" : "Bekräftad" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Потврђено" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -18020,6 +30062,12 @@ "value" : "Felaktig begäran" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Лош захтев" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -18079,6 +30127,12 @@ "value" : "Regionala sändningsgränsen nådd" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Достигнут регионални лимит радног циклуса" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -18138,6 +30192,12 @@ "value" : "Mottog ett negativt kvitto" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Примљено негативно признање" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -18197,6 +30257,12 @@ "value" : "Max antal omsändningar nått" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Достигнут максималан број поновних слања" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -18256,6 +30322,12 @@ "value" : "Ingen kanal" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Нема канала" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -18276,7 +30348,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Kein Interface" + "value" : "Keine Schnittstelle" } }, "en" : { @@ -18315,6 +30387,12 @@ "value" : "Inget gränssnitt" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Нема интерфејса" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -18374,6 +30452,12 @@ "value" : "Inget svar" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Нема одговора" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -18433,6 +30517,12 @@ "value" : "Ingen rutt" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Нема руте" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -18492,6 +30582,12 @@ "value" : "Inte auktoriserad" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Није ауторизовано" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -18509,22 +30605,64 @@ "routing.pkifailed" : { "extractionState" : "manual", "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Verschlüsseltes Senden fehlgeschlagen" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Encrypted Send Failed" } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Шифровано слање није успело" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "加密傳送失敗" + } } } }, "routing.pkiunknownpubkey" : { "extractionState" : "manual", "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Unbekannter öffentlicher Schlüssel" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Unknown Public Key" } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Непознат јавни кључ" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "未知的公钥" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "未知公用金鑰" + } } } }, @@ -18573,6 +30711,12 @@ "value" : "Tidsgräns överskriden" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Време истекло" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -18632,6 +30776,12 @@ "value" : "Paketet är för stort" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Пакет је превелики" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -18647,16 +30797,91 @@ } }, "RSSI %@ dBm" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "RSSI %@ dBm" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "RSSI %@ dBm" + } + } + } }, "RSSI %ddB" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "RSSI %ddB" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "RSSI %ddBm" + } + } + } }, "RSSI %llddB" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "RSSI %llddB" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "RSSI %lld dBm" + } + } + } + }, + "russia" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Russia" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Русија" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "俄羅斯" + } + } + } }, "RX Boosted Gain" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Појачање пријемника" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "接收信號增益" + } + } + } }, "satellite" : { "extractionState" : "migrated", @@ -18703,6 +30928,12 @@ "value" : "Satellit" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Сателит" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -18762,6 +30993,12 @@ "value" : "Satellitöverflygning" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Прелет сателита" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -18777,13 +31014,70 @@ } }, "Sats" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Satelliten" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Сателита" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "衛星" + } + } + } }, "Sats Estimate %lld" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Satelliten Schätzung %lld" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Процена броја сателита %lld" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "衛星估計 %lld" + } + } + } }, "Sats in view: %@" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Satelliten in Sicht: %@" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Сателити на видику: %@" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "可見衛星數: %@" + } + } + } }, "save" : { "localizations" : { @@ -18829,6 +31123,12 @@ "value" : "Spara" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Сачувај" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -18844,13 +31144,54 @@ } }, "Save" : { - - }, - "Save Channel Settings" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Speichern" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Сачувај" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "儲存" + } + } + } }, "Save User Config to %@?" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Benutzerkonfiguration nach %@ speichern?" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Сачувати корисничу конфигурацију за %@?" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "保存用户配置到 %@?" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "將使用者組態儲存至 %@?" + } + } + } }, "save.config %@" : { "extractionState" : "migrated", @@ -18858,7 +31199,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Save Config for %@" + "value" : "Speichere Konfiguration für %@" } }, "en" : { @@ -18897,6 +31238,12 @@ "value" : "Spara konfiguration för %@" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Сачувати конфигурацију за %@" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -18912,43 +31259,278 @@ } }, "Saves a CSV with the range test message details, currently only available on ESP32 devices with a web server." : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Снима CSV са детаљима порука теста домета, тренутно доступно само на ESP32 уређајима са веб сервером." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "保存包含量程测试报文详细信息的 CSV 文件,目前仅适用于配有网络服务器的 ESP32 设备。" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "將範圍測試訊息詳細資料儲存為 CSV 檔案,目前僅適用於具有 Web 伺服器的 ESP32 裝置。" + } + } + } }, "Screen on for" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Екран укључен за" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "螢幕開啟時間為" + } + } + } }, "Search" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Suchen" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Претражи" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "搜尋" + } + } + } }, "Second" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Други" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "第二" + } + } + } }, "Secondary" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Секундарни" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "備用" + } + } + } }, "Secondary Admin Key" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zweiter Admin-Schlüssel" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Секундарни административни кључ" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "二级管理员密钥" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "備用管理金鑰" + } + } + } }, "Security" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sicherheit" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Сигурност" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "安全" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "安全" + } + } + } }, "Security Config" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sicherheitskonfiguration" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Сигурносна подешавања" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "安全配置" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "安全組態" + } + } + } }, "Security Config Settings require a firmware version 2.5+" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sicherheitskonfigurationseinstellungen erfordern eine Firmware mit Version 2.5 oder höher" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Сигурносна подешавања захтевају фирмвер верзију 2.5+" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "安全配置需要固件版本 2.5+" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "安全組態設定需要韌體版本 2.5 或以上。" + } + } + } }, "Select a channel" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kanal wählen" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Одабери канал" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "選擇通道" + } + } + } }, "Select a conversation" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Изабери разговор" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "選擇對話" + } + } + } }, "Select a conversation type" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Изабери тип разговора" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "選擇對話類型" + } + } + } }, "Select a Trace Route" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Изабери пут праћења кроз мрежу" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "選擇追蹤路線" + } + } + } }, "select.contact" : { "extractionState" : "manual", @@ -18995,6 +31577,12 @@ "value" : "Välj en kontakt" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Одабери контакт" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -19009,71 +31597,12 @@ } } }, - "select.menu.item" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Wähle einen Menüeintrag aus" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Select an item from the menu" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Sélectioner un item du menu" - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "בחר מהתפריט" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Wybierz element z menu" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Seleciona um opção do menu" - } - }, - "se" : { - "stringUnit" : { - "state" : "translated", - "value" : "Välj ett alternativ från menyn" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "从菜单选择一个选项" - } - }, - "zh-Hant-TW" : { - "stringUnit" : { - "state" : "translated", - "value" : "從菜單選擇項目" - } - } - } - }, "select.node" : { "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Node auswählen" + "value" : "Knoten auswählen" } }, "en" : { @@ -19112,6 +31641,12 @@ "value" : "Välj en nod" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Одабери чвор" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -19126,6 +31661,3450 @@ } } }, + "Send" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Senden" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Пошаљи" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "傳送" + } + } + } + }, + "Send ${messageContent} to ${channelNumber}" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sende ${messageContent} an ${channelNumber}" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Пошаљи ${messageContent} на ${channelNumber}" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "將 \"${messageContent}\" 發送至 ${channelNumber}" + } + } + } + }, + "Send a Group Message" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gruppennachricht senden" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Пошаљи групну поруку" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "發送群組訊息" + } + } + } + }, + "Send a message to a certain meshtastic channel" : { + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Пошаљи поруку на одређени месхтастичан канал" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "將訊息發送至特定 Meshtastic 通道" + } + } + } + }, + "Send a position on the primary channel when the user button is triple clicked." : { + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Пошаљи позицију на примарном каналу када се корисничко дугме три пута кликне." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "当用户按钮被点击三次时,在主通道上发送定位。" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "使用者按鈕被三連擊時,在主要頻道上發送位置。" + } + } + } + }, + "Send a shutdown to the node you are connected to" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Herunterfahren an verbundenen Knoten senden" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Пошаљи искључење чвору на који си повезан" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "將關閉訊號傳送到您已連線的節點。" + } + } + } + }, + "Send a Waypoint" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wegpunkt senden" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Пошаљи тачку путање" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "傳送航點" + } + } + } + }, + "Send ASCII bell with alert message. Useful for triggering external notification on bell." : { + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Пошаљи ASCII звона са поруком упозорења. Корисно за покретање спољашњег обавештења на звону." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "发送带有警报信息的 ASCII 铃声。用于触发外部铃声通知。" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "使用 ASCII 鈴聲與警報訊息傳送。有助於觸發外部通知中的鈴聲提示。" + } + } + } + }, + "Send Bell" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sende Glocke" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Пошаљи звоно" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "发送铃声" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "傳送鈴聲" + } + } + } + }, + "Send Reboot OTA" : { + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Пошаљи сигнал поновног покретања (OTA)" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "发送重启 OTA" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "傳送開機重新整理 OTA" + } + } + } + }, + "Sender Interval" : { + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Инерварл пошиљаоца" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "发送间隔" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "傳送間隔" + } + } + } + }, + "Sensor Metrics" : { + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Метрике сензора" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "传感器指标" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "感應器指標" + } + } + } + }, + "Sensor options" : { + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Опције сензора" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "感應器選項" + } + } + } + }, + "Sensor Options" : { + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Опције сензора" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "感應器選項" + } + } + } + }, + "Sent out to other nodes on the mesh to allow them to compute a shared secret key." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wird an andere Knoten im Netz gesendet, damit diese einen gemeinsamen geheimen Schlüssel berechnen können." + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Послато другим чворовима на меш мрежи како би им омогућило да израчунају заједнички тајни кључ." + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "傳送至網格上的其他節點,以允許它們計算共享密鑰。" + } + } + } + }, + "Sequence number" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sequenznummer" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Број секвенце" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "序號" + } + } + } + }, + "Sequence: %@" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sequenz: %@" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Секвенца: %@" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "序號:%@" + } + } + } + }, + "serial" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Serial" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Serial" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Série" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "סיריאלי" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Seryjny" + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "Serial" + } + }, + "se" : { + "stringUnit" : { + "state" : "translated", + "value" : "Serie" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Серијска веза" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "串口" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "我很高興可以幫助您!請提供原文,我們可以一起進行中文翻譯。" + } + } + } + }, + "Serial Console" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Serielle Konsole" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Серијска конзола" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "序列控制台" + } + } + } + }, + "Serial Console over the Stream API." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Serielle Konsole über die Stream-API." + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Серијска конзола преко Stream API-ја." + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "串流 API 的序列控制台。" + } + } + } + }, + "serial.config" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Serial Konfiguration" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Serial Config" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Configuration série" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "'הגדרות מודולה 'סיריאלי" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Konfiguracja seryjna" + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "Configuração Serial" + } + }, + "se" : { + "stringUnit" : { + "state" : "translated", + "value" : "Seriekonfiguration" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Подешавања серијске везе" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "串口配置" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "串口設定" + } + } + } + }, + "serial.mode.default" : { + "extractionState" : "migrated", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Standard" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Default" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Défaut" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "ברירת מחדל" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Domyślny" + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "Padrão" + } + }, + "se" : { + "stringUnit" : { + "state" : "translated", + "value" : "Standard" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Основни" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "默认" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "預設" + } + } + } + }, + "serial.mode.nmea" : { + "extractionState" : "migrated", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "NMEA Positionen" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "NMEA Positions" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Positions NMEA" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "מיקומי NMEA" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pozycje NMEA" + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "Posições NMEA" + } + }, + "se" : { + "stringUnit" : { + "state" : "translated", + "value" : "NMEA-positioner" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "NMEA позиције" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "NMEA 位置" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "NMEA 位置" + } + } + } + }, + "serial.mode.proto" : { + "extractionState" : "migrated", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Protobufs" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Protobufs" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Protobufs" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "Protobufs" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Protobufy" + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "Protobufs" + } + }, + "se" : { + "stringUnit" : { + "state" : "translated", + "value" : "Protobufs" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Протобафови" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "Protobufs" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "Protobufs" + } + } + } + }, + "serial.mode.simple" : { + "extractionState" : "migrated", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Einfach" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Simple" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Simple" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "פשוט" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Prosty" + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "Simples" + } + }, + "se" : { + "stringUnit" : { + "state" : "translated", + "value" : "Enkel" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Једноставни" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "简单" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "簡單" + } + } + } + }, + "serial.mode.txtmsg" : { + "extractionState" : "migrated", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Textnachricht" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Text Message" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Message texte" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "הודעת טקסט" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wiadomość tekstowa" + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mensagem de Texto" + } + }, + "se" : { + "stringUnit" : { + "state" : "translated", + "value" : "Textmeddelande" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Текстуална порука" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "文本消息" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "文本訊息" + } + } + } + }, + "Series" : { + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Серије" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "系列" + } + } + } + }, + "Server" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Server" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Сервер" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "伺服器" + } + } + } + }, + "Server Address" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Serveradresse" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Адреса сервера" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "伺服器位址" + } + } + } + }, + "Set" : { + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Подеси" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "設定" + } + } + } + }, + "Set the GPIO pins for RXD and TXD." : { + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Подеси GPIO пинове за RXD и TXD." + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "設定 RXD 和 TXD 的 GPIO 腳位。" + } + } + } + }, + "set.region" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Setze LoRa Region" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Set LoRa Region" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Définir la région LoRa" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "בחר אזור לורה" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ustaw region LoRa" + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "Seleciona o Região da LoRa" + } + }, + "se" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ställ in LoRa-region" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Подеси LoRA регион" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "设置 LoRa 区域" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "設定 LoRa 區域" + } + } + } + }, + "Sets the maximum number of hops, default is 3. Increasing hops also increases congestion and should be used carefully. O hop broadcast messages will not get ACKs." : { + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Подешава максималан број скокова. Подразумевано је 3, а повећање броја одобрених скокова такође повећава загушење и треба га користити опрезно. Поруке емитоване са 0 скокова неће добити потврде пријема (ACK)." + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "設定最大跳數,預設為 3。增加跳數也會增加擁塞,應謹慎使用。0 跳廣播訊息將不會收到確認回應 (ACKs)。" + } + } + } + }, + "settings" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Einstellungen" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Settings" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Réglages" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "הגדרות" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ustawienia" + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "Definições" + } + }, + "se" : { + "stringUnit" : { + "state" : "translated", + "value" : "Inställningar" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Подешавања" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "设置" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "sorry,我們無法完成這項 request。" + } + } + } + }, + "Share QR Code & Link" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "QR Code & Link teilen" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Дели QR код и линк" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "分享 QR 碼與連結" + } + } + } + }, + "share.channels" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kanal QR Code teilen" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Share QR Code" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Partager le QR Code" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "שתף ערוצים באמצעות קוד QR" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Udostępnij kod QR kanałów" + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "Partilhar o Código do QR" + } + }, + "se" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dela QR-kod" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Дели QR код" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "分享频道二维码" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "分享頻道QRcode" + } + } + } + }, + "share.position" : { + "extractionState" : "manual", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Position teilen" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Share Position" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Partager la position" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "שתף מיקום" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Udostępnij pozycję" + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "Partilhar o Posição" + } + }, + "se" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dela position" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Подели позицију" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "分享位置" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "分享位置" + } + } + } + }, + "Shared Key" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gemeinsamer Schlüssel" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Дељени кључ" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "共用金鑰" + } + } + } + }, + "Short Name" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kurzname" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Кратко име" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "短名称" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "簡短名稱" + } + } + } + }, + "Short Name: %@" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kurzname: %@" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Кратко име: %@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "短名称: %@" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "簡短名稱: %@" + } + } + } + }, + "short.range.fast" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Short Range - Fast" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Кратки домет - Брзо" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "短距離 - 快速" + } + } + } + }, + "short.range.slow" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Short Range - Slow" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Кратки домет - Споро" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "短距離 - 慢速" + } + } + } + }, + "short.range.turbo" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Short Range - Turbo" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Кратки домет - Турбо" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "短距離 - 極速" + } + } + } + }, + "Show alerts" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zeige Alarme" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Прикажи узбуне" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "顯示警報" + } + } + } + }, + "Show Alerts" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zeige Alarme" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Прикажи узбуне" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "顯示警報" + } + } + } + }, + "Show nodes" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zeige Knoten" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Прикажи чворове" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "顯示節點" + } + } + } + }, + "Show on device screen" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zeige auf dem Gerätebildschirm" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Прикажи на екрану уређаја" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "顯示在裝置螢幕上" + } + } + } + }, + "Show on the mesh map." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zeige auf der Netzwerkkarte." + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Прикажи на мапи меш мреже." + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "顯示在網狀圖上。" + } + } + } + }, + "Show Waypoints " : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zeige Wegpunkte" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Прикажи тачке путање" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "顯示航點" + } + } + } + }, + "Shut Down" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Herunterfahren" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Искључи" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "關閉" + } + } + } + }, + "Shut Down Node?" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Knoten herunterfahren?" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Искључити чвор?" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "關閉節點?" + } + } + } + }, + "Shutdown Node?" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Knoten herunterfahren?" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Искључити чвор?" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "關閉節點?" + } + } + } + }, + "Signal %@" : { + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Сигнал %@" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "訊號 %@" + } + } + } + }, + "singapore.923mhz" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Singapore 923MHz" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Сингапур 923MHz" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "新加坡 923MHz" + } + } + } + }, + "Smart Position" : { + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Паметно позиционирање" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "智慧位置" + } + } + } + }, + "SNR" : { + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "SNR" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "信噪比" + } + } + } + }, + "SNR %@ dB" : { + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "SNR %@ dB" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "信噪比 %@ dB" + } + } + } + }, + "SNR %@dB" : { + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "SNR %@dB" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "信噪比 %@ dB" + } + } + } + }, + "Specifies how long the monitored GPIO should output." : { + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Одређује колико дуго треба да траје излазни сигнал надзираног GPIO-а." + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "指定監控的 GPIO 應輸出的時間長度。" + } + } + } + }, + "Speed" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Geschwindigkeit" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Брзина" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "速度" + } + } + } + }, + "Speed %@" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Geschwindigkeit %@" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Брзина %@" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "速度 %@" + } + } + } + }, + "Speed: %@" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Geschwindigkeit: %@" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Брзина: %@" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "速度:%@" + } + } + } + }, + "Spread Factor" : { + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Фактор ширења" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "擴散因子" + } + } + } + }, + "ssid" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "SSID" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "SSID" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "SSID" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "שם רשת וויפי" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "SSID" + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "SSID" + } + }, + "se" : { + "stringUnit" : { + "state" : "translated", + "value" : "SSID" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "SSID" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "SSID" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "SSID" + } + } + } + }, + "standard" : { + "extractionState" : "migrated", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Standard" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Standard" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Standard" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "סטנדרטי" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Standardowy" + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "Padrão" + } + }, + "se" : { + "stringUnit" : { + "state" : "translated", + "value" : "Standard" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Стандардно" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "标准" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "標準" + } + } + } + }, + "standard.muted" : { + "extractionState" : "migrated", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Standard Muted" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Standard Muted" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Standard en sourdine" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "סטנדרתי-השתק" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Standardowy wyłączony" + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "Padrão Silenciado" + } + }, + "se" : { + "stringUnit" : { + "state" : "translated", + "value" : "Standard Muted" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Стандардно мутирано" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "标准静音" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "標準靜音" + } + } + } + }, + "start" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Start" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Start" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Démarrer" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "החל" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Start" + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "Iniciar" + } + }, + "se" : { + "stringUnit" : { + "state" : "translated", + "value" : "Start" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Почетак" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "开始" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "開始" + } + } + } + }, + "State Broadcast Interval" : { + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Интервал емитовања стања" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "狀態廣播間隔" + } + } + } + }, + "Store and forward clients can request history from routers on the network." : { + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Клијенти за складиштење и прослеђивање могу затражити историју од рутера на мрежи." + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "儲存與轉發的客戶端可以從網路上的路由器請求歷史紀錄。" + } + } + } + }, + "Store and forward router devices require a ESP32 device with PSRAM." : { + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Рутер за складиштење и прослеђивање захтева ESP32 уређај са PSRAM." + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "儲存與轉發路由器設備需要搭載 PSRAM 的 ESP32 裝置。" + } + } + } + }, + "storeforward" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Store & Forward" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Store & Forward" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Stocker et Transmettre" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "שמירה ושליחה" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Store & Forward" + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "Armazenar e Encaminhar" + } + }, + "se" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lagra & Videresänd" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Складиштење и прослеђивање" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "储存 & 转发" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "儲存 & 轉發" + } + } + } + }, + "storeforward.config" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Store & Forward Config" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Store & Forward Config" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Configuration de Stocker et Transmettre" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "הגדרות שמירה ושליחה" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Store & Forward Config" + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "Configuração de Armazenar e Encaminhar" + } + }, + "se" : { + "stringUnit" : { + "state" : "translated", + "value" : "Konfiguration för Lagra & Videresänd" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Конфигурација за складиштење и прослеђивање" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "储存 & 转发设置" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "儲存 & 轉發設定" + } + } + } + }, + "storeforward.heartbeat" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Herzschlag senden" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Send Heartbeat" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Envoyer une impulsion" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "שלח דופק" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Send Heartbeat" + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "Enviar Batimento Cardíaco" + } + }, + "se" : { + "stringUnit" : { + "state" : "translated", + "value" : "Skicka hjärtslag" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Пошаљи откуцај срца" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "发送心跳包" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "發送心跳包" + } + } + } + }, + "subscribed" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Verbunden mit dem Mesh" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Subscribed to mesh" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Abonné au maillage" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "מחובר למש" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zapisano do siatki" + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "Inscrito no mesh" + } + }, + "se" : { + "stringUnit" : { + "state" : "translated", + "value" : "Prenumererar på mesh" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Повезано са меш мрежом" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "连接到 Mesh 网络" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "連接到 Mesh 網路" + } + } + } + }, + "Supported" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Unterstützt" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Подржан" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "支援" + } + } + } + }, + "Supported I2C Connected sensors will be detected automatically, sensors are BMP280, BME280, BME680, MCP9808, INA219, INA260, LPS22 and SHTC3." : { + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Подржани I2C повезани сензори ће бити аутоматски детектовани. Сензори су: BMP280, BME280, BME680, MCP9808, INA219, INA260, LPS22 и SHTC3." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "将自动检测支持 I2C 连接的传感器,包括 BMP280、BME280、BME680、MCP9808、INA219、INA260、LPS22 和 SHTC3。" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "支援的 I2C 連接感測器將會自動偵測,這些感測器包括 BMP280、BME280、BME680、MCP9808、INA219、INA260、LPS22 和 SHTC3。" + } + } + } + }, + "Table" : { + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Табела" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "表格" + } + } + } + }, + "taiwan" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Taiwan" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Тајван" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "台灣" + } + } + } + }, + "tapback" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tapback Antwort" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tapback Response" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Réponse de Tapback" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "תגובה מהירה" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Odpowiedź na stuknięcie" + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "Resposta Tapback" + } + }, + "se" : { + "stringUnit" : { + "state" : "translated", + "value" : "Svarsreaktion" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Реакција додиром" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "响应" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "響應" + } + } + } + }, + "tapback.exclamation" : { + "extractionState" : "migrated", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ausrufezeichen" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Exclamation Mark" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Point d'exclamation" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "סימן קריאה" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wykrzyknik" + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ponto de Exclamação" + } + }, + "se" : { + "stringUnit" : { + "state" : "translated", + "value" : "Utropstecken" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Узвичник" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "感叹号" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "驚嘆號" + } + } + } + }, + "tapback.haha" : { + "extractionState" : "migrated", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "HaHa" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "HaHa" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "HaHa" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "חחח" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "HaHa" + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "HaHa" + } + }, + "se" : { + "stringUnit" : { + "state" : "translated", + "value" : "HaHa" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Хахаха" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "哈哈" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "哈哈" + } + } + } + }, + "tapback.heart" : { + "extractionState" : "migrated", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Herz" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Heart" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Coeur" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "לב" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Serce" + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "Coração" + } + }, + "se" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hjärta" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Срце" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "心" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "心" + } + } + } + }, + "tapback.poop" : { + "extractionState" : "migrated", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kacke" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Poop" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Caca" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "חרא" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kupa" + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cocó" + } + }, + "se" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bajs" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Кака" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "便便" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "便便" + } + } + } + }, + "tapback.question" : { + "extractionState" : "migrated", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Fragezeichen" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Question Mark" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Point d'interrogation" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "סימן שאלה" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Znak zapytania" + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ponto de Interrogação" + } + }, + "se" : { + "stringUnit" : { + "state" : "translated", + "value" : "Frågetecken" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Знак питања" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "问号" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "I'll be happy to help you with the translation. Please go ahead and provide the existing prompt content in English, and I'll translate it into zh-Hant-TW for you.\n\n(如果您需要任何其他語言的翻譯,請告訴我,我會盡力幫助您)" + } + } + } + }, + "tapback.thumbsdown" : { + "extractionState" : "migrated", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Daumen runter" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Thumbs Down" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pouce baissé" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "אגודל למטה" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kciuk w dół" + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "Polegar para Baixo" + } + }, + "se" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tummen ner" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Палац доле" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "倒大拇指" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "倒大拇指" + } + } + } + }, + "tapback.thumbsup" : { + "extractionState" : "migrated", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Daumen hoch" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Thumbs Up" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pouce levé" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "אגודל למעלה" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kciuk w górę" + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "Polegar para Cima" + } + }, + "se" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tummen upp" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Лајк" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "竖大拇指" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "豎大拇指" + } + } + } + }, + "tapback.wave" : { + "extractionState" : "migrated", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Welle" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wave" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wave" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wave" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wave" + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "Adeus" + } + }, + "se" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vinka" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Махање" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wave" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "招手" + } + } + } + }, + "telementry.hazardous" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hazardous" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Опасно" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "危險" + } + } + } + }, + "telementry.unhealthy" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Unhealthy" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Нездраво" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "不健康" + } + } + } + }, + "telementry.veryUnhealthy" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Very Unhealthy" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Веома нездраво" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "非常不健康" + } + } + } + }, + "telemetry" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Telemetrie (Sensoren)" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Telemetry (Sensors)" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Télémetrie (Capteurs)" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "טלמטריה (חיישנים)" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Telemetria (czujniki)" + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "Telemetria (Sensores)" + } + }, + "se" : { + "stringUnit" : { + "state" : "translated", + "value" : "Telemetri (Sensorer)" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Телеметрија (сензори)" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "遥测(传感器)" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "遠測(傳感器)" + } + } + } + }, + "Save" : { + + }, + "Save Channel Settings" : { + + }, + "Save User Config to %@?" : { + + }, + "save.config %@" : { + "extractionState" : "migrated", + "telemetry.config" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Telemetrie Einstellungen" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Telemetry Config" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Configuration de télémetrie" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "הגדרות טלמטריה" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Konfiguracja telemetrii" + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "Configuração Telemetria" + } + }, + "se" : { + "stringUnit" : { + "state" : "translated", + "value" : "Telemetriinställningar" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Конфигурација телеметрије" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "遥测配置" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "遠側設定" + } + } + } + }, + "telemetry.good" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Good" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Добро" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "很好" + } + } + } + }, + "telemetry.moderate" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Moderate" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Умерено" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "适度" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "適中" + } + } + } + }, + "telemetry.sensitive" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Unhealthy for Sensitive Groups" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Нездраво за осетљиве групе" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "對敏感族群不健康" + } + } + } + }, + "Temp" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Temp" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Темп." + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "暫存" + } + } + } + }, + "Temperature" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Temperatur" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Температура" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "溫度" + } + } + } + }, + "Ten Minutes" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zehn Minuten" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Десет пинута" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "十分鐘" + } + } + } + }, + "Tertiary Admin Key" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dritter Admin-Schlüssel" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Терцијарни административни кључ" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "三级管理员密钥" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "第三管理金鑰" + } + } + } + }, + "tft.full.color.displays" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "TFT Full Color Displays" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "TFT екрани у пуној боји" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "TFT 全彩顯示器" + } + } + } + }, + "thailand" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Thailand" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Тајланд" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "泰國" + } + } + } + }, "Send" : { }, @@ -19190,1210 +35169,1332 @@ }, "serial" : { + "The amount of time to wait before we consider your packet as done." : { "localizations" : { - "de" : { + "sr" : { "stringUnit" : { "state" : "translated", - "value" : "Serial" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Serial" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Série" - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "סיריאלי" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Seryjny" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Serial" - } - }, - "se" : { - "stringUnit" : { - "state" : "translated", - "value" : "Serie" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "串口" + "value" : "Време чекања пре него што сматрамо да је ваш пакет завршен." } }, "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "串口" + "value" : "在將您的封包視為已完成之前,等待的時間長度。" } } } }, - "Serial Console" : { - - }, - "Serial Console over the Stream API." : { - - }, - "serial.config" : { + "The compass heading on the screen outside of the circle will always point north." : { "localizations" : { - "de" : { + "sr" : { "stringUnit" : { "state" : "translated", - "value" : "Serial Konfiguration" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Serial Config" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Configuration série" - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "'הגדרות מודולה 'סיריאלי" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Konfiguracja seryjna" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Configuração Serial" - } - }, - "se" : { - "stringUnit" : { - "state" : "translated", - "value" : "Seriekonfiguration" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "串口配置" + "value" : "Смер компаса на екрану изван круга увек ће указивати на север." } }, "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "串口設定" + "value" : "螢幕外圓圈之外的指南針方向會始終指向北方。" } } } }, - "serial.mode.default" : { - "extractionState" : "migrated", + "The dew point is %@ right now." : { "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Standard" + "value" : "Der Taupunkt ist gerade %@" } }, - "en" : { + "sr" : { "stringUnit" : { "state" : "translated", - "value" : "Default" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Défaut" - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "ברירת מחדל" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Domyślny" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Padrão" - } - }, - "se" : { - "stringUnit" : { - "state" : "translated", - "value" : "Standard" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "默认" + "value" : "Тачка росе тренутно износи %@." } }, "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "預設" + "value" : "目前露點為 %@。" } } } }, - "serial.mode.nmea" : { - "extractionState" : "migrated", + "The fastest that position updates will be sent if the minimum distance has been satisfied" : { "localizations" : { - "de" : { + "sr" : { "stringUnit" : { "state" : "translated", - "value" : "NMEA Positionen" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "NMEA Positions" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Positions NMEA" - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "מיקומי NMEA" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Pozycje NMEA" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Posições NMEA" - } - }, - "se" : { - "stringUnit" : { - "state" : "translated", - "value" : "NMEA-positioner" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "NMEA 位置" + "value" : "Најбржа брзина којом ће се ажурирати позиција уколико је задовољен минимални услов за растојање." } }, "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "NMEA 位置" + "value" : "如果已滿足最小距離要求,位置更新將以最快速度發送。" } } } }, - "serial.mode.proto" : { - "extractionState" : "migrated", + "The format used to display GPS coordinates on the device screen." : { "localizations" : { - "de" : { + "sr" : { "stringUnit" : { "state" : "translated", - "value" : "Protobufs" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Protobufs" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Protobufs" - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "Protobufs" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Protobufy" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Protobufs" - } - }, - "se" : { - "stringUnit" : { - "state" : "translated", - "value" : "Protobufs" + "value" : "Формат који се користи за приказивање GPS координата на екрану уређаја." } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "Protobufs" + "value" : "用于在设备屏幕上显示 GPS 坐标的格式。" } }, "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "Protobufs" + "value" : "裝置螢幕上顯示 GPS 坐標的格式。" } } } }, - "serial.mode.simple" : { - "extractionState" : "migrated", + "The last 4 of the device MAC address will be appended to the short name to set the device's BLE Name. Short name can be up to 4 bytes long." : { "localizations" : { - "de" : { + "sr" : { "stringUnit" : { "state" : "translated", - "value" : "Einfach" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Simple" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Simple" - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "פשוט" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Prosty" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Simples" - } - }, - "se" : { - "stringUnit" : { - "state" : "translated", - "value" : "Enkel" + "value" : "Последња 4 знака MAC адресе уређаја ће бити додата кратком имену како би се подесило BLE име уређаја. Кратко име може бити до 4 бајта дуго." } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "简单" + "value" : "设备 MAC 地址的后 4 位将附加到短名称中,以设置设备的 BLE 名称。 短名称的长度最多为 4 个字节。" } }, "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "簡單" + "value" : "設備的 MAC 位址最後 4 位將會附加到短名稱,以設定設備的藍牙低功耗 (BLE) 名稱。短名稱長度可達 4 個位元組。" } } } }, - "serial.mode.txtmsg" : { - "extractionState" : "migrated", + "The maximum interval that can elapse without a node broadcasting a position" : { "localizations" : { - "de" : { + "sr" : { "stringUnit" : { "state" : "translated", - "value" : "Text Nachricht" + "value" : "Максимални интервал који може протећи без да чвор емитује позицију." } }, - "en" : { + "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "Text Message" + "value" : "節點最多可以在不廣播位置的情況下經過的時間間隔。" } - }, - "fr" : { + } + } + }, + "The Meshtastic Apple apps support firmware version %@ and above." : { + "localizations" : { + "sr" : { "stringUnit" : { "state" : "translated", - "value" : "Message texte" - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "הודעת טקסט" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Wiadomość tekstowa" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Mensagem de Texto" - } - }, - "se" : { - "stringUnit" : { - "state" : "translated", - "value" : "Textmeddelande" + "value" : "Мештастик апликације за Епл уређаје подржавају верзију фирмвера %@ и новије." } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "文本消息" + "value" : "Meshtastic Apple 应用程序支持 %@ 及以上版本的固件。" } }, "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "文本訊息" + "value" : "Meshtastic 的 Apple 應用程式支援韌體版本 %@ 或以上。" } } } }, - "Server" : { - - }, - "Server Address" : { - - }, - "Set" : { - - }, - "Set the GPIO pins for RXD and TXD." : { - - }, - "set.region" : { + "The minimum distance change in meters to be considered for a smart position broadcast." : { "localizations" : { - "de" : { + "sr" : { "stringUnit" : { "state" : "translated", - "value" : "Setze LoRa Region" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Set LoRa Region" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Définir la région LoRa" - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "בחר אזור לורה" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ustaw region LoRa" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Seleciona o Região da LoRa" - } - }, - "se" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ställ in LoRa-region" + "value" : "Минимална промена растојања у метрима која ће се узети у обзир за паметно емитовање позиције." } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "设置 LoRa 区域" + "value" : "智能位置广播考虑的最小距离变化(以米为单位)。" } }, "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "設定 LoRa 區域" + "value" : "被視為智慧型位置廣播的最小距離變化(公尺)。" } } } }, - "Sets the maximum number of hops, default is 3. Increasing hops also increases congestion and should be used carefully. O hop broadcast messages will not get ACKs." : { - - }, - "settings" : { + "The most recent public key for this node does not match the previously recorded key. You can delete the node and let it exchange keys again, but this also may indicate a more serious security problem. Contact the user through another trusted channel to determine if the key change was due to a factory reset or other intentional action." : { "localizations" : { - "de" : { + "sr" : { "stringUnit" : { "state" : "translated", - "value" : "Einstellungen" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Settings" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Réglages" - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "הגדרות" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ustawienia" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Definições" - } - }, - "se" : { - "stringUnit" : { - "state" : "translated", - "value" : "Inställningar" + "value" : "Најновији јавни кључ за овај чвор се не подудара са претходно снимљеним кључем. Можете избрисати чвор и дозволити му да поново размени кључеве, али ово такође може указивати на озбиљнији безбедносни проблем. Контактирајте корисника преко другог поузданог канала како бисте утврдили да ли је промена кључа резултат фабричког ресетовања или друге намерне акције." } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "设置" + "value" : "该节点的最新公钥与之前记录的公钥不匹配。您可以删除该节点,让它重新交换公钥,但这也可能表明存在更严重的安全问题。通过其他可信渠道联系用户,以确定公钥更改是否是由于出厂重置或其他故意行为造成的。" } }, "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "設定" + "value" : "此節點的最新公鑰與之前記錄的公鑰不符。您可以刪除節點並讓其重新交換金鑰,但這也可能表示存在更嚴重的安全問題。請通過其他可信賴的管道聯繫使用者,以確定金鑰更改是由于出廠重置還是其他故意操作造成的。" } } } }, - "Share QR Code & Link" : { - - }, - "share.channels" : { + "The primary public key authorized to send admin messages to this node." : { "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Kanal QR Code teilen" + "value" : "Der erste öffentliche Schlüssel, der berechtigt ist, Admin-Nachrichten an diesen Knoten zu senden." } }, - "en" : { + "sr" : { "stringUnit" : { "state" : "translated", - "value" : "Share QR Code" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Partager le QR Code" - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "שתף ערוצים באמצעות קוד QR" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Udostępnij kod QR kanałów" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Partilhar o Código do QR" - } - }, - "se" : { - "stringUnit" : { - "state" : "translated", - "value" : "Dela QR-kod" + "value" : "Примарни јавни кључ овлашћен за слање административних порука овом чвору." } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "分享频道二维码" + "value" : "授权向该节点发送管理信息的一级管理员公钥。" } }, "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "分享頻道QRcode" + "value" : "此節點允許發送管理訊息的主要公鑰。" } } } }, - "share.position" : { + "The public key does not match the recorded key. You may delete the node and let it exchange keys again, but this may indicate a more serious security problem. Contact the user through another trusted channel, to determine if the key change was due to a factory reset or other intentional action." : { + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Јавни кључ се не подудара са снимљеним кључем. Можете избрисати чвор и дозволити му да поново размени кључеве, али ово може указивати на озбиљнији безбедносни проблем. Контактирајте корисника преко другог поузданог канала како бисте утврдили да ли је промена кључа резултат фабричког ресетовања или друге намерне акције." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "公钥与记录的公钥不匹配。您可以删除节点,让它重新交换公钥,但这可能表明存在更严重的安全问题。通过其他可信渠道联系用户,以确定公钥更改是否是由于出厂重置或其他故意行为造成的。" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "公鑰與記錄的公鑰不符。您可以刪除該節點並讓其重新交換金鑰,但這可能表示存在更嚴重的安全問題。請通過其他可信賴的管道聯繫使用者,以確定金鑰更改是由于出廠重置還是其他故意操作造成的。" + } + } + } + }, + "The region where you will be using your radios." : { + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Регион у коме ћете користити ваше радио уређаје." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "使用电台的地区。" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "您將要使用無線電的區域。" + } + } + } + }, + "The root topic to use for MQTT." : { + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Корен тема која ће се користити за MQTT." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "用于 MQTT 的根主题。" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "用於 MQTT 的根主題。" + } + } + } + }, + "The secondary public key authorized to send admin messages to this node." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Der zweite öffentliche Schlüssel, der berechtigt ist, Admin-Nachrichten an diesen Knoten zu senden." + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Секундарни јавни кључ овлашћен за слање административних порука овом чвору." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "授权向该节点发送管理信息的二级管理员公钥。" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "此節點允許發送管理訊息的第二個公鑰。" + } + } + } + }, + "The specified device has disconnected from us" : { "extractionState" : "manual", "localizations" : { - "de" : { + "sr" : { "stringUnit" : { "state" : "translated", - "value" : "Position teilen" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Share Position" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Partager la position" - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "שתף מיקום" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Udostępnij pozycję" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Partilhar o Posição" - } - }, - "se" : { - "stringUnit" : { - "state" : "translated", - "value" : "Dela position" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "分享位置" + "value" : "Наведени уређај је прекинуо везу са нама" } }, "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "分享位置" + "value" : "指定的裝置已中斷連線。" } } } }, - "Shared Key" : { - - }, - "Short Name" : { - - }, - "Short Name: %@" : { - - }, - "Show alerts" : { - - }, - "Show Alerts" : { - - }, - "Show nodes" : { - - }, - "Show on device screen" : { - - }, - "Show on the mesh map." : { - - }, - "Show Waypoints " : { - - }, - "Shut Down" : { - - }, - "Shut Down Node?" : { - - }, - "Shutdown Node?" : { - - }, - "Signal %@" : { - - }, - "Smart Position" : { - - }, - "SNR" : { - - }, - "SNR %@ dB" : { - - }, - "SNR %@dB" : { - - }, - "Specifies how long the monitored GPIO should output." : { - - }, - "Speed" : { - - }, - "Speed %@" : { - - }, - "Speed: %@" : { - - }, - "Spread Factor" : { - - }, - "ssid" : { + "The state of the LED (on/off)" : { "localizations" : { - "de" : { + "sr" : { "stringUnit" : { "state" : "translated", - "value" : "SSID" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "SSID" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "SSID" - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "שם רשת וויפי" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "SSID" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "SSID" - } - }, - "se" : { - "stringUnit" : { - "state" : "translated", - "value" : "SSID" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "SSID" + "value" : "Стање LED диоде (укључено/искључено)" } }, "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "SSID" + "value" : "LED 的狀態(開啟/關閉)" } } } }, - "standard" : { - "extractionState" : "migrated", + "The tertiary public key authorized to send admin messages to this node." : { "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Standard" + "value" : "Der dritte öffentliche Schlüssel, der berechtigt ist, Admin-Nachrichten an diesen Knoten zu senden." } }, - "en" : { + "sr" : { "stringUnit" : { "state" : "translated", - "value" : "Standard" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Standard" - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "סטנדרטי" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Standardowy" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Padrão" - } - }, - "se" : { - "stringUnit" : { - "state" : "translated", - "value" : "Standard" + "value" : "Терцијарни јавни кључ овлашћен за слање административних порука овом чвору." } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "标准" + "value" : "授权向该节点发送管理信息的三级管理员公钥。" } }, "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "標準" + "value" : "此節點允許發送管理訊息的第三個公鑰。" } } } }, - "standard.muted" : { - "extractionState" : "migrated", + "There has been no response to a request for device metadata over the admin channel for this node." : { "localizations" : { - "de" : { + "sr" : { "stringUnit" : { "state" : "translated", - "value" : "Standard Muted" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Standard Muted" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Standard en sourdine" - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "סטנדרתי-השתק" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Standardowy wyłączony" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Padrão Silenciado" - } - }, - "se" : { - "stringUnit" : { - "state" : "translated", - "value" : "Standard Muted" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "标准静音" + "value" : "Није било одговора на захтев за метаподатке уређаја преко административног канала за овај чвор." } }, "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "標準靜音" + "value" : "此節點透過管理通道要求裝置元數據,但未收到回覆。" } } } }, - "start" : { + "These settings will %@ channels. The current LoRa Config will be replaced, if there are substantial changes to the LoRa config the device will reboot" : { "localizations" : { - "de" : { + "sr" : { "stringUnit" : { "state" : "translated", - "value" : "Start" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Start" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Démarrer" - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "החל" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Start" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Iniciar" - } - }, - "se" : { - "stringUnit" : { - "state" : "translated", - "value" : "Start" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "开始" + "value" : "Ова подешавања ће %@ канале. Тренутна LoRA конфигурација ће бити замењена. Ако дође до значајних промена у LoRA конфигурацији, уређај ће се поново покренути." } }, "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "開始" + "value" : "這些設定將%@頻道。如果LoRa組態有重大變更,目前的LoRa組態將會被取代,並且設備將會重新啟動。" } } } }, - "State Broadcast Interval" : { - - }, - "Store and forward clients can request history from routers on the network." : { - - }, - "Store and forward router devices require a ESP32 device with PSRAM." : { - - }, - "storeforward" : { + "Thirty Minutes" : { "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Store & Forward" + "value" : "Dreißig Minuten" } }, - "en" : { + "sr" : { "stringUnit" : { "state" : "translated", - "value" : "Store & Forward" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Stocker et Transmettre" - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "שמירה ושליחה" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Store & Forward" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Armazenar e Encaminhar" - } - }, - "se" : { - "stringUnit" : { - "state" : "translated", - "value" : "Lagra & Videresänd" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "储存 & 转发" + "value" : "Тридесет минута" } }, "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "儲存 & 轉發" + "value" : "三十分鐘" } } } }, - "storeforward.config" : { + "This conversation will be deleted." : { "localizations" : { - "de" : { + "sr" : { "stringUnit" : { "state" : "translated", - "value" : "Store & Forward Config" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Store & Forward Config" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Configuration de Stocker et Transmettre" - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "הגדרות שמירה ושליחה" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Store & Forward Config" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Configuração de Armazenar e Encaminhar" - } - }, - "se" : { - "stringUnit" : { - "state" : "translated", - "value" : "Konfiguration för Lagra & Videresänd" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "储存 & 转发设置" + "value" : "Овај разговор ће бити обрисан." } }, "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "儲存 & 轉發設定" + "value" : "此對話將被刪除。" } } } }, - "storeforward.heartbeat" : { + "This could take a while, response will appear in the trace route log for the node it was sent to." : { "localizations" : { - "de" : { + "sr" : { "stringUnit" : { "state" : "translated", - "value" : "Send Heartbeat" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Send Heartbeat" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Envoyer une impulsion" - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "שלח דופק" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Send Heartbeat" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Enviar Batimento Cardíaco" - } - }, - "se" : { - "stringUnit" : { - "state" : "translated", - "value" : "Skicka hjärtslag" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "发送心跳包" + "value" : "Ово може потрајати. Одговор ће се појавити у евиденцији трасе праћења за чвор којем је послат." } }, "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "發送心跳包" + "value" : "這可能需要一些時間,回應將會出現在傳送到的節點的追蹤路由日誌中。" } } } }, - "subscribed" : { + "This could take a while. The response will appear in the trace route log for the node it was sent to." : { "localizations" : { - "de" : { + "sr" : { "stringUnit" : { "state" : "translated", - "value" : "Subscribed to mesh" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Subscribed to mesh" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Abonné au maillage" - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "מחובר למש" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Zapisano do siatki" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Inscrito no mesh" - } - }, - "se" : { - "stringUnit" : { - "state" : "translated", - "value" : "Prenumererar på mesh" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "连接到 Mesh 网络" + "value" : "Ово може потрајати. Одговор ће се појавити у евиденцији трасе праћења за чвор којем је послат." } }, "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "連接到 Mesh 網路" + "value" : "這可能需要一些時間,回應將會出現在傳送到的節點的追蹤路由日誌中。" + } + } + } + }, + "This determines the actual frequency you are transmitting on in the band. If set to 0 this value will be calculated automatically based on the primary channel name." : { + "extractionState" : "stale", + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ово одређује стварну фреквенцију на којој преносите у опсегу. Ако је постављено на 0, ова вредност ће се аутоматски израчунати на основу назива примарног канала." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "这决定了您在频段内的实际发射频率。如果设置为 0,该值将根据主频道名称自动计算。" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "這決定您在頻帶中實際傳輸的頻率。如果設定為 0,則此值將根據主要通道名稱自動計算。" + } + } + } + }, + "This device will send out range test messages on the selected interval." : { + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Овај уређај ће слати поруке за тестирање домета у одабраном интервалу." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "该设备将按所选时间间隔发送测距信息。" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "此裝置將會在選擇的時間間隔內發送範圍測試訊息。" + } + } + } + }, + "This message was likely not delivered." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Diese Nachricht wurde höchstwahrscheinlich nicht übermittelt." + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ова порука вероватно није била примљена." + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "此訊息可能未送達。" + } + } + } + }, + "This will disable fixed position and remove the currently set position." : { + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ово ће онемогућити фиксну позицију и уклонити тренутно постављену позицију." + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "這將停用固定位置並移除當前設定的位置。" + } + } + } + }, + "This will send a current position from your phone and enable fixed position." : { + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ово ће послати тренутну позицију са вашег телефона и омогућити фиксну позицију." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "这将从手机发送当前位置并启用固定位置。" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "這將從您的手機發送當前位置並啟用固定位置。" + } + } + } + }, + "Time" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zeit" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Време" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "時間" + } + } + } + }, + "Time Stamp" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zeitstempel" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Временски жиг" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "時間戳記" + } + } + } + }, + "Time Zone" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zeitzone" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Временска зона" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "時區" + } + } + } + }, + "Time zone for dates on the device screen and log." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zeitzone für Daten auf dem Gerätebildschirm und Log." + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Временска зона за датуме на екрану уређаја и у евиденцији." + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "裝置螢幕和日誌上的日期時區。" + } + } + } + }, + "timeout" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zeitlimit erreicht" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Timeout" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Délai d'expiration" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "זמן קצוב" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Limit czasu" + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tempo Limite" + } + }, + "se" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tidsgräns överskriden" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Временско ограничење" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "超时" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "超時" + } + } + } + }, + "timestamp" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zeitstempel" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Timestamp" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Horodatage" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "שעה/תאריך" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Znacznik czasu" + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "Carimbo de Data/Hora" + } + }, + "se" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tidsstämpel" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Временска ознака" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "时间戳" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "時間戳記" + } + } + } + }, + "Timing & Format" : { + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Време и формат" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "時機與格式" + } + } + } + }, + "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" : { + "stringUnit" : { + "state" : "translated", + "value" : "Admin Kanal erkannt: Wähle einen Knoten vom Dropdown aus um verbundene oder entfernte Geräte zu verwalten." + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Admin channel detected: Select a node from the drop down to manage connected or remote devices." + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Admin channel detected: Select a node from the drop down to manage connected or remote devices." + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "Admin channel detected: Select a node from the drop down to manage connected or remote devices." + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Admin channel detected: Select a node from the drop down to manage connected or remote devices." + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "Canal de administração detectado: Selecione um nó do menu suspenso para gerir dispositivos conectados ou remotos." + } + }, + "se" : { + "stringUnit" : { + "state" : "translated", + "value" : "Administratörskanal upptäckt: Välj en nod från rullgardinsmenyn för att hantera anslutna eller fjärranslutna enheter." + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Детектован админ канал: Изаберите чвор из падајућег менија да бисте управљали повезаним или удаљеним уређајима." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "检测到 admin 频道:请从下拉菜单中选择一个节点,来管理已连接或远程设备。" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "偵測到管理頻道:請從下拉式選單中選擇節點,以管理連接或遠端裝置。" + } + } + } + }, + "tip.channel.admin.title" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Admin Kanal" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Admin Channel" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Admin Channel" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "Admin Channel" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Admin Channel" + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "Canal de Administração" + } + }, + "se" : { + "stringUnit" : { + "state" : "translated", + "value" : "Administratörskanal" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Административни канал" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "admin 频道" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "管理頻道" + } + } + } + }, + "tip.channels.create.message" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Die meisten Daten in deinem Mesh werden über den primären Kanal gesendet. Du kannst sekundäre Kanäle einrichten, um zusätzliche Nachrichtengruppen zu erstellen, die durch ihren eigenen Schlüssel gesichert sind. [Tipps zur Kanalkonfiguration](https://meshtastic.org/docs/configuration/radio/channels/)" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Most data on your mesh is sent over the primary channel. You can set up secondary channels to create additional messaging groups secured by their own key. [Channel config tips](https://meshtastic.org/docs/configuration/tips/)" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "La pluspart des données de votre maillage sont envoyées sur le canal principal. Vous pouvez définir des canaux secondaires pour créer des groupes de messagerie additionnelle sécurisés avec leur propre clé. [Conseils de configuration du canal](https://meshtastic.org/docs/configuration/tips/)" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "Most data on your mesh is sent over the primary channel. You can set up secondary channels to create additional messaging groups secured by their own key. [Channel config tips](https://meshtastic.org/docs/configuration/radio/channels/)" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Most data on your mesh is sent over the primary channel. You can set up secondary channels to create additional messaging groups secured by their own key. [Channel config tips](https://meshtastic.org/docs/configuration/radio/channels/)" + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "A maioria dos dados na sua malha é enviada pelo canal principal. Você pode configurar canais secundários para criar grupos de mensagens adicionais protegidos por sua própria chave. [Channel config tips](https://meshtastic.org/docs/configuration/tips/)" + } + }, + "se" : { + "stringUnit" : { + "state" : "translated", + "value" : "De flesta data i ditt mesh-nätverk skickas över primärkanalen. Du kan ställa in sekundära kanaler för att skapa ytterligare meddelandegrupper skyddade av sin egen nyckel. Tips för kanalkonfiguration" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Већина података на вашој мрежи шаље се преко примарног канала. Можете подесити секундарне канале како бисте креирали додатне групе за размену порука, које су обезбеђене сопственим кључем. [Савети за конфигурацију канала](https://meshtastic.org/docs/configuration/tips/)" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mesh 网格上的大部分数据都通过主频道发送。您可以设置辅助频道以创建由其自身密钥保护的消息组。[频道配置提示](https://meshtastic.org/docs/configuration/tips/)" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "您的網狀網路上的大部分資料都是透過主要通道傳送的。您可以設定輔助通道以建立額外的訊息群組,並由其專屬的密鑰保護。[通道配置技巧](https://meshtastic.org/docs/configuration/tips/)" + } + } + } + }, + "tip.channels.create.title" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kanäle verwalten" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Manage Channels" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gérer les canaux" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "Manage Channels" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Manage Channels" + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gerir Canais" + } + }, + "se" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hantera Kanaler" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Управљај каналима" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "管理频道" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "管理頻道" + } + } + } + }, + "tip.channels.share.message" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "In a Meshtastic LoRa Mesh there are up to 8 channels. The first one is the Primary channel where most activity happens and is required. If you don't share your primary channel your first shared channel becomes the primary channel on the other network. It talks on its primary and your secondary channel. A channel with the name 'admin' controls nodes remotely. Other channels are for private groups, each with its own key." + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "A Meshtastic QR code contains the LoRa config and channel values needed for radios to communicate. You can share a complete channel configuration using the Replace Channels option, if you choose Add Channels your shared channels will be added to the channels on the receiving radio." + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Un code QR Meshtastic contient la configuration LoRa et les valeurs de canal nécessaires pour communiquer. La plupart des activités du maillage ont lieu sur le canal principal requis. Si vous ne partagez pas votre canal principal, votre premier canal partagé devient le canal principal de l’autre réseau. Les autres canaux sont pour les groupes privés, chacun avec sa propre clé." + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "במשטסטיק יש עד 8 ערוצים. הראשון הינו הראשי והינו היכן שרוב הפעילות מתבצעת והכרחי. אם לא תשתף את הערוץ הראשי שלך הערוץ הראשון שלך נהיה הערוץ הראשי ברשת השניה. הוא מדבר בערוץ הראשי שלו במשני שלך. ערוץ בעל השם 'admin' הינו לשליטה מרחוק. ערוצים נוספים הינם לקבוצות פרטיות, כל אחת עם מפתח הצפנה משלה." + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "In a Meshtastic LoRa Mesh there are up to 8 channels. The first one is the Primary channel where most activity happens and is required. If you don't share your primary channel your first shared channel becomes the primary channel on the other network. It talks on its primary and your secondary channel. A channel with the name 'admin' controls nodes remotely. Other channels are for private groups, each with its own key." + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "Um código QR Meshtastic contém a configuração LoRa e os valores do canal necessários para os rádios se comunicarem. Você pode compartilhar uma configuração completa do canal usando a opção Substituir Canais; se você escolher Adicionar Canais, seus canais compartilhados serão adicionados aos canais no rádio receptor." + } + }, + "se" : { + "stringUnit" : { + "state" : "translated", + "value" : "En Meshtastic QR-kod innehåller LoRa-konfigurationen och kanalvärden som behövs för kommunikation. De flesta aktiviteter i mesh-nätverket sker på den obligatoriska primärkanalen. Om du inte delar din primärkanal blir din första delade kanal primärkanalen på det andra nätverket. Andra kanaler är för privata grupper, varje med sin egen nyckel." + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "QR код за Мештастик садржи LoRA конфигурацију и вредности канала које су потребне радијима за комуникацију. Можете поделити потпуну конфигурацију канала користећи опцију „Замени канале“, а ако изаберете „Додај канале“, ваши делени канали ће бити додати каналима на примајућем радију." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "在 Meshtastic 网络中最多有 8 个频道。第一个频道是主频道,大多数活动都发生在这里,也是必需的。如果您不共享主频道,您的第一个共享频道就会成为其他网络的主频道。它会在其主频道和您的辅助频道上对话。名称为 admin 的频道可远程控制节点。其他频道用于私人群组,每个群组都有自己的密钥。" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "MeshTastic 二維條碼包含無線電通訊所需的 LoRa 組態和通道值。您可以使用「取代通道」選項分享完整的通道設定,如果您選擇「新增通道」,您的共享通道將會添加到接收無線電的通道中。" + } + } + } + }, + "tip.channels.share.title" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Meshtastic Kanäle teilen" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sharing Meshtastic Channels" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Partage des canaux Meshtastic" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "משתף ערוצי משטסטיק" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sharing Meshtastic Channels" + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "Compartilhando Canais Meshtastis" + } + }, + "se" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dela Meshtastic-kanaler" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Дељење Мештастик канала" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "共享 Meshtastic 频道" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "共享 Meshtastic 頻道" } } } @@ -20408,647 +36509,762 @@ }, "tapback" : { + "tip.messages.message" : { "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Tapback Response" + "value" : "Du kannst Kanalnachrichten (Gruppenchats) und Direktnachrichten senden und empfangen. Bei jeder Nachricht kannst du lange drücken, um verfügbare Aktionen wie Kopieren, Antworten, Tapback und Löschen sowie Zustelldetails anzuzeigen." } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Tapback Response" + "value" : "You can send and receive channel (group chats) and direct messages. From any message you can long press to see available actions like copy, reply, tapback and delete as well as delivery details." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Réponse de Tapback" + "value" : "Vous pouvez envoyer et recevoir des canaux (chats de groupe) et des messages directs. Depuis n’importe quel message, vous pouvez faire un appui long pour voir les actions possibles comme copier, répondre, tapback et supprimer ainsi que les détails de l'envoi." } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "תגובה מהירה" + "value" : "ניתן לשלוח הודעות ערוץ (קבוצות צ'אט) והודעות פרטיות. על הודעה ניתן לעשות לחיצה ארוכה בכדי לראות פעולות אפשריות כגון העתק, הגב, תגובה מהירה, מחק ובנוסף לראות מצב שליחה." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Odpowiedź na stuknięcie" + "value" : "You can send and receive channel (group chats) and direct messages. From any message you can long press to see available actions like copy, reply, tapback and delete as well as delivery details." } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Resposta Tapback" + "value" : "Você pode enviar e receber mensagens de canal (conversas em grupo) e mensagens diretas. De qualquer mensagem, você pode pressionar por um longo período para ver ações disponíveis como copiar, responder, tapback e excluir, bem como detalhes de entrega." } }, "se" : { "stringUnit" : { "state" : "translated", - "value" : "Svarsreaktion" + "value" : "Du kan skicka och ta emot kanalmeddelanden (gruppchatt) och direkta meddelanden. Från alla meddelanden kan du långtrycka för att se tillgängliga åtgärder som kopiera, svara, tapback och radera samt leveransdetaljer." + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Можете слати и примати поруке у каналима (групним четовима) и директне поруке. Из било које поруке можете дуго притиснути да бисте видели доступне радње као што су копирање, одговор, реакција и брисање, као и детаље о испоруци." } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "响应" + "value" : "您可以发送和接收群聊或私聊消息。在任何消息中,您都可以长按查看可用的操作,如复制、回复、拍一拍、删除以及投递详情。" } }, "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "響應" + "value" : "您可以發送和接收頻道(群組聊天)以及直接訊息。從任何訊息中,您可以長按以查看可用的動作,例如複製、回覆、快速回應和刪除,以及傳遞詳細資訊。" } } } }, - "tapback.exclamation" : { - "extractionState" : "migrated", + "tip.messages.title" : { "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Ausrufezeichen" + "value" : "Nachrichten" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Exclamation Mark" + "value" : "Messages" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Point d'exclamation" + "value" : "Messages" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "סימן קריאה" + "value" : "הודעות" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wykrzyknik" + "value" : "Messages" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Ponto de Exclamação" + "value" : "Mensagens" } }, "se" : { "stringUnit" : { "state" : "translated", - "value" : "Utropstecken" + "value" : "Meddelanden" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Поруке" } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "感叹号" + "value" : "消息" } }, "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "驚嘆號" + "value" : "消息" } } } }, - "tapback.haha" : { - "extractionState" : "migrated", + "TLS Enabled" : { "localizations" : { - "de" : { + "sr" : { "stringUnit" : { "state" : "translated", - "value" : "HaHa" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "HaHa" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "HaHa" - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "חחח" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "HaHa" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "HaHa" - } - }, - "se" : { - "stringUnit" : { - "state" : "translated", - "value" : "HaHa" + "value" : "TLS укључен" } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "哈哈" + "value" : "启用 TLS" } }, "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "哈哈" + "value" : "TLS 已啟用" } } } }, - "tapback.heart" : { - "extractionState" : "migrated", + "Topic: %@" : { + "extractionState" : "manual", "localizations" : { - "de" : { + "sr" : { "stringUnit" : { "state" : "translated", - "value" : "Gehört" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Heart" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Coeur" - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "לב" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Serce" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Coração" - } - }, - "se" : { - "stringUnit" : { - "state" : "translated", - "value" : "Hjärta" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "心" + "value" : "Тема: %@" } }, "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "心" + "value" : "主題:%@" } } } }, - "tapback.poop" : { - "extractionState" : "migrated", + "Total" : { "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Kacke" + "value" : "Total" } }, - "en" : { + "sr" : { "stringUnit" : { "state" : "translated", - "value" : "Poop" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Caca" - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "חרא" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Kupa" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Cocó" - } - }, - "se" : { - "stringUnit" : { - "state" : "translated", - "value" : "Bajs" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "便便" + "value" : "Укупно" } }, "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "便便" + "value" : "總計" } } } }, - "tapback.question" : { - "extractionState" : "migrated", + "Trace Route" : { "localizations" : { - "de" : { + "sr" : { "stringUnit" : { "state" : "translated", - "value" : "Fragezeichen" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Question Mark" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Point d'interrogation" - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "סימן שאלה" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Znak zapytania" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ponto de Interrogação" - } - }, - "se" : { - "stringUnit" : { - "state" : "translated", - "value" : "Frågetecken" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "问号" + "value" : "Праћење руте" } }, "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "問號" + "value" : "追蹤路線" } } } }, - "tapback.thumbsdown" : { - "extractionState" : "migrated", + "Trace Route Log" : { "localizations" : { - "de" : { + "sr" : { "stringUnit" : { "state" : "translated", - "value" : "Daumen runter" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Thumbs Down" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Pouce baissé" - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "אגודל למטה" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Kciuk w dół" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Polegar para Baixo" - } - }, - "se" : { - "stringUnit" : { - "state" : "translated", - "value" : "Tummen ner" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "倒大拇指" + "value" : "Лог праћења руте комуникације" } }, "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "倒大拇指" + "value" : "追蹤路線日誌" } } } }, - "tapback.thumbsup" : { - "extractionState" : "migrated", + "Trace route received directly by %@ with a SNR of %@ dB" : { + "extractionState" : "stale", "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Daumen hoch" - } - }, "en" : { "stringUnit" : { - "state" : "translated", - "value" : "Thumbs Up" + "state" : "new", + "value" : "Trace route received directly by %1$@ with a SNR of %2$@ dB" } }, - "fr" : { + "sr" : { "stringUnit" : { "state" : "translated", - "value" : "Pouce levé" - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "אגודל למעלה" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Kciuk w górę" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Polegar para Cima" - } - }, - "se" : { - "stringUnit" : { - "state" : "translated", - "value" : "Tummen upp" + "value" : "Захтев за праћење руте комуникације директно примљен од %1$@ са SNR од %2$@ dB." } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "竖大拇指" + "value" : "由 %1$@ 直接接收的跟踪路由,信噪比为 %2$@ dB" } }, "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "豎大拇指" + "value" : "追蹤路線已由 %1$@ 直接接收,信噪比為 %2$@ dB。" } } } }, - "tapback.wave" : { - "extractionState" : "migrated", + "Trace Route Sent" : { "localizations" : { - "de" : { + "sr" : { "stringUnit" : { "state" : "translated", - "value" : "Wave" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Wave" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Wave" - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "Wave" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Wave" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Adeus" - } - }, - "se" : { - "stringUnit" : { - "state" : "translated", - "value" : "Vinka" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "Wave" + "value" : "Захтев за праћење руте комуникације послат." } }, "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "招手" + "value" : "已發送追蹤路線" } } } }, - "telemetry" : { + "Trace route sent to %@" : { "localizations" : { - "de" : { + "sr" : { "stringUnit" : { "state" : "translated", - "value" : "Telemetrie (Sensoren)" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Telemetry (Sensors)" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Télémetrie (Capteurs)" - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "טלמטריה (חיישנים)" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Telemetria (czujniki)" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Telemetria (Sensores)" - } - }, - "se" : { - "stringUnit" : { - "state" : "translated", - "value" : "Telemetri (Sensorer)" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "遥测(传感器)" + "value" : "Захтев за праћење руте комуникације послат до %@." } }, "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "遠測(傳感器)" + "value" : "已發送追蹤路線至 %@" } } } }, - "telemetry.config" : { + "Trace route to %@ was not sent." : { "localizations" : { - "de" : { + "sr" : { "stringUnit" : { "state" : "translated", - "value" : "Telemetrie Einstellungen" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Telemetry Config" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Configuration de télémetrie" - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "הגדרות טלמטריה" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Konfiguracja telemetrii" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Configuração Telemetria" - } - }, - "se" : { - "stringUnit" : { - "state" : "translated", - "value" : "Telemetriinställningar" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "遥测配置" + "value" : "Захтев за праћење руте комуникације до %@ није послат." } }, "zh-Hant-TW" : { "stringUnit" : { "state" : "translated", - "value" : "遠側設定" + "value" : "追蹤路線至 %@ 尚未發送。" + } + } + } + }, + "Trace Route was rate limited. You can send a trace route a maximum of once every thirty seconds." : { + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Праћење руте комуникације је било ограничено по брзини. Можете послати захтев за праћење руте комуникације највише једном у сваких тридесет секунди." + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "追蹤路線請求受到速率限制。您最多只能每三十秒發送一次追蹤路線。" + } + } + } + }, + "Traffic" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Verkehr" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Саобраћај" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "流量" + } + } + } + }, + "Transmit data (txd) GPIO pin" : { + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "GPIO pin за трансмисију података (txd)" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "傳輸資料 (txd) GPIO 腳位" + } + } + } + }, + "Transmit Enabled" : { + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Трансмитер укључен" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "启用传输" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "傳輸已啟用" + } + } + } + }, + "Treat double tap on supported accelerometers as a user button press." : { + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Третирај двоструки додир на подржаним акцелераметрима као притисак корисничког дугмета." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "将支持双击的加速度计视为按下用户按钮。" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "將支援加速度感測器的雙擊視為使用者按鈕按下。" + } + } + } + }, + "TriggerType" : { + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Тип покретача" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "觸發類型" + } + } + } + }, + "Triple Click Ad Hoc Ping" : { + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Троструки клик за Ad Hoc пинг" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "三倍點擊臨時Ping" + } + } + } + }, + "Try Again" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Erneut versuchen" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Покушај поново" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "再試一次" + } + } + } + }, + "twitter" : { + "extractionState" : "manual", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Twitter" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Twitter" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Twitter" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "טוויטר" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Twitter" + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "Twitter" + } + }, + "se" : { + "stringUnit" : { + "state" : "translated", + "value" : "Twitter" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "X.com" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "Twitter" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "Twitter" + } + } + } + }, + "Two Hours" : { + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Два сата" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "兩小時" + } + } + } + }, + "ukraine.433mhz" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ukraine 433MHz" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Украјина 433MHz" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "烏克蘭 433MHz" + } + } + } + }, + "ukraine.868mhz" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ukraine 868MHz" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Украјина 868MHz" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "烏克蘭 868MHz" + } + } + } + }, + "Un-Favorite" : { + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Уклони са фаворита" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "從最愛中移除" + } + } + } + }, + "united.states" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "United States" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Сједињене Америчке државе" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "美國" + } + } + } + }, + "Units displayed on the device screen" : { + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Јединице приказане на екрану уређаја" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "裝置螢幕上顯示的單位" + } + } + } + }, + "unknown" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Unknown" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Unknown" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Inconnu" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "לא ידוע" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nieznany" + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "Desconhecido" + } + }, + "se" : { + "stringUnit" : { + "state" : "translated", + "value" : "Okänd" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Непознато" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "未知" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "未知" + } + } + } + }, + "unknown.age" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Unbekanntes alter" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Unknown Age" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Age inconnu" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "גיל לא ידוע" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nieznany wiek" + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "Idade Desconhecido" + } + }, + "se" : { + "stringUnit" : { + "state" : "translated", + "value" : "Okänd ålder" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Непозната старост" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "未知时间" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "未知時間" } } } @@ -21165,946 +37381,6 @@ }, "timeout" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Zeitlimit erreicht" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Timeout" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Délai d'expiration" - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "זמן קצוב" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Limit czasu" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Tempo Limite" - } - }, - "se" : { - "stringUnit" : { - "state" : "translated", - "value" : "Tidsgräns överskriden" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "超时" - } - }, - "zh-Hant-TW" : { - "stringUnit" : { - "state" : "translated", - "value" : "超時" - } - } - } - }, - "timestamp" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Timestamp" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Timestamp" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Horodatage" - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "שעה/תאריך" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Znacznik czasu" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Carimbo de Data/Hora" - } - }, - "se" : { - "stringUnit" : { - "state" : "translated", - "value" : "Tidsstämpel" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "时间戳" - } - }, - "zh-Hant-TW" : { - "stringUnit" : { - "state" : "translated", - "value" : "時間戳記" - } - } - } - }, - "Timing & Format" : { - - }, - "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." - } - }, - "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" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "连接到 LoRa 电台" - } - }, - "zh-Hant-TW" : { - "stringUnit" : { - "state" : "translated", - "value" : "連接到 LoRa 電台" - } - } - } - }, - "tip.channel.admin.message" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Admin channel detected: Select a node from the drop down to manage connected or remote devices." - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Admin channel detected: Select a node from the drop down to manage connected or remote devices." - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Admin channel detected: Select a node from the drop down to manage connected or remote devices." - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "Admin channel detected: Select a node from the drop down to manage connected or remote devices." - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Admin channel detected: Select a node from the drop down to manage connected or remote devices." - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Canal de administração detectado: Selecione um nó do menu suspenso para gerir dispositivos conectados ou remotos." - } - }, - "se" : { - "stringUnit" : { - "state" : "translated", - "value" : "Administratörskanal upptäckt: Välj en nod från rullgardinsmenyn för att hantera anslutna eller fjärranslutna enheter." - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "检测到 admin 频道:请从下拉菜单中选择一个节点,来管理已连接或远程设备。" - } - }, - "zh-Hant-TW" : { - "stringUnit" : { - "state" : "translated", - "value" : "偵測到管理頻道:從下拉選單中選擇一個節點來管理連接或遠端設備。" - } - } - } - }, - "tip.channel.admin.title" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Admin Channel" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Admin Channel" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Admin Channel" - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "Admin Channel" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Admin Channel" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Canal de Administração" - } - }, - "se" : { - "stringUnit" : { - "state" : "translated", - "value" : "Administratörskanal" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "admin 频道" - } - }, - "zh-Hant-TW" : { - "stringUnit" : { - "state" : "translated", - "value" : "管理頻道" - } - } - } - }, - "tip.channels.create.message" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Most data on your mesh is sent over the primary channel. You can set up secondary channels to create additional messaging groups secured by their own key. [Channel config tips](https://meshtastic.org/docs/configuration/radio/channels/)" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Most data on your mesh is sent over the primary channel. You can set up secondary channels to create additional messaging groups secured by their own key. [Channel config tips](https://meshtastic.org/docs/configuration/tips/)" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "La pluspart des données de votre maillage sont envoyées sur le canal principal. Vous pouvez définir des canaux secondaires pour créer des groupes de messagerie additionnelle sécurisés avec leur propre clé. [Conseils de configuration du canal](https://meshtastic.org/docs/configuration/tips/)" - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "Most data on your mesh is sent over the primary channel. You can set up secondary channels to create additional messaging groups secured by their own key. [Channel config tips](https://meshtastic.org/docs/configuration/radio/channels/)" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Most data on your mesh is sent over the primary channel. You can set up secondary channels to create additional messaging groups secured by their own key. [Channel config tips](https://meshtastic.org/docs/configuration/radio/channels/)" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "A maioria dos dados na sua malha é enviada pelo canal principal. Você pode configurar canais secundários para criar grupos de mensagens adicionais protegidos por sua própria chave. [Channel config tips](https://meshtastic.org/docs/configuration/tips/)" - } - }, - "se" : { - "stringUnit" : { - "state" : "translated", - "value" : "De flesta data i ditt mesh-nätverk skickas över primärkanalen. Du kan ställa in sekundära kanaler för att skapa ytterligare meddelandegrupper skyddade av sin egen nyckel. Tips för kanalkonfiguration" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "大多数 Mesh 上的数据都通过主要频道发送。您可以设置次要频道,以创建额外的消息组,并通过其自己的密钥进行安全保护。 [频道配置提示](https://meshtastic.org/docs/configuration/radio/channels/)" - } - }, - "zh-Hant-TW" : { - "stringUnit" : { - "state" : "translated", - "value" : "現在 Mesh 上的資料會通過主通道發送。您可以設定輔助通道來建立由自己的金鑰保護的其他訊息組 [頻道設定提示](https://meshtastic.org/docs/configuration/radio/channels/)" - } - } - } - }, - "tip.channels.create.title" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Manage Channels" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Manage Channels" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Gérer les canaux" - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "Manage Channels" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Manage Channels" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Gerir Canais" - } - }, - "se" : { - "stringUnit" : { - "state" : "translated", - "value" : "Hantera Kanaler" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "管理频道" - } - }, - "zh-Hant-TW" : { - "stringUnit" : { - "state" : "translated", - "value" : "管理頻道" - } - } - } - }, - "tip.channels.share.message" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "In a Meshtastic LoRa Mesh there are up to 8 channels. The first one is the Primary channel where most activity happens and is required. If you don't share your primary channel your first shared channel becomes the primary channel on the other network. It talks on its primary and your secondary channel. A channel with the name 'admin' controls nodes remotely. Other channels are for private groups, each with its own key." - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "A Meshtastic QR code contains the LoRa config and channel values needed for radios to communicate. You can share a complete channel configuration using the Replace Channels option, if you choose Add Channels your shared channels will be added to the channels on the receiving radio." - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Un code QR Meshtastic contient la configuration LoRa et les valeurs de canal nécessaires pour communiquer. La plupart des activités du maillage ont lieu sur le canal principal requis. Si vous ne partagez pas votre canal principal, votre premier canal partagé devient le canal principal de l’autre réseau. Les autres canaux sont pour les groupes privés, chacun avec sa propre clé." - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "במשטסטיק יש עד 8 ערוצים. הראשון הינו הראשי והינו היכן שרוב הפעילות מתבצעת והכרחי. אם לא תשתף את הערוץ הראשי שלך הערוץ הראשון שלך נהיה הערוץ הראשי ברשת השניה. הוא מדבר בערוץ הראשי שלו במשני שלך. ערוץ בעל השם 'admin' הינו לשליטה מרחוק. ערוצים נוספים הינם לקבוצות פרטיות, כל אחת עם מפתח הצפנה משלה." - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "In a Meshtastic LoRa Mesh there are up to 8 channels. The first one is the Primary channel where most activity happens and is required. If you don't share your primary channel your first shared channel becomes the primary channel on the other network. It talks on its primary and your secondary channel. A channel with the name 'admin' controls nodes remotely. Other channels are for private groups, each with its own key." - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Um código QR Meshtastic contém a configuração LoRa e os valores do canal necessários para os rádios se comunicarem. Você pode compartilhar uma configuração completa do canal usando a opção Substituir Canais; se você escolher Adicionar Canais, seus canais compartilhados serão adicionados aos canais no rádio receptor." - } - }, - "se" : { - "stringUnit" : { - "state" : "translated", - "value" : "En Meshtastic QR-kod innehåller LoRa-konfigurationen och kanalvärden som behövs för kommunikation. De flesta aktiviteter i mesh-nätverket sker på den obligatoriska primärkanalen. Om du inte delar din primärkanal blir din första delade kanal primärkanalen på det andra nätverket. Andra kanaler är för privata grupper, varje med sin egen nyckel." - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "在 Meshtastic 网络中最多有 8 个频道。第一个频道是主频道,大多数活动都发生在这里,也是必需的。如果您不共享主频道,您的第一个共享频道就会成为其他网络的主频道。它会在其主频道和您的辅助频道上对话。名称为 admin 的频道可远程控制节点。其他频道用于私人群组,每个群组都有自己的密钥。" - } - }, - "zh-Hant-TW" : { - "stringUnit" : { - "state" : "translated", - "value" : "在 Meshtastic 網路中最多有 8 個頻道。第一個頻道是主頻道,大多數活動都發生在這裡,也是必需的。如果您不共享主頻道,您的第一個共享頻道就會成為其他網路的主頻道。它會在其主頻道和您的輔助頻道上對話。名稱為 admin 的頻道可遠端控制中繼點。其他頻道用於私人群组,每個群組都有自己的密鑰。" - } - } - } - }, - "tip.channels.share.title" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Sharing Meshtastic Channels" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Sharing Meshtastic Channels" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Partage des canaux Meshtastic" - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "משתף ערוצי משטסטיק" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Sharing Meshtastic Channels" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Compartilhando Canais Meshtastis" - } - }, - "se" : { - "stringUnit" : { - "state" : "translated", - "value" : "Dela Meshtastic-kanaler" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "共享 Meshtastic 频道" - } - }, - "zh-Hant-TW" : { - "stringUnit" : { - "state" : "translated", - "value" : "共享 Meshtastic 頻道" - } - } - } - }, - "tip.messages.message" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "You can send and receive channel (group chats) and direct messages. From any message you can long press to see available actions like copy, reply, tapback and delete as well as delivery details." - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "You can send and receive channel (group chats) and direct messages. From any message you can long press to see available actions like copy, reply, tapback and delete as well as delivery details." - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Vous pouvez envoyer et recevoir des canaux (chats de groupe) et des messages directs. Depuis n’importe quel message, vous pouvez faire un appui long pour voir les actions possibles comme copier, répondre, tapback et supprimer ainsi que les détails de l'envoi." - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "ניתן לשלוח הודעות ערוץ (קבוצות צ'אט) והודעות פרטיות. על הודעה ניתן לעשות לחיצה ארוכה בכדי לראות פעולות אפשריות כגון העתק, הגב, תגובה מהירה, מחק ובנוסף לראות מצב שליחה." - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "You can send and receive channel (group chats) and direct messages. From any message you can long press to see available actions like copy, reply, tapback and delete as well as delivery details." - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Você pode enviar e receber mensagens de canal (conversas em grupo) e mensagens diretas. De qualquer mensagem, você pode pressionar por um longo período para ver ações disponíveis como copiar, responder, tapback e excluir, bem como detalhes de entrega." - } - }, - "se" : { - "stringUnit" : { - "state" : "translated", - "value" : "Du kan skicka och ta emot kanalmeddelanden (gruppchatt) och direkta meddelanden. Från alla meddelanden kan du långtrycka för att se tillgängliga åtgärder som kopiera, svara, tapback och radera samt leveransdetaljer." - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "您可以发送和接收直频消息和群聊。在任何信息中,您都可以长按查看可用的操作,如复制、回复、拍一拍、删除以及详情。" - } - }, - "zh-Hant-TW" : { - "stringUnit" : { - "state" : "translated", - "value" : "您可以發送和接收1對1聊天和群聊。在任何訊息中,您都可以長按查看可用的操作,如複製、回復、拍一拍、刪除以及詳情。" - } - } - } - }, - "tip.messages.title" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Messages" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Messages" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Messages" - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "הודעות" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Messages" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Mensagens" - } - }, - "se" : { - "stringUnit" : { - "state" : "translated", - "value" : "Meddelanden" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "消息" - } - }, - "zh-Hant-TW" : { - "stringUnit" : { - "state" : "translated", - "value" : "消息" - } - } - } - }, - "TLS Enabled" : { - - }, - "Total" : { - - }, - "Trace Route" : { - - }, - "Trace Route Log" : { - - }, - "Trace route received directly by %@ with a SNR of %@ dB" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "Trace route received directly by %1$@ with a SNR of %2$@ dB" - } - } - } - }, - "Trace Route Sent" : { - - }, - "Trace route sent to %@" : { - - }, - "Trace route to %@ was not sent." : { - - }, - "Trace Route was rate limited. You can send a trace route a maximum of once every thirty seconds." : { - - }, - "Tracker" : { - - }, - "Traffic" : { - - }, - "Transmit data (txd) GPIO pin" : { - - }, - "Transmit Enabled" : { - - }, - "Treat double tap on supported accelerometers as a user button press." : { - - }, - "TriggerType" : { - - }, - "Triple Click Ad Hoc Ping" : { - - }, - "Try Again" : { - - }, - "twitter" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Twitter" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Twitter" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Twitter" - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "טוויטר" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Twitter" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Twitter" - } - }, - "se" : { - "stringUnit" : { - "state" : "translated", - "value" : "Twitter" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "Twitter" - } - }, - "zh-Hant-TW" : { - "stringUnit" : { - "state" : "translated", - "value" : "Twitter" - } - } - } - }, - "Two Hours" : { - - }, - "Un-Favorite" : { - - }, - "Units displayed on the device screen" : { - - }, - "unknown" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Unknown" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Unknown" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Inconnu" - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "לא ידוע" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nieznany" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Desconhecido" - } - }, - "se" : { - "stringUnit" : { - "state" : "translated", - "value" : "Okänd" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "未知" - } - }, - "zh-Hant-TW" : { - "stringUnit" : { - "state" : "translated", - "value" : "未知" - } - } - } - }, - "unknown.age" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Unbekanntes alter" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Unknown Age" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Age inconnu" - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "גיל לא ידוע" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nieznany wiek" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Idade Desconhecido" - } - }, - "se" : { - "stringUnit" : { - "state" : "translated", - "value" : "Okänd ålder" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "未知时间" - } - }, - "zh-Hant-TW" : { - "stringUnit" : { - "state" : "translated", - "value" : "未知時間" - } - } - } - }, "unset" : { "localizations" : { "de" : { @@ -22149,6 +37425,12 @@ "value" : "Återställ" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Уклони" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -22164,20 +37446,65 @@ } }, "Unsupported" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Није подржано" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "不支援" + } + } + } }, "Up Down 1" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Горе Доле 1" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "上下一次" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "上傳下載 1" + } + } + } }, "Update Interval" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Интервал ажурирања" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "更新間隔" + } + } + } }, "update.firmware" : { "localizations" : { "de" : { "stringUnit" : { "state" : "translated", - "value" : "Update Your Firmware" + "value" : "Firmware aktualisieren" } }, "en" : { @@ -22216,6 +37543,12 @@ "value" : "Uppdatera din firmware" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ажурирај твој фирмвер" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -22235,7 +37568,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Update intervall" + "value" : "Aktualisierungsintervall" } }, "en" : { @@ -22274,6 +37607,12 @@ "value" : "Uppdateringsintervall" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Интервал ажурирања" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -22289,13 +37628,64 @@ } }, "Updated Node Stats Data." : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ажурирани подаци о статистици чвора." + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "更新節點統計資料。" + } + } + } }, "Updated: %@" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aktualisiert: %@" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ажуриран: %@" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "更新時間: %@" + } + } + } }, "Uplink Enabled" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Укључен узлазни канал" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "启用上传" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "上行鏈路已啟用" + } + } + } }, "uptime" : { "localizations" : { @@ -22322,23 +37712,102 @@ "state" : "translated", "value" : "Drifttid" } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Време рада" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "正常運行時間" + } } } }, "Use a PWM output (like the RAK Buzzer) for tunes instead of an on/off output. This will ignore the output, output duration and active settings and use the device config buzzer GPIO option instead." : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Користи PWM излаз (као што је RAK звучник) за мелодије уместо укључивања/искључивања излаза. Ово ће игнорисати подешавања излаза, трајање излаза и активна подешавања и користити подешавање GPIO опције звучника у конфигурацији уређаја." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "使用 PWM 输出(如 RAK 蜂鸣器)代替开/关输出进行调谐。这将忽略输出、输出持续时间和激活设置,而使用设备配置蜂鸣器 GPIO 选项。" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "使用 PWM 輸出(例如 RAK Buzzer)來播放旋律,而不是開關輸出。這將忽略輸出、輸出持續時間和活動設定,並改用設備配置蜂鳴器 GPIO 選項。" + } + } + } }, "Use I2S As Buzzer" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Користи I2S као звучник" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "使用 I2S 作為蜂鳴器" + } + } + } }, "Use Preset" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Користи предефинисано подешавање" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "使用预设" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "使用預設設定" + } + } + } }, "Use PWM Buzzer" : { - - }, - "Used to create a shared key with a remote device." : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Користи PWM звучник" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "使用 PWM 蜂鸣器" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "使用 PWM 蜂鳴器" + } + } + } }, "user" : { "localizations" : { @@ -22384,6 +37853,12 @@ "value" : "Användare" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Корисник" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -22399,13 +37874,93 @@ } }, "User Config" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Корисничка подешавања" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "用户配置" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "使用者組態" + } + } + } }, "User Details" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Кориснички детаљи" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "用户信息" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "使用者詳細資料" + } + } + } }, "User Id" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "ИД корисника" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "用户 ID" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "使用者 ID" + } + } + } + }, + "User Initiated Disconnect" : { + "extractionState" : "manual", + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Корисник је покренуо прекид везе" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "用户主动断开连接" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "使用者發起斷線" + } + } + } }, "user.details" : { "extractionState" : "manual", @@ -22452,6 +38007,12 @@ "value" : "Användaruppgifter" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Кориснички детаљи" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -22467,45 +38028,221 @@ } }, "Uses pullup resistor" : { - - }, - "Utilizes the network connection on your phone to connect to MQTT." : { - - }, - "Vehicle heading" : { - - }, - "Vehicle speed" : { - - }, - "Version %@ includes breaking changes to devices and the client apps. Only nodes version %@ and above are supported." : { "localizations" : { - "en" : { + "sr" : { "stringUnit" : { - "state" : "new", - "value" : "Version %1$@ includes breaking changes to devices and the client apps. Only nodes version %2$@ and above are supported." + "state" : "translated", + "value" : "Користи pull-up отпорник" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "使用上拉電阻" } } } }, - "Version 1.2 End of life (EOL) Info" : { - + "Utilizes the network connection on your phone to connect to MQTT." : { + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Користи мрежну везу на вашем телефону за повезивање са MQTT." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "利用手机上的网络连接到 MQTT。" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "利用手機上的網路連線來連接到 MQTT。" + } + } + } + }, + "Vehicle heading" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Fahrzeugsteuerkurs" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Правац возила" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "車輛方向" + } + } + } + }, + "Vehicle speed" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Fahrzeuggeschwindigkeit" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Брзина возила" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "車輛速度" + } + } + } + }, + "Version %@ includes substantial network optimizations and extensive changes to devices and client apps. Only nodes version %@ and above are supported." : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Version %1$@ includes substantial network optimizations and extensive changes to devices and client apps. Only nodes version %2$@ and above are supported." + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Верзија %1$@ укључује значајне оптимизације мреже и обимне измене уређаја и клијентских апликација. Подржане су само верзије чворова %2$@ и новије." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "版本 %1$@ 包括大量网络优化以及对设备和客户端应用程序的广泛更改。仅支持 %2$@ 及以上版本的节点。" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "版本 %1$@ 包含了大幅的網路優化,以及對裝置和用戶端應用程式的廣泛修改。僅支援版本 %2$@ 或以上的節點。" + } + } + } }, "Version: %@ (%@) " : { "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Version: %1$@ (%2$@) " + } + }, "en" : { "stringUnit" : { "state" : "new", "value" : "Version: %1$@ (%2$@) " } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Верзија: %1$@ (%2$@) " + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "版本号: %1$@ (%2$@) " + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "版本: %1$@ (%2$@)" + } + } + } + }, + "very.long.range.slow" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Very Long Range - Slow" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Веома дугачки домет - Споро" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "超長距離 - 慢速" + } } } }, "Via Lora" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Via Lora" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Преко LoRA" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "透過 LoRa" + } + } + } }, "Via Mqtt" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Via Mqtt" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Преко MQTT-а" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "通过 MQTT" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "透過 MQTT" + } + } + } }, "voltage" : { "localizations" : { @@ -22551,6 +38288,12 @@ "value" : "Spänning" } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Напон" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -22566,8 +38309,65 @@ } }, "Volts %@ " : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Volt %@" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Волти %@" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "電壓 %@" + } + } + } + }, + "Trace Route Sent" : { }, + "Trace route sent to %@" : { + + }, + "Trace route to %@ was not sent." : { + + }, + "Trace Route was rate limited. You can send a trace route a maximum of once every thirty seconds." : { + + }, + "Tracker" : { + + }, + "Traffic" : { + + }, + "Transmit data (txd) GPIO pin" : { + + }, + "Transmit Enabled" : { + + }, + "Treat double tap on supported accelerometers as a user button press." : { + + }, + "TriggerType" : { + + }, + "Triple Click Ad Hoc Ping" : { + + }, + "Try Again" : { + + }, + "twitter" : { + "extractionState" : "manual", "waiting" : { "localizations" : { "de" : { @@ -22612,6 +38412,12 @@ "value" : "Väntar..." } }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Чекам. . ." + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -22627,49 +38433,356 @@ } }, "Waiting to be acknowledged. . ." : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Чека се на потврду пријема..." + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "等待確認中..." + } + } + } }, "Wake Screen on tap or motion" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Пробуди екран додиром или покретом" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "觸碰或移動喚醒螢幕" + } + } + } }, "Waypoint Options" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wegpunktoptionen" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Опције за тачке пута" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "航點選項" + } + } + } }, "Weather Conditions" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wetterverhältnisse" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Временски услови" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "天气状况" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "氣象狀況" + } + } + } }, "Web Flasher" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Веб флашер" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "Web 閃爍器" + } + } + } }, "Website" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Вебсајт" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "网站" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "網站" + } + } + } }, "What does the lock mean?" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Was bedeutet das Schloß?" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Шта значи закључавање?" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "锁意味着什么?" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "鎖頭代表什麼意思?" + } + } + } }, "What is Meshtastic?" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Was ist Meshtastic?" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Шта је Мештастик?" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "什么是 Meshtastic?" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "什麼是 Meshtastic?" + } + } + } }, "What licensed operator mode does:\n* Sets the node name to your call sign \n* Broadcasts node info every 10 minutes \n* Overrides frequency, dutycycle and tx power \n* Disables encryption" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Шта ради режим лиценцираног оператера:\n* Поставља име чвора на ваш позивни знак\n* Емитује информације о чвору сваких 10 минута\n* Превазилази фреквенцију, циклус рада и излазну снагу\n* Онемогућава енкрипцију" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "业余无线电模式的作用:\n* 将节点名称设置为您的呼号 \n* 每 10 分钟广播一次节点信息 \n* 覆盖频率、占空比和发射功率 \n* 禁用加密" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "取得執照的作業員模式的作用如下:\n\n* 將節點名稱設定為您的呼號。\n* 每 10 分鐘廣播一次節點資訊。\n* 覆寫頻率、工作週期和傳輸功率。\n* 禁用加密。" + } + } + } }, "When using in GPIO mode, keep the output on for this long. " : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Када користите у GPIO режиму, задржите излаз укључен овако дуго." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "在 GPIO 模式下使用时,请将输出保持接通这么长时间。" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "使用 GPIO 模式時,維持輸出開啟的時間長度。" + } + } + } }, "WiFi Options" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "WiFi Optionen" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Опције вајфаја" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wi-Fi 選項" + } + } + } }, "WIND" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "WIND" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "ВЕТАР" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "無線網路侦测程式" + } + } + } }, "Wind Direction" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Windrichtung" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Правац ветра" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "風向" + } + } + } }, "Wind Speed" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Windgeschwindigkeit" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Брзина ветра" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "無線網路侦测程式速度" + } + } + } + }, + "Wird verwendet, um einen gemeinsamen Schlüssel mit einem entfernten Gerät zu erstellen." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Verbunden mit einem Knoten" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Користи се за креирање заједничког кључа са удаљеним уређајем." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "用于与远程设备创建共享密钥。" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "用於與遠端設備建立共享密鑰。" + } + } + } }, "x" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "x" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "x" + } + } + } }, "X: %@, Y: %d" : { "localizations" : { @@ -22678,6 +38791,24 @@ "state" : "new", "value" : "X: %1$@, Y: %2$d" } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "X: %1$@, Y: %2$d" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "X: %1$@, Y: %2$d" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "X: %1$@,Y: %2$d" + } } } }, @@ -22688,6 +38819,24 @@ "state" : "new", "value" : "X: %1$@, Y: %2$f" } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "X: %1$@, Y: %2$f" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "X: %1$@, Y: %2$f" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "X: %1$@,Y: %2$f" + } } } }, @@ -22698,38 +38847,261 @@ "state" : "new", "value" : "X: %1$@, Y: %2$lld" } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "X: %1$@, Y: %2$lld" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "X: %1$@, Y: %2$lld" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "X: %1$@,Y: %2$lld" + } } } }, "y" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "y" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "y" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "y" + } + } + } }, "Yesterday" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gestern" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Јуче" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "昨天" + } + } + } }, "You can also update your Meshtastic device over bluetooth using the Nordic DFU app." : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Такође можете ажурирати свој Мештастик уређај преко блутута користећи Nordic DFU апликацију." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "您还可以使用 Nordic DFU 应用程序通过蓝牙更新 Meshtastic 设备。" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "您也可以使用 Nordic DFU 應用程式透過藍牙更新您的 Meshtastic 裝置。" + } + } + } }, "Your current location will be set as the fixed position and broadcast over the mesh on the position interval." : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ваша тренутна позиција ће бити постављена као фиксна позиција и емитована преко мреже на интервалу позиције." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "您当前的位置将被设置为固定位置,并以定位间隔向 Mesh 网络广播。" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "您的當前位置將會被設定為固定位置,並在位置間隔期間透過網格廣播。" + } + } + } }, "Your Firmware is up to date" : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Deine Firmware ist aktuell" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ваш фирмвер је на најновијој верзији" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "你的固件已经是最新版本" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "您的韌體是最新的" + } + } + } + }, + "Your MQTT Server must support TLS." : { + "localizations" : { + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "您的 MQTT 伺服器必須支援 TLS。" + } + } + } }, "Your MQTT Server must support TLS. Not available via the public mqtt server." : { - + "extractionState" : "stale", + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ваш MQTT сервер мора подржавати TLS. Није доступно преко јавног MQTT сервера." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "您的 MQTT 服务器必须支持 TLS。没有可用的公开 MQTT 服务器。" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "您的 MQTT 伺服器必須支援 TLS。公眾 MQTT 伺服器不提供此功能。" + } + } + } + }, + "Your node’s operating frequency is calculated based on the region, modem preset, and this field. When 0, the slot is automatically calculated based on the primary channel name." : { + "localizations" : { + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "您的節點操作頻率會根據區域、調製解調器預設值以及此欄位進行計算。當為 0 時,時隙會自動根據主要通道名稱進行計算。" + } + } + } }, "Your position has been sent with a request for a response with their position. You will receive a notification when a position is returned." : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ваша позиција је послата са захтевом за одговор са њиховом позицијом. Добићете обавештење када се позиција врати." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "您的位置已发送,并请求对方回复其位置。位置返回后,您将收到通知。" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "您的位置已發送,並附帶了請求對方回傳其位置。當收到位置資訊時,您將會收到通知。" + } + } + } }, "Your region has a %lld%% duty cycle. MQTT is not advised when you are duty cycle restricted, the extra traffic will quickly overwhelm your LoRa mesh." : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ваш регион има %lld%% циклус рада. MQTT се не препоручује када сте ограничени циклусом рада, јер ће додатни саобраћај брзо преоптеретити вашу LoRa мрежу." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "您所在地区的占空比为 %lld%%。在占空比受限的情况下,不建议使用 MQTT,因为额外的流量会很快压垮您的 LoRa 网格。" + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "您的地區有 %lld%% 的工作週期。當您受到工作週期限制時,不建議使用 MQTT,額外的流量將會迅速淹沒您的 LoRa 網路。" + } + } + } }, "Your region has a %lld%% hourly duty cycle, your radio will stop sending packets when it reaches the hourly limit." : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ваш регион има %lld%% радни циклус по сату, ваш радио ће престати да шаље пакете када достигне ограничење по сату." + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "您的地區每小時工作週期為 %lld%%,當無線電達到每小時限制時,將停止傳送封包。" + } + } + } }, "Your route file must have both Latitude and Longitude columns and headers." : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ваша датотека руте мора имати колоне и заглавља и ширину и дужину." + } + }, + "zh-Hant-TW" : { + "stringUnit" : { + "state" : "translated", + "value" : "您的路線檔案必須包含緯度和經度欄位以及標頭。" + } + } + } } }, "version" : "1.0" diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 1fafb26c..47b3c13f 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -7,6 +7,13 @@ objects = { /* Begin PBXBuildFile section */ + 231B3F212D087A4C0069A07D /* MetricTableColumn.swift in Sources */ = {isa = PBXBuildFile; fileRef = 231B3F202D087A4C0069A07D /* MetricTableColumn.swift */; }; + 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 */; }; + 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 */; }; 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 */; }; @@ -30,6 +37,8 @@ 6DA39D8E2A92DC52007E311C /* MeshtasticAppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DA39D8D2A92DC52007E311C /* MeshtasticAppDelegate.swift */; }; 6DEDA55A2A957B8E00321D2E /* DetectionSensorLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DEDA5592A957B8E00321D2E /* DetectionSensorLog.swift */; }; 6DEDA55C2A9592F900321D2E /* MessageEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DEDA55B2A9592F900321D2E /* MessageEntityExtension.swift */; }; + 8D3F8A3F2D44BB02009EAAA4 /* PowerMetrics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D3F8A3E2D44BB02009EAAA4 /* PowerMetrics.swift */; }; + 8D3F8A412D44C2A6009EAAA4 /* PowerMetricsLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D3F8A402D44C2A6009EAAA4 /* PowerMetricsLog.swift */; }; B399E8A42B6F486400E4488E /* RetryButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = B399E8A32B6F486400E4488E /* RetryButton.swift */; }; B3E905B12B71F7F300654D07 /* TextMessageField.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3E905B02B71F7F300654D07 /* TextMessageField.swift */; }; BC47C2EF2CE0017D008245CA /* MessageNodeIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC47C2EE2CE0017D008245CA /* MessageNodeIntent.swift */; }; @@ -174,7 +183,6 @@ DDC2E15F26CE248F0042C5E4 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DDC2E15E26CE248F0042C5E4 /* Preview Assets.xcassets */; }; DDC2E18F26CE25FE0042C5E4 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC2E18E26CE25FE0042C5E4 /* ContentView.swift */; }; DDC2E1A726CEB3400042C5E4 /* LocationHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC2E1A626CEB3400042C5E4 /* LocationHelper.swift */; }; - DDC3B274283F411B00AC321C /* LastHeardText.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC3B273283F411B00AC321C /* LastHeardText.swift */; }; DDC4C9FF2A8D982900CE201C /* DetectionSensorConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC4C9FE2A8D982900CE201C /* DetectionSensorConfig.swift */; }; DDC4D568275499A500A4208E /* Persistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC4D567275499A500A4208E /* Persistence.swift */; }; DDC94FC129CE063B0082EA6E /* BatteryLevel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC94FC029CE063B0082EA6E /* BatteryLevel.swift */; }; @@ -218,6 +226,7 @@ DDDE5A1129AFE69700490C6C /* MeshActivityAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDDE5A0F29AFE69700490C6C /* MeshActivityAttributes.swift */; }; DDDE5A1329AFEAB900490C6C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DDDE5A1229AFEAB900490C6C /* Assets.xcassets */; }; DDDE5A1429AFEAB900490C6C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DDDE5A1229AFEAB900490C6C /* Assets.xcassets */; }; + DDDFE73F2D0D48FF0044463C /* IgnoreNodeButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDDFE73E2D0D48FF0044463C /* IgnoreNodeButton.swift */; }; DDE0F7C5295F77B700B8AAB3 /* AppSettingsEnums.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE0F7C4295F77B700B8AAB3 /* AppSettingsEnums.swift */; }; DDE5B4042B2279A700FCDD05 /* TraceRouteLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE5B4032B2279A700FCDD05 /* TraceRouteLog.swift */; }; DDE9659C2B1C3B6A00531070 /* RouteRecorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE9659B2B1C3B6A00531070 /* RouteRecorder.swift */; }; @@ -261,6 +270,13 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 231B3F1F2D087A4C0069A07D /* MetricsColumnList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetricsColumnList.swift; sourceTree = ""; }; + 231B3F202D087A4C0069A07D /* MetricTableColumn.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetricTableColumn.swift; sourceTree = ""; }; + 231B3F242D087C3C0069A07D /* EnvironmentDefaultColumns.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnvironmentDefaultColumns.swift; sourceTree = ""; }; + 231B3F262D0885240069A07D /* MetricsColumnDetail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetricsColumnDetail.swift; sourceTree = ""; }; + 2373AE122D0A216C0086C749 /* MetricsChartSeries.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetricsChartSeries.swift; sourceTree = ""; }; + 2373AE142D0A24930086C749 /* MetricsSeriesList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetricsSeriesList.swift; sourceTree = ""; }; + 2373AE162D0A26620086C749 /* EnviornmentDefaultSeries.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnviornmentDefaultSeries.swift; sourceTree = ""; }; 251926842C3BA97800249DF5 /* FavoriteNodeButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoriteNodeButton.swift; sourceTree = ""; }; 251926862C3BAE2200249DF5 /* NodeAlertsButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeAlertsButton.swift; sourceTree = ""; }; 251926892C3BB1B200249DF5 /* ExchangePositionsButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExchangePositionsButton.swift; sourceTree = ""; }; @@ -278,6 +294,9 @@ 6DA39D8D2A92DC52007E311C /* MeshtasticAppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshtasticAppDelegate.swift; sourceTree = ""; }; 6DEDA5592A957B8E00321D2E /* DetectionSensorLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetectionSensorLog.swift; sourceTree = ""; }; 6DEDA55B2A9592F900321D2E /* MessageEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageEntityExtension.swift; sourceTree = ""; }; + 8D3F8A3D2D44B137009EAAA4 /* MeshtasticDataModelV 49.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 49.xcdatamodel"; sourceTree = ""; }; + 8D3F8A3E2D44BB02009EAAA4 /* PowerMetrics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PowerMetrics.swift; sourceTree = ""; }; + 8D3F8A402D44C2A6009EAAA4 /* PowerMetricsLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PowerMetricsLog.swift; sourceTree = ""; }; B399E8A32B6F486400E4488E /* RetryButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RetryButton.swift; sourceTree = ""; }; B3E905B02B71F7F300654D07 /* TextMessageField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextMessageField.swift; sourceTree = ""; }; BC47C2EE2CE0017D008245CA /* MessageNodeIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageNodeIntent.swift; sourceTree = ""; }; @@ -291,6 +310,10 @@ BCE2D3C42C7AE369008E6199 /* RestartNodeIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RestartNodeIntent.swift; sourceTree = ""; }; BCE2D3C62C7B0D0A008E6199 /* ShortcutsProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShortcutsProvider.swift; sourceTree = ""; }; BCE2D3C82C7C377F008E6199 /* FactoryResetNodeIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FactoryResetNodeIntent.swift; sourceTree = ""; }; + D32BA3912D54617800714840 /* NodeInfoEntity+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NodeInfoEntity+CoreDataClass.swift"; sourceTree = ""; }; + D32BA3922D54617800714840 /* NodeInfoEntity+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NodeInfoEntity+CoreDataProperties.swift"; sourceTree = ""; }; + D32BA3932D54617800714840 /* UserEntity+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserEntity+CoreDataClass.swift"; sourceTree = ""; }; + D32BA3942D54617800714840 /* UserEntity+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserEntity+CoreDataProperties.swift"; sourceTree = ""; }; D93068D22B8129510066FBC8 /* MessageContextMenuItems.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageContextMenuItems.swift; sourceTree = ""; }; D93068D42B812B700066FBC8 /* MessageDestination.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageDestination.swift; sourceTree = ""; }; D93068D62B8146690066FBC8 /* MessageText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageText.swift; sourceTree = ""; }; @@ -420,6 +443,7 @@ DD9A1A912BA2D2D3001E602E /* MeshtasticDataModelV 30.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 30.xcdatamodel"; sourceTree = ""; }; DDA0B6B1294CDC55001356EC /* Channels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Channels.swift; sourceTree = ""; }; DDA1C48D28DB49D3009933EC /* ChannelRoles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelRoles.swift; sourceTree = ""; }; + DDA28B1B2D32C89200EF726F /* MeshtasticDataModelV 48.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 48.xcdatamodel"; sourceTree = ""; }; DDA6B2E828419CF2003E8C16 /* MeshPackets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshPackets.swift; sourceTree = ""; }; DDA951592BC6624100CEA535 /* TelemetryWeather.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelemetryWeather.swift; sourceTree = ""; }; DDA9515B2BC6631200CEA535 /* TelemetryEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TelemetryEnums.swift; sourceTree = ""; }; @@ -459,7 +483,6 @@ DDC2E16526CE248F0042C5E4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; DDC2E18E26CE25FE0042C5E4 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; DDC2E1A626CEB3400042C5E4 /* LocationHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationHelper.swift; sourceTree = ""; }; - DDC3B273283F411B00AC321C /* LastHeardText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LastHeardText.swift; sourceTree = ""; }; DDC4C9FE2A8D982900CE201C /* DetectionSensorConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetectionSensorConfig.swift; sourceTree = ""; }; DDC4CA012A8DAA3800CE201C /* MeshtasticDataModelV16.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV16.xcdatamodel; sourceTree = ""; }; DDC4D567275499A500A4208E /* Persistence.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Persistence.swift; sourceTree = ""; }; @@ -469,6 +492,7 @@ DDCDC69A29467643004C1DDA /* MeshtasticDataModelV3.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV3.xcdatamodel; sourceTree = ""; }; DDCE4E2B2869F92900BE9F8F /* UserConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserConfig.swift; sourceTree = ""; }; DDD28D372C0CD2670063CFA3 /* MeshtasticDataModelV 37.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 37.xcdatamodel"; sourceTree = ""; }; + DDD3A2B22D5127CF0045EB48 /* ci_pre_xcodebuild.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = ci_pre_xcodebuild.sh; sourceTree = ""; }; DDD43FE22A78C8900083A3E9 /* MqttClientProxyManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MqttClientProxyManager.swift; sourceTree = ""; }; DDD5BB082C285DDC007E03CA /* AppLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLog.swift; sourceTree = ""; }; DDD5BB0A2C285E45007E03CA /* LogDetail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogDetail.swift; sourceTree = ""; }; @@ -512,6 +536,8 @@ DDDE5A0F29AFE69700490C6C /* MeshActivityAttributes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshActivityAttributes.swift; sourceTree = ""; }; DDDE5A1229AFEAB900490C6C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; DDDEE5E229DBE43E00A8E078 /* MeshtasticDataModelV11.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV11.xcdatamodel; sourceTree = ""; }; + DDDFE73E2D0D48FF0044463C /* IgnoreNodeButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IgnoreNodeButton.swift; sourceTree = ""; }; + DDDFE7402D0D4A070044463C /* MeshtasticDataModelV 47.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 47.xcdatamodel"; sourceTree = ""; }; DDE0F7C4295F77B700B8AAB3 /* AppSettingsEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettingsEnums.swift; sourceTree = ""; }; DDE5B4032B2279A700FCDD05 /* TraceRouteLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TraceRouteLog.swift; sourceTree = ""; }; DDE5B4052B227E3200FCDD05 /* TraceRouteEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TraceRouteEntityExtension.swift; sourceTree = ""; }; @@ -558,9 +584,31 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 231B3F1E2D0879BC0069A07D /* Metrics Visualization */ = { + isa = PBXGroup; + children = ( + 2373AE122D0A216C0086C749 /* MetricsChartSeries.swift */, + 231B3F202D087A4C0069A07D /* MetricTableColumn.swift */, + 231B3F1F2D087A4C0069A07D /* MetricsColumnList.swift */, + 2373AE142D0A24930086C749 /* MetricsSeriesList.swift */, + ); + path = "Metrics Visualization"; + sourceTree = ""; + }; + 231B3F232D087C020069A07D /* Metrics Columns */ = { + isa = PBXGroup; + children = ( + 231B3F242D087C3C0069A07D /* EnvironmentDefaultColumns.swift */, + 2373AE162D0A26620086C749 /* EnviornmentDefaultSeries.swift */, + 231B3F262D0885240069A07D /* MetricsColumnDetail.swift */, + ); + path = "Metrics Columns"; + sourceTree = ""; + }; 251926882C3BAF2E00249DF5 /* Actions */ = { isa = PBXGroup; children = ( + DDDFE73E2D0D48FF0044463C /* IgnoreNodeButton.swift */, 251926842C3BA97800249DF5 /* FavoriteNodeButton.swift */, 251926892C3BB1B200249DF5 /* ExchangePositionsButton.swift */, 251926862C3BAE2200249DF5 /* NodeAlertsButton.swift */, @@ -627,6 +675,17 @@ path = Custom; sourceTree = ""; }; + D32BA3902D54612800714840 /* CoreData */ = { + isa = PBXGroup; + children = ( + D32BA3912D54617800714840 /* NodeInfoEntity+CoreDataClass.swift */, + D32BA3922D54617800714840 /* NodeInfoEntity+CoreDataProperties.swift */, + D32BA3932D54617800714840 /* UserEntity+CoreDataClass.swift */, + D32BA3942D54617800714840 /* UserEntity+CoreDataProperties.swift */, + ); + path = CoreData; + sourceTree = ""; + }; D9C9839E2B79D0C600BDBE6A /* TextMessageField */ = { isa = PBXGroup; children = ( @@ -675,6 +734,7 @@ 6DEDA5592A957B8E00321D2E /* DetectionSensorLog.swift */, DD15E4F42B8BFC8E00654F61 /* PaxCounterLog.swift */, DDE5B4032B2279A700FCDD05 /* TraceRouteLog.swift */, + 8D3F8A402D44C2A6009EAAA4 /* PowerMetricsLog.swift */, ); path = Nodes; sourceTree = ""; @@ -867,6 +927,7 @@ DDC2E14B26CE248E0042C5E4 = { isa = PBXGroup; children = ( + DDD3A2B12D5127B40045EB48 /* ci_scripts */, DDDBC87A2BC62E4E001E8DF7 /* Settings.bundle */, 25AECD4E2C2F723200862C8E /* Localizable.xcstrings */, DD3CC6BA28E366DF00FA9159 /* Meshtastic.xcdatamodeld */, @@ -892,6 +953,7 @@ DDC2E15626CE248E0042C5E4 /* Meshtastic */ = { isa = PBXGroup; children = ( + D32BA3902D54612800714840 /* CoreData */, BCB6137F2C6728E700485544 /* AppIntents */, DD1BD0EC2C603C5B008C0C70 /* Measurement */, 25F5D5BC2C3F6D7B008036E3 /* Router */, @@ -940,6 +1002,7 @@ DDC2E18826CE24EE0042C5E4 /* Model */ = { isa = PBXGroup; children = ( + 231B3F1E2D0879BC0069A07D /* Metrics Visualization */, DD23A50E26FD1B4400D9B90C /* PeripheralModel.swift */, ); path = Model; @@ -983,13 +1046,13 @@ DDF924C926FBB953009FE055 /* ConnectedDevice.swift */, DDD94A4F2845C8F5004A87A0 /* DateTimeText.swift */, DDB6ABDA28B0AC6000384BA1 /* DistanceText.swift */, - DDC3B273283F411B00AC321C /* LastHeardText.swift */, DDB75A202A12B954006ED576 /* LoRaSignalStrength.swift */, DDB75A1D2A0B0CD0006ED576 /* LoRaSignalStrengthIndicator.swift */, DD97E96528EFD9820056DDA4 /* MeshtasticLogo.swift */, DDF45C332BC1A48E005ED5F2 /* MQTTIcon.swift */, DD5E523D298F5A7D00D21B61 /* Weather */, DD6F65712C6AB8EC0053C113 /* SecureInput.swift */, + 8D3F8A3E2D44BB02009EAAA4 /* PowerMetrics.swift */, ); path = Helpers; sourceTree = ""; @@ -1021,6 +1084,14 @@ path = Persistence; sourceTree = ""; }; + DDD3A2B12D5127B40045EB48 /* ci_scripts */ = { + isa = PBXGroup; + children = ( + DDD3A2B22D5127CF0045EB48 /* ci_pre_xcodebuild.sh */, + ); + path = ci_scripts; + sourceTree = ""; + }; DDD43FE12A78C86B0083A3E9 /* Mqtt */ = { isa = PBXGroup; children = ( @@ -1041,6 +1112,7 @@ DDDB26402AABEF7B003AFCB7 /* Helpers */ = { isa = PBXGroup; children = ( + 231B3F232D087C020069A07D /* Metrics Columns */, DDAD49EB2AFAE82500B4425D /* Map */, DDDB26432AAC0206003AFCB7 /* NodeDetail.swift */, DDDB26452AACC0B7003AFCB7 /* NodeInfoItem.swift */, @@ -1208,6 +1280,7 @@ "zh-Hant-TW", se, "pt-PT", + sr, ); mainGroup = DDC2E14B26CE248E0042C5E4; packageReferences = ( @@ -1316,11 +1389,13 @@ DD5D0A9C2931B9F200F7EA61 /* EthernetModes.swift in Sources */, 6DEDA55A2A957B8E00321D2E /* DetectionSensorLog.swift in Sources */, DD798B072915928D005217CD /* ChannelMessageList.swift in Sources */, + 231B3F272D0885240069A07D /* MetricsColumnDetail.swift in Sources */, DDC2E1A726CEB3400042C5E4 /* LocationHelper.swift in Sources */, DD77093D2AA1AFA3007A8BF0 /* ChannelTips.swift in Sources */, 6D825E622C34786C008DBEE4 /* CommonRegex.swift in Sources */, DD913639270DFF4C00D7ACF3 /* LocalNotificationManager.swift in Sources */, DDDB444C29F8AAA600EE2349 /* Color.swift in Sources */, + DDDFE73F2D0D48FF0044463C /* IgnoreNodeButton.swift in Sources */, DDB8F4122A9EE5DD00230ECE /* UserList.swift in Sources */, DDB75A0F2A05920E006ED576 /* FileManager.swift in Sources */, DD3D17E02C3FB67200561584 /* LocalWeatherConditions.swift in Sources */, @@ -1331,11 +1406,13 @@ DDB6ABD628AE742000384BA1 /* BluetoothConfig.swift in Sources */, 251926902C3CB44900249DF5 /* ClientHistoryButton.swift in Sources */, DDD5BB102C285FB3007E03CA /* AppLogFilter.swift in Sources */, + 2373AE172D0A26620086C749 /* EnviornmentDefaultSeries.swift in Sources */, DD4640202AFF10F4002A5ECB /* WaypointForm.swift in Sources */, DD769E0328D18BF1001A3F05 /* DeviceMetricsLog.swift in Sources */, DDAF8C5326EB1DF10058C060 /* BLEManager.swift in Sources */, DD15E4F32B8BA56E00654F61 /* PaxCounterConfig.swift in Sources */, DDDB445229F8ACF900EE2349 /* Date.swift in Sources */, + 2373AE132D0A216C0086C749 /* MetricsChartSeries.swift in Sources */, DDC4D568275499A500A4208E /* Persistence.swift in Sources */, DDD6EEAF29BC024700383354 /* Firmware.swift in Sources */, DD77093B2AA1ABB8007A8BF0 /* BluetoothTips.swift in Sources */, @@ -1345,7 +1422,9 @@ DD964FBD296E6B01007C176F /* EmojiOnlyTextField.swift in Sources */, DD8169FF272476C700F4AB02 /* LogDocument.swift in Sources */, DDC94FC129CE063B0082EA6E /* BatteryLevel.swift in Sources */, + 231B3F252D087C3C0069A07D /* EnvironmentDefaultColumns.swift in Sources */, 25F5D5BE2C3F6D87008036E3 /* NavigationState.swift in Sources */, + 2373AE152D0A24930086C749 /* MetricsSeriesList.swift in Sources */, DD354FD92BD96A0B0061A25F /* IAQScale.swift in Sources */, DDDB445429F8AD1600EE2349 /* Data.swift in Sources */, DDDB26462AACC0B7003AFCB7 /* NodeInfoItem.swift in Sources */, @@ -1430,12 +1509,14 @@ DD3CC24C2C498D6C001BD3A2 /* BatteryCompact.swift in Sources */, BCB613812C67290800485544 /* SendWaypointIntent.swift in Sources */, DD1B8F402B35E2F10022AABC /* GPSStatus.swift in Sources */, + 231B3F212D087A4C0069A07D /* MetricTableColumn.swift in Sources */, + 231B3F222D087A4C0069A07D /* MetricsColumnList.swift in Sources */, DD8ED9C52898D51F00B3B0AB /* NetworkConfig.swift in Sources */, - DDC3B274283F411B00AC321C /* LastHeardText.swift in Sources */, DDDE5A1029AFE69700490C6C /* MeshActivityAttributes.swift in Sources */, DD1925B928CDA93900720036 /* SerialConfigEnums.swift in Sources */, 251926852C3BA97800249DF5 /* FavoriteNodeButton.swift in Sources */, D9C983A02B79D0E800BDBE6A /* AlertButton.swift in Sources */, + 8D3F8A412D44C2A6009EAAA4 /* PowerMetricsLog.swift in Sources */, DD86D4112881D16900BAEB7A /* WriteCsvFile.swift in Sources */, DD6F65762C6EA5490053C113 /* AckErrors.swift in Sources */, DDDB445029F8AC9C00EE2349 /* UIImage.swift in Sources */, @@ -1452,6 +1533,7 @@ DDB6ABE428B13FFF00384BA1 /* DisplayEnums.swift in Sources */, DD4975A52B147BA90026544E /* AmbientLightingConfig.swift in Sources */, D93068D92B81509C0066FBC8 /* TapbackResponses.swift in Sources */, + 8D3F8A3F2D44BB02009EAAA4 /* PowerMetrics.swift in Sources */, 2519268A2C3BB1B200249DF5 /* ExchangePositionsButton.swift in Sources */, DD86D40A287F04F100BAEB7A /* InvalidVersion.swift in Sources */, DDD94A502845C8F5004A87A0 /* DateTimeText.swift in Sources */, @@ -1536,7 +1618,7 @@ ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 17.5; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.MeshtasticTests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1559,7 +1641,7 @@ DEVELOPMENT_TEAM = GCH7VS5Y9R; GCC_C_LANGUAGE_STANDARD = gnu17; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 17.5; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.MeshtasticTests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1716,7 +1798,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.5.9; + MARKETING_VERSION = 2.5.19; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; @@ -1750,7 +1832,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.5.9; + MARKETING_VERSION = 2.5.19; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; @@ -1776,13 +1858,13 @@ INFOPLIST_FILE = Widgets/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Widgets; INFOPLIST_KEY_NSHumanReadableCopyright = ""; - IPHONEOS_DEPLOYMENT_TARGET = 16.6; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.5.9; + MARKETING_VERSION = 2.5.19; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1809,13 +1891,13 @@ INFOPLIST_FILE = Widgets/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Widgets; INFOPLIST_KEY_NSHumanReadableCopyright = ""; - IPHONEOS_DEPLOYMENT_TARGET = 16.6; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.5.9; + MARKETING_VERSION = 2.5.19; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1927,6 +2009,9 @@ DD3CC6BA28E366DF00FA9159 /* Meshtastic.xcdatamodeld */ = { isa = XCVersionGroup; children = ( + 8D3F8A3D2D44B137009EAAA4 /* MeshtasticDataModelV 49.xcdatamodel */, + DDA28B1B2D32C89200EF726F /* MeshtasticDataModelV 48.xcdatamodel */, + DDDFE7402D0D4A070044463C /* MeshtasticDataModelV 47.xcdatamodel */, DD0BE30C2CB785D8000BA445 /* MeshtasticDataModelV 46.xcdatamodel */, DD6D5A342CA13BA600ED3032 /* MeshtasticDataModelV 45.xcdatamodel */, DD7CF8DA2C93663C008BD10E /* MeshtasticDataModelV 44.xcdatamodel */, @@ -1974,7 +2059,7 @@ DD5D0A9A2931AD6B00F7EA61 /* MeshtasticDataModelV2.xcdatamodel */, DD3CC6BB28E366DF00FA9159 /* MeshtasticDataModel.xcdatamodel */, ); - currentVersion = DD0BE30C2CB785D8000BA445 /* MeshtasticDataModelV 46.xcdatamodel */; + currentVersion = 8D3F8A3D2D44B137009EAAA4 /* MeshtasticDataModelV 49.xcdatamodel */; name = Meshtastic.xcdatamodeld; path = Meshtastic/Meshtastic.xcdatamodeld; sourceTree = ""; diff --git a/Meshtastic.xcodeproj/xcshareddata/xcschemes/Meshtastic.xcscheme b/Meshtastic.xcodeproj/xcshareddata/xcschemes/Meshtastic.xcscheme index 19c6089f..508a6cab 100644 --- a/Meshtastic.xcodeproj/xcshareddata/xcschemes/Meshtastic.xcscheme +++ b/Meshtastic.xcodeproj/xcshareddata/xcschemes/Meshtastic.xcscheme @@ -77,6 +77,16 @@ value = "oslogToStdio" isEnabled = "YES"> + + + + some IntentResult & ProvidesDialog { + if !BLEManager.shared.isConnected { + throw AppIntentErrors.AppIntentError.notConnected + } + + let fetchNodeInfoRequest: NSFetchRequest = NSFetchRequest(entityName: "NodeInfoEntity") + fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum)) + + do { + guard let fetchedNode = try PersistenceController.shared.container.viewContext.fetch(fetchNodeInfoRequest) as? [NodeInfoEntity], + fetchedNode.count == 1 else { + throw $nodeNum.needsValueError("Could not find node") + } + + let nodeInfo = fetchedNode[0] + if let latitude = nodeInfo.latestPosition?.coordinate.latitude, + let longitude = nodeInfo.latestPosition?.coordinate.longitude { + + let url = URL(string: "maps://?saddr=&daddr=\(latitude),\(longitude)") + + if let mapURL = url, UIApplication.shared.canOpenURL(mapURL) { + // Request to continue in foreground before opening the app + try await requestToContinueInForeground() + + // Open Apple Maps for navigation + UIApplication.shared.open(mapURL, options: [:], completionHandler: nil) + return .result(dialog: "Navigating to node location.") + } else { + throw AppIntentErrors.AppIntentError.message("Unable to open Apple Maps.") + } + } else { + throw AppIntentErrors.AppIntentError.message("Node does not have a recorded position.") + } + } catch { + throw AppIntentErrors.AppIntentError.message("Failed to fetch node data.") + } + } +} diff --git a/Meshtastic/Assets.xcassets/TLORABOARD.imageset/Contents.json b/Meshtastic/Assets.xcassets/HELTECHT62.imageset/Contents.json similarity index 51% rename from Meshtastic/Assets.xcassets/TLORABOARD.imageset/Contents.json rename to Meshtastic/Assets.xcassets/HELTECHT62.imageset/Contents.json index f8356864..418dd7fe 100644 --- a/Meshtastic/Assets.xcassets/TLORABOARD.imageset/Contents.json +++ b/Meshtastic/Assets.xcassets/HELTECHT62.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "LILYGO-TTGO-LoRa32-V2-1-1-6-Version-433-868-915Mhz-ESP32-LoRa-OLED-0-96.jpg_Q90.jpg_.webp.png", + "filename" : "heltec-ht62-esp32c3-sx1262.svg", "idiom" : "universal" } ], diff --git a/Meshtastic/Assets.xcassets/HELTECHT62.imageset/heltec-ht62-esp32c3-sx1262.svg b/Meshtastic/Assets.xcassets/HELTECHT62.imageset/heltec-ht62-esp32c3-sx1262.svg new file mode 100644 index 00000000..c52534ef --- /dev/null +++ b/Meshtastic/Assets.xcassets/HELTECHT62.imageset/heltec-ht62-esp32c3-sx1262.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Meshtastic/Assets.xcassets/HELTECMESHNODET114.imageset/Contents.json b/Meshtastic/Assets.xcassets/HELTECMESHNODET114.imageset/Contents.json new file mode 100644 index 00000000..a4f550b7 --- /dev/null +++ b/Meshtastic/Assets.xcassets/HELTECMESHNODET114.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "heltec-mesh-node-t114-case.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Meshtastic/Assets.xcassets/HELTECMESHNODET114.imageset/heltec-mesh-node-t114-case.svg b/Meshtastic/Assets.xcassets/HELTECMESHNODET114.imageset/heltec-mesh-node-t114-case.svg new file mode 100644 index 00000000..b2abe639 --- /dev/null +++ b/Meshtastic/Assets.xcassets/HELTECMESHNODET114.imageset/heltec-mesh-node-t114-case.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Meshtastic/Assets.xcassets/HELTECV3.imageset/Contents.json b/Meshtastic/Assets.xcassets/HELTECV3.imageset/Contents.json index 98595042..42c0472b 100644 --- a/Meshtastic/Assets.xcassets/HELTECV3.imageset/Contents.json +++ b/Meshtastic/Assets.xcassets/HELTECV3.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "Heltec_turq.png", + "filename" : "heltec-v3-case.svg", "idiom" : "universal" } ], diff --git a/Meshtastic/Assets.xcassets/HELTECV3.imageset/Heltec_turq.png b/Meshtastic/Assets.xcassets/HELTECV3.imageset/Heltec_turq.png deleted file mode 100644 index c4454bcc..00000000 Binary files a/Meshtastic/Assets.xcassets/HELTECV3.imageset/Heltec_turq.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/HELTECV3.imageset/heltec-v3-case.svg b/Meshtastic/Assets.xcassets/HELTECV3.imageset/heltec-v3-case.svg new file mode 100644 index 00000000..1b1d3c55 --- /dev/null +++ b/Meshtastic/Assets.xcassets/HELTECV3.imageset/heltec-v3-case.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Meshtastic/Assets.xcassets/HELTECVISIONMASTERE213.imageset/Contents.json b/Meshtastic/Assets.xcassets/HELTECVISIONMASTERE213.imageset/Contents.json new file mode 100644 index 00000000..687a7da9 --- /dev/null +++ b/Meshtastic/Assets.xcassets/HELTECVISIONMASTERE213.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "heltec-vision-master-e213.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Meshtastic/Assets.xcassets/HELTECVISIONMASTERE213.imageset/heltec-vision-master-e213.svg b/Meshtastic/Assets.xcassets/HELTECVISIONMASTERE213.imageset/heltec-vision-master-e213.svg new file mode 100644 index 00000000..2c1cca09 --- /dev/null +++ b/Meshtastic/Assets.xcassets/HELTECVISIONMASTERE213.imageset/heltec-vision-master-e213.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Meshtastic/Assets.xcassets/HELTECVISIONMASTERE290.imageset/Contents.json b/Meshtastic/Assets.xcassets/HELTECVISIONMASTERE290.imageset/Contents.json new file mode 100644 index 00000000..13ddda16 --- /dev/null +++ b/Meshtastic/Assets.xcassets/HELTECVISIONMASTERE290.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "heltec-vision-master-e290.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Meshtastic/Assets.xcassets/HELTECVISIONMASTERE290.imageset/heltec-vision-master-e290.svg b/Meshtastic/Assets.xcassets/HELTECVISIONMASTERE290.imageset/heltec-vision-master-e290.svg new file mode 100644 index 00000000..ca7d296a --- /dev/null +++ b/Meshtastic/Assets.xcassets/HELTECVISIONMASTERE290.imageset/heltec-vision-master-e290.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Meshtastic/Assets.xcassets/HELTECWIRELESSPAPER.imageset/Contents.json b/Meshtastic/Assets.xcassets/HELTECWIRELESSPAPER.imageset/Contents.json index 1a8d07dc..a1a7444e 100644 --- a/Meshtastic/Assets.xcassets/HELTECWIRELESSPAPER.imageset/Contents.json +++ b/Meshtastic/Assets.xcassets/HELTECWIRELESSPAPER.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "Paper-Meshtastic-2 copy.jpg", + "filename" : "heltec-wireless-paper.svg", "idiom" : "universal" } ], diff --git a/Meshtastic/Assets.xcassets/HELTECWIRELESSPAPER.imageset/Paper-Meshtastic-2 copy.jpg b/Meshtastic/Assets.xcassets/HELTECWIRELESSPAPER.imageset/Paper-Meshtastic-2 copy.jpg deleted file mode 100644 index 36692599..00000000 Binary files a/Meshtastic/Assets.xcassets/HELTECWIRELESSPAPER.imageset/Paper-Meshtastic-2 copy.jpg and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/HELTECWIRELESSPAPER.imageset/heltec-wireless-paper.svg b/Meshtastic/Assets.xcassets/HELTECWIRELESSPAPER.imageset/heltec-wireless-paper.svg new file mode 100644 index 00000000..cb3f188d --- /dev/null +++ b/Meshtastic/Assets.xcassets/HELTECWIRELESSPAPER.imageset/heltec-wireless-paper.svg @@ -0,0 +1 @@ + diff --git a/Meshtastic/Assets.xcassets/HELTECWIRELESSTRACKER.imageset/Contents.json b/Meshtastic/Assets.xcassets/HELTECWIRELESSTRACKER.imageset/Contents.json index 3b6b227c..d13152fe 100644 --- a/Meshtastic/Assets.xcassets/HELTECWIRELESSTRACKER.imageset/Contents.json +++ b/Meshtastic/Assets.xcassets/HELTECWIRELESSTRACKER.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "images.png", + "filename" : "heltec-wireless-tracker.svg", "idiom" : "universal" } ], diff --git a/Meshtastic/Assets.xcassets/HELTECWIRELESSTRACKER.imageset/heltec-wireless-tracker.svg b/Meshtastic/Assets.xcassets/HELTECWIRELESSTRACKER.imageset/heltec-wireless-tracker.svg new file mode 100644 index 00000000..a5392595 --- /dev/null +++ b/Meshtastic/Assets.xcassets/HELTECWIRELESSTRACKER.imageset/heltec-wireless-tracker.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Meshtastic/Assets.xcassets/HELTECWIRELESSTRACKER.imageset/images.png b/Meshtastic/Assets.xcassets/HELTECWIRELESSTRACKER.imageset/images.png deleted file mode 100644 index 4e9336c5..00000000 Binary files a/Meshtastic/Assets.xcassets/HELTECWIRELESSTRACKER.imageset/images.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/HELTECWSLV3.imageset/Contents.json b/Meshtastic/Assets.xcassets/HELTECWSLV3.imageset/Contents.json index aed717e4..dea94fc1 100644 --- a/Meshtastic/Assets.xcassets/HELTECWSLV3.imageset/Contents.json +++ b/Meshtastic/Assets.xcassets/HELTECWSLV3.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "heltecwsl.png", + "filename" : "heltec-wsl-v3.svg", "idiom" : "universal" } ], diff --git a/Meshtastic/Assets.xcassets/HELTECWSLV3.imageset/heltec-wsl-v3.svg b/Meshtastic/Assets.xcassets/HELTECWSLV3.imageset/heltec-wsl-v3.svg new file mode 100644 index 00000000..1741223e --- /dev/null +++ b/Meshtastic/Assets.xcassets/HELTECWSLV3.imageset/heltec-wsl-v3.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Meshtastic/Assets.xcassets/HELTECWSLV3.imageset/heltecwsl.png b/Meshtastic/Assets.xcassets/HELTECWSLV3.imageset/heltecwsl.png deleted file mode 100644 index 8881d0e1..00000000 Binary files a/Meshtastic/Assets.xcassets/HELTECWSLV3.imageset/heltecwsl.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/LILYGOTBEAMS3CORE.imageset/Contents.json b/Meshtastic/Assets.xcassets/LILYGOTBEAMS3CORE.imageset/Contents.json index 892d20eb..1febc627 100644 --- a/Meshtastic/Assets.xcassets/LILYGOTBEAMS3CORE.imageset/Contents.json +++ b/Meshtastic/Assets.xcassets/LILYGOTBEAMS3CORE.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "tbeam_supreme.png", + "filename" : "tbeam-s3-core.svg", "idiom" : "universal" } ], diff --git a/Meshtastic/Assets.xcassets/LILYGOTBEAMS3CORE.imageset/tbeam-s3-core.svg b/Meshtastic/Assets.xcassets/LILYGOTBEAMS3CORE.imageset/tbeam-s3-core.svg new file mode 100644 index 00000000..f42e6d2c --- /dev/null +++ b/Meshtastic/Assets.xcassets/LILYGOTBEAMS3CORE.imageset/tbeam-s3-core.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Meshtastic/Assets.xcassets/LILYGOTBEAMS3CORE.imageset/tbeam_supreme.png b/Meshtastic/Assets.xcassets/LILYGOTBEAMS3CORE.imageset/tbeam_supreme.png deleted file mode 100644 index 6a618653..00000000 Binary files a/Meshtastic/Assets.xcassets/LILYGOTBEAMS3CORE.imageset/tbeam_supreme.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/NANOG2ULTRA.imageset/Contents.json b/Meshtastic/Assets.xcassets/NANOG2ULTRA.imageset/Contents.json index 2f074381..fe8b1d15 100644 --- a/Meshtastic/Assets.xcassets/NANOG2ULTRA.imageset/Contents.json +++ b/Meshtastic/Assets.xcassets/NANOG2ULTRA.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "nano_g2_ultra_product_image.jpg", + "filename" : "nano-g2-ultra.svg", "idiom" : "universal" } ], diff --git a/Meshtastic/Assets.xcassets/NANOG2ULTRA.imageset/nano-g2-ultra.svg b/Meshtastic/Assets.xcassets/NANOG2ULTRA.imageset/nano-g2-ultra.svg new file mode 100644 index 00000000..6dbe47af --- /dev/null +++ b/Meshtastic/Assets.xcassets/NANOG2ULTRA.imageset/nano-g2-ultra.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Meshtastic/Assets.xcassets/NANOG2ULTRA.imageset/nano_g2_ultra_product_image.jpg b/Meshtastic/Assets.xcassets/NANOG2ULTRA.imageset/nano_g2_ultra_product_image.jpg deleted file mode 100644 index 18f2b472..00000000 Binary files a/Meshtastic/Assets.xcassets/NANOG2ULTRA.imageset/nano_g2_ultra_product_image.jpg and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/RAK11200.imageset/Contents.json b/Meshtastic/Assets.xcassets/PROMICRO.imageset/Contents.json similarity index 75% rename from Meshtastic/Assets.xcassets/RAK11200.imageset/Contents.json rename to Meshtastic/Assets.xcassets/PROMICRO.imageset/Contents.json index ed6c2585..0fbd5109 100644 --- a/Meshtastic/Assets.xcassets/RAK11200.imageset/Contents.json +++ b/Meshtastic/Assets.xcassets/PROMICRO.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "RAK_DEV_KIT-2.jpg", + "filename" : "promicro.svg", "idiom" : "universal" } ], diff --git a/Meshtastic/Assets.xcassets/PROMICRO.imageset/promicro.svg b/Meshtastic/Assets.xcassets/PROMICRO.imageset/promicro.svg new file mode 100644 index 00000000..3dc26021 --- /dev/null +++ b/Meshtastic/Assets.xcassets/PROMICRO.imageset/promicro.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Meshtastic/Assets.xcassets/RAK11200.imageset/RAK_DEV_KIT-2.jpg b/Meshtastic/Assets.xcassets/RAK11200.imageset/RAK_DEV_KIT-2.jpg deleted file mode 100644 index 9300bed0..00000000 Binary files a/Meshtastic/Assets.xcassets/RAK11200.imageset/RAK_DEV_KIT-2.jpg and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/TLORAV1.imageset/Contents.json b/Meshtastic/Assets.xcassets/RAK11310.imageset/Contents.json similarity index 75% rename from Meshtastic/Assets.xcassets/TLORAV1.imageset/Contents.json rename to Meshtastic/Assets.xcassets/RAK11310.imageset/Contents.json index 093c722d..3046b536 100644 --- a/Meshtastic/Assets.xcassets/TLORAV1.imageset/Contents.json +++ b/Meshtastic/Assets.xcassets/RAK11310.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "TLORA_olive 1.png", + "filename" : "rak11310.svg", "idiom" : "universal" } ], diff --git a/Meshtastic/Assets.xcassets/RAK11310.imageset/rak11310.svg b/Meshtastic/Assets.xcassets/RAK11310.imageset/rak11310.svg new file mode 100644 index 00000000..8c5ce28e --- /dev/null +++ b/Meshtastic/Assets.xcassets/RAK11310.imageset/rak11310.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Meshtastic/Assets.xcassets/RAK4631.imageset/Contents.json b/Meshtastic/Assets.xcassets/RAK4631.imageset/Contents.json index feb2e6c0..60b17db3 100644 --- a/Meshtastic/Assets.xcassets/RAK4631.imageset/Contents.json +++ b/Meshtastic/Assets.xcassets/RAK4631.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "RAK 4.png", + "filename" : "rak4631_case.svg", "idiom" : "universal" } ], diff --git a/Meshtastic/Assets.xcassets/RAK4631.imageset/RAK 4.png b/Meshtastic/Assets.xcassets/RAK4631.imageset/RAK 4.png deleted file mode 100644 index e34322b8..00000000 Binary files a/Meshtastic/Assets.xcassets/RAK4631.imageset/RAK 4.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/RAK4631.imageset/rak4631_case.svg b/Meshtastic/Assets.xcassets/RAK4631.imageset/rak4631_case.svg new file mode 100644 index 00000000..a0b2bbb8 --- /dev/null +++ b/Meshtastic/Assets.xcassets/RAK4631.imageset/rak4631_case.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Meshtastic/Assets.xcassets/RPIPICO.imageset/Contents.json b/Meshtastic/Assets.xcassets/RPIPICO.imageset/Contents.json new file mode 100644 index 00000000..87088506 --- /dev/null +++ b/Meshtastic/Assets.xcassets/RPIPICO.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "pico.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Meshtastic/Assets.xcassets/RPIPICO.imageset/pico.svg b/Meshtastic/Assets.xcassets/RPIPICO.imageset/pico.svg new file mode 100644 index 00000000..82ce6526 --- /dev/null +++ b/Meshtastic/Assets.xcassets/RPIPICO.imageset/pico.svg @@ -0,0 +1,2956 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Meshtastic/Assets.xcassets/SEEEDXIAOS3.imageset/Contents.json b/Meshtastic/Assets.xcassets/SEEEDXIAOS3.imageset/Contents.json new file mode 100644 index 00000000..fdd4019e --- /dev/null +++ b/Meshtastic/Assets.xcassets/SEEEDXIAOS3.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "seeed-xiao-s3.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Meshtastic/Assets.xcassets/SEEEDXIAOS3.imageset/seeed-xiao-s3.svg b/Meshtastic/Assets.xcassets/SEEEDXIAOS3.imageset/seeed-xiao-s3.svg new file mode 100644 index 00000000..04e97fe0 --- /dev/null +++ b/Meshtastic/Assets.xcassets/SEEEDXIAOS3.imageset/seeed-xiao-s3.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Meshtastic/Assets.xcassets/SENSECAPINDICATOR.imageset/Contents.json b/Meshtastic/Assets.xcassets/SENSECAPINDICATOR.imageset/Contents.json new file mode 100644 index 00000000..3870939e --- /dev/null +++ b/Meshtastic/Assets.xcassets/SENSECAPINDICATOR.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "seeed-sensecap-indicator.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Meshtastic/Assets.xcassets/SENSECAPINDICATOR.imageset/seeed-sensecap-indicator.svg b/Meshtastic/Assets.xcassets/SENSECAPINDICATOR.imageset/seeed-sensecap-indicator.svg new file mode 100644 index 00000000..f7bf9db0 --- /dev/null +++ b/Meshtastic/Assets.xcassets/SENSECAPINDICATOR.imageset/seeed-sensecap-indicator.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Meshtastic/Assets.xcassets/STATIONG2.imageset/Contents.json b/Meshtastic/Assets.xcassets/STATIONG2.imageset/Contents.json new file mode 100644 index 00000000..dc823045 --- /dev/null +++ b/Meshtastic/Assets.xcassets/STATIONG2.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "station-g2.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Meshtastic/Assets.xcassets/STATIONG2.imageset/station-g2.svg b/Meshtastic/Assets.xcassets/STATIONG2.imageset/station-g2.svg new file mode 100644 index 00000000..8d2e0aed --- /dev/null +++ b/Meshtastic/Assets.xcassets/STATIONG2.imageset/station-g2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Meshtastic/Assets.xcassets/TBEAM.imageset/Contents.json b/Meshtastic/Assets.xcassets/TBEAM.imageset/Contents.json index 64a09f22..0ecd041c 100644 --- a/Meshtastic/Assets.xcassets/TBEAM.imageset/Contents.json +++ b/Meshtastic/Assets.xcassets/TBEAM.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "tbeam.png", + "filename" : "tbeam.svg", "idiom" : "universal" } ], diff --git a/Meshtastic/Assets.xcassets/TBEAM.imageset/tbeam.png b/Meshtastic/Assets.xcassets/TBEAM.imageset/tbeam.png deleted file mode 100644 index 75fec7be..00000000 Binary files a/Meshtastic/Assets.xcassets/TBEAM.imageset/tbeam.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/TBEAM.imageset/tbeam.svg b/Meshtastic/Assets.xcassets/TBEAM.imageset/tbeam.svg new file mode 100644 index 00000000..cd0475c6 --- /dev/null +++ b/Meshtastic/Assets.xcassets/TBEAM.imageset/tbeam.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Meshtastic/Assets.xcassets/TDECK.imageset/Contents.json b/Meshtastic/Assets.xcassets/TDECK.imageset/Contents.json new file mode 100644 index 00000000..b8451344 --- /dev/null +++ b/Meshtastic/Assets.xcassets/TDECK.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "t-deck.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Meshtastic/Assets.xcassets/TDECK.imageset/t-deck.svg b/Meshtastic/Assets.xcassets/TDECK.imageset/t-deck.svg new file mode 100644 index 00000000..cdc53c5d --- /dev/null +++ b/Meshtastic/Assets.xcassets/TDECK.imageset/t-deck.svg @@ -0,0 +1 @@ +QWERTYIUPOASDFGHKJLaltZXCVBMN \ No newline at end of file diff --git a/Meshtastic/Assets.xcassets/TECHO.imageset/Contents.json b/Meshtastic/Assets.xcassets/TECHO.imageset/Contents.json index f380b7af..e1adcf61 100644 --- a/Meshtastic/Assets.xcassets/TECHO.imageset/Contents.json +++ b/Meshtastic/Assets.xcassets/TECHO.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "LILYGO-TTGO-SoftRF-T-Echo-NRF52840-LoRa-SX1262-433-868-915MHz-Wireless-Module-L76K-GPS-1.png", + "filename" : "t-echo.svg", "idiom" : "universal" } ], diff --git a/Meshtastic/Assets.xcassets/TECHO.imageset/LILYGO-TTGO-SoftRF-T-Echo-NRF52840-LoRa-SX1262-433-868-915MHz-Wireless-Module-L76K-GPS-1.png b/Meshtastic/Assets.xcassets/TECHO.imageset/LILYGO-TTGO-SoftRF-T-Echo-NRF52840-LoRa-SX1262-433-868-915MHz-Wireless-Module-L76K-GPS-1.png deleted file mode 100644 index 7b2f9f96..00000000 Binary files a/Meshtastic/Assets.xcassets/TECHO.imageset/LILYGO-TTGO-SoftRF-T-Echo-NRF52840-LoRa-SX1262-433-868-915MHz-Wireless-Module-L76K-GPS-1.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/TECHO.imageset/t-echo.svg b/Meshtastic/Assets.xcassets/TECHO.imageset/t-echo.svg new file mode 100644 index 00000000..e178a50f --- /dev/null +++ b/Meshtastic/Assets.xcassets/TECHO.imageset/t-echo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Meshtastic/Assets.xcassets/TLORABOARD.imageset/LILYGO-TTGO-LoRa32-V2-1-1-6-Version-433-868-915Mhz-ESP32-LoRa-OLED-0-96.jpg_Q90.jpg_.webp.png b/Meshtastic/Assets.xcassets/TLORABOARD.imageset/LILYGO-TTGO-LoRa32-V2-1-1-6-Version-433-868-915Mhz-ESP32-LoRa-OLED-0-96.jpg_Q90.jpg_.webp.png deleted file mode 100644 index ff3da639..00000000 Binary files a/Meshtastic/Assets.xcassets/TLORABOARD.imageset/LILYGO-TTGO-LoRa32-V2-1-1-6-Version-433-868-915Mhz-ESP32-LoRa-OLED-0-96.jpg_Q90.jpg_.webp.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/TLORAC6.imageset/Contents.json b/Meshtastic/Assets.xcassets/TLORAC6.imageset/Contents.json new file mode 100644 index 00000000..593dc16e --- /dev/null +++ b/Meshtastic/Assets.xcassets/TLORAC6.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "tlora-c6.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Meshtastic/Assets.xcassets/TLORAC6.imageset/tlora-c6.svg b/Meshtastic/Assets.xcassets/TLORAC6.imageset/tlora-c6.svg new file mode 100644 index 00000000..8b626638 --- /dev/null +++ b/Meshtastic/Assets.xcassets/TLORAC6.imageset/tlora-c6.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Meshtastic/Assets.xcassets/TLORAT3S3EPAPER.imageset/Contents.json b/Meshtastic/Assets.xcassets/TLORAT3S3EPAPER.imageset/Contents.json new file mode 100644 index 00000000..33fb9c78 --- /dev/null +++ b/Meshtastic/Assets.xcassets/TLORAT3S3EPAPER.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "tlora-t3s3-epaper.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Meshtastic/Assets.xcassets/TLORAT3S3EPAPER.imageset/tlora-t3s3-epaper.svg b/Meshtastic/Assets.xcassets/TLORAT3S3EPAPER.imageset/tlora-t3s3-epaper.svg new file mode 100644 index 00000000..6f2e8452 --- /dev/null +++ b/Meshtastic/Assets.xcassets/TLORAT3S3EPAPER.imageset/tlora-t3s3-epaper.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Meshtastic/Assets.xcassets/TLORAT3S3V1.imageset/Contents.json b/Meshtastic/Assets.xcassets/TLORAT3S3V1.imageset/Contents.json new file mode 100644 index 00000000..a5716fc8 --- /dev/null +++ b/Meshtastic/Assets.xcassets/TLORAT3S3V1.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "tlora-t3s3-v1.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Meshtastic/Assets.xcassets/TLORAT3S3V1.imageset/tlora-t3s3-v1.svg b/Meshtastic/Assets.xcassets/TLORAT3S3V1.imageset/tlora-t3s3-v1.svg new file mode 100644 index 00000000..1f8847d4 --- /dev/null +++ b/Meshtastic/Assets.xcassets/TLORAT3S3V1.imageset/tlora-t3s3-v1.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Meshtastic/Assets.xcassets/TLORAV1.imageset/TLORA_olive 1.png b/Meshtastic/Assets.xcassets/TLORAV1.imageset/TLORA_olive 1.png deleted file mode 100644 index e8980a2c..00000000 Binary files a/Meshtastic/Assets.xcassets/TLORAV1.imageset/TLORA_olive 1.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/TLORAV211P6.imageset/Contents.json b/Meshtastic/Assets.xcassets/TLORAV211P6.imageset/Contents.json new file mode 100644 index 00000000..eb286609 --- /dev/null +++ b/Meshtastic/Assets.xcassets/TLORAV211P6.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "tlora-v2-1-1_6.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Meshtastic/Assets.xcassets/TLORAV211P6.imageset/tlora-v2-1-1_6.svg b/Meshtastic/Assets.xcassets/TLORAV211P6.imageset/tlora-v2-1-1_6.svg new file mode 100644 index 00000000..dbe36ef5 --- /dev/null +++ b/Meshtastic/Assets.xcassets/TLORAV211P6.imageset/tlora-v2-1-1_6.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Meshtastic/Assets.xcassets/TLORAV211P8.imageset/Contents.json b/Meshtastic/Assets.xcassets/TLORAV211P8.imageset/Contents.json new file mode 100644 index 00000000..c7aff831 --- /dev/null +++ b/Meshtastic/Assets.xcassets/TLORAV211P8.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "tlora-v2-1-1_8.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Meshtastic/Assets.xcassets/TLORAV211P8.imageset/tlora-v2-1-1_8.svg b/Meshtastic/Assets.xcassets/TLORAV211P8.imageset/tlora-v2-1-1_8.svg new file mode 100644 index 00000000..dbe36ef5 --- /dev/null +++ b/Meshtastic/Assets.xcassets/TLORAV211P8.imageset/tlora-v2-1-1_8.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Meshtastic/Assets.xcassets/TRACKERT1000E.imageset/Contents.json b/Meshtastic/Assets.xcassets/TRACKERT1000E.imageset/Contents.json new file mode 100644 index 00000000..e966c95f --- /dev/null +++ b/Meshtastic/Assets.xcassets/TRACKERT1000E.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "tracker-t1000-e.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Meshtastic/Assets.xcassets/TRACKERT1000E.imageset/tracker-t1000-e.svg b/Meshtastic/Assets.xcassets/TRACKERT1000E.imageset/tracker-t1000-e.svg new file mode 100644 index 00000000..6f7a06c9 --- /dev/null +++ b/Meshtastic/Assets.xcassets/TRACKERT1000E.imageset/tracker-t1000-e.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Meshtastic/Assets.xcassets/TWATCHS3.imageset/Contents.json b/Meshtastic/Assets.xcassets/TWATCHS3.imageset/Contents.json new file mode 100644 index 00000000..baffc648 --- /dev/null +++ b/Meshtastic/Assets.xcassets/TWATCHS3.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "t-watch-s3.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Meshtastic/Assets.xcassets/TWATCHS3.imageset/t-watch-s3.svg b/Meshtastic/Assets.xcassets/TWATCHS3.imageset/t-watch-s3.svg new file mode 100644 index 00000000..19084c19 --- /dev/null +++ b/Meshtastic/Assets.xcassets/TWATCHS3.imageset/t-watch-s3.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Meshtastic/Assets.xcassets/UNSET.imageset/Contents.json b/Meshtastic/Assets.xcassets/UNSET.imageset/Contents.json index 04be44d5..4508d9cd 100644 --- a/Meshtastic/Assets.xcassets/UNSET.imageset/Contents.json +++ b/Meshtastic/Assets.xcassets/UNSET.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "play_store_icon_114px-2.png", + "filename" : "unknown.svg", "idiom" : "universal" } ], diff --git a/Meshtastic/Assets.xcassets/UNSET.imageset/play_store_icon_114px-2.png b/Meshtastic/Assets.xcassets/UNSET.imageset/play_store_icon_114px-2.png deleted file mode 100644 index 79cf0e00..00000000 Binary files a/Meshtastic/Assets.xcassets/UNSET.imageset/play_store_icon_114px-2.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/UNSET.imageset/unknown.svg b/Meshtastic/Assets.xcassets/UNSET.imageset/unknown.svg new file mode 100644 index 00000000..3b0a0744 --- /dev/null +++ b/Meshtastic/Assets.xcassets/UNSET.imageset/unknown.svg @@ -0,0 +1,129 @@ + + diff --git a/Meshtastic/Assets.xcassets/WIOWM1110.imageset/Contents.json b/Meshtastic/Assets.xcassets/WIOWM1110.imageset/Contents.json new file mode 100644 index 00000000..706f7fc3 --- /dev/null +++ b/Meshtastic/Assets.xcassets/WIOWM1110.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "wio-tracker-wm1110.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Meshtastic/Assets.xcassets/WIOWM1110.imageset/wio-tracker-wm1110.svg b/Meshtastic/Assets.xcassets/WIOWM1110.imageset/wio-tracker-wm1110.svg new file mode 100644 index 00000000..15ace5c5 --- /dev/null +++ b/Meshtastic/Assets.xcassets/WIOWM1110.imageset/wio-tracker-wm1110.svg @@ -0,0 +1 @@ +LoRaWI FILEDRESETGNSSBLE \ No newline at end of file diff --git a/Meshtastic/Assets.xcassets/WISMESHTAP.imageset/Contents.json b/Meshtastic/Assets.xcassets/WISMESHTAP.imageset/Contents.json new file mode 100644 index 00000000..85d43a9b --- /dev/null +++ b/Meshtastic/Assets.xcassets/WISMESHTAP.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "rak-wismeshtap.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Meshtastic/Assets.xcassets/WISMESHTAP.imageset/rak-wismeshtap.svg b/Meshtastic/Assets.xcassets/WISMESHTAP.imageset/rak-wismeshtap.svg new file mode 100644 index 00000000..34e77876 --- /dev/null +++ b/Meshtastic/Assets.xcassets/WISMESHTAP.imageset/rak-wismeshtap.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Meshtastic/CoreData/NodeInfoEntity+CoreDataClass.swift b/Meshtastic/CoreData/NodeInfoEntity+CoreDataClass.swift new file mode 100644 index 00000000..37c14e81 --- /dev/null +++ b/Meshtastic/CoreData/NodeInfoEntity+CoreDataClass.swift @@ -0,0 +1,15 @@ +// +// NodeInfoEntity+CoreDataClass.swift +// +// +// Created by Brian Floersch on 2/5/25. +// +// + +import Foundation +import CoreData + +@objc(NodeInfoEntity) +public class NodeInfoEntity: NSManagedObject { + +} diff --git a/Meshtastic/CoreData/NodeInfoEntity+CoreDataProperties.swift b/Meshtastic/CoreData/NodeInfoEntity+CoreDataProperties.swift new file mode 100644 index 00000000..4df81fe2 --- /dev/null +++ b/Meshtastic/CoreData/NodeInfoEntity+CoreDataProperties.swift @@ -0,0 +1,200 @@ +// +// NodeInfoEntity+CoreDataProperties.swift +// +// +// Created by Brian Floersch on 2/5/25. +// +// + +import Foundation +import CoreData + +extension NodeInfoEntity { + + @nonobjc public class func fetchRequest() -> NSFetchRequest { + return NSFetchRequest(entityName: "NodeInfoEntity") + } + + @NSManaged public var bleName: String? + @NSManaged public var channel: Int32 + @NSManaged public var favorite: Bool + @NSManaged public var firstHeard: Date? + @NSManaged public var hopsAway: Int32 + @NSManaged public var id: Int64 + @NSManaged public var ignored: Bool + @NSManaged public var lastHeard: Date? + @NSManaged public var num: Int64 + @NSManaged public var peripheralId: String? + @NSManaged public var rssi: Int32 + @NSManaged public var sessionExpiration: Date? + @NSManaged public var sessionPasskey: Data? + @NSManaged public var snr: Float + @NSManaged public var viaMqtt: Bool + @NSManaged public var ambientLightingConfig: AmbientLightingConfigEntity? + @NSManaged public var bluetoothConfig: BluetoothConfigEntity? + @NSManaged public var cannedMessageConfig: CannedMessageConfigEntity? + @NSManaged public var detectionSensorConfig: DetectionSensorConfigEntity? + @NSManaged public var deviceConfig: DeviceConfigEntity? + @NSManaged public var displayConfig: DisplayConfigEntity? + @NSManaged public var externalNotificationConfig: ExternalNotificationConfigEntity? + @NSManaged public var loRaConfig: LoRaConfigEntity? + @NSManaged public var metadata: DeviceMetadataEntity? + @NSManaged public var mqttConfig: MQTTConfigEntity? + @NSManaged public var myInfo: MyInfoEntity? + @NSManaged public var networkConfig: NetworkConfigEntity? + @NSManaged public var pax: NSOrderedSet? + @NSManaged public var paxCounterConfig: PaxCounterConfigEntity? + @NSManaged public var positionConfig: PositionConfigEntity? + @NSManaged public var positions: NSOrderedSet? + @NSManaged public var powerConfig: PowerConfigEntity? + @NSManaged public var rangeTestConfig: RangeTestConfigEntity? + @NSManaged public var rtttlConfig: RTTTLConfigEntity? + @NSManaged public var securityConfig: SecurityConfigEntity? + @NSManaged public var serialConfig: SerialConfigEntity? + @NSManaged public var storeForwardConfig: StoreForwardConfigEntity? + @NSManaged public var telemetries: NSOrderedSet? + @NSManaged public var telemetryConfig: TelemetryConfigEntity? + @NSManaged public var traceRoutes: NSOrderedSet? + @NSManaged public var user: UserEntity? + +} + +// MARK: Generated accessors for pax +extension NodeInfoEntity { + + @objc(insertObject:inPaxAtIndex:) + @NSManaged public func insertIntoPax(_ value: PaxCounterEntity, at idx: Int) + + @objc(removeObjectFromPaxAtIndex:) + @NSManaged public func removeFromPax(at idx: Int) + + @objc(insertPax:atIndexes:) + @NSManaged public func insertIntoPax(_ values: [PaxCounterEntity], at indexes: NSIndexSet) + + @objc(removePaxAtIndexes:) + @NSManaged public func removeFromPax(at indexes: NSIndexSet) + + @objc(replaceObjectInPaxAtIndex:withObject:) + @NSManaged public func replacePax(at idx: Int, with value: PaxCounterEntity) + + @objc(replacePaxAtIndexes:withPax:) + @NSManaged public func replacePax(at indexes: NSIndexSet, with values: [PaxCounterEntity]) + + @objc(addPaxObject:) + @NSManaged public func addToPax(_ value: PaxCounterEntity) + + @objc(removePaxObject:) + @NSManaged public func removeFromPax(_ value: PaxCounterEntity) + + @objc(addPax:) + @NSManaged public func addToPax(_ values: NSOrderedSet) + + @objc(removePax:) + @NSManaged public func removeFromPax(_ values: NSOrderedSet) + +} + +// MARK: Generated accessors for positions +extension NodeInfoEntity { + + @objc(insertObject:inPositionsAtIndex:) + @NSManaged public func insertIntoPositions(_ value: PositionEntity, at idx: Int) + + @objc(removeObjectFromPositionsAtIndex:) + @NSManaged public func removeFromPositions(at idx: Int) + + @objc(insertPositions:atIndexes:) + @NSManaged public func insertIntoPositions(_ values: [PositionEntity], at indexes: NSIndexSet) + + @objc(removePositionsAtIndexes:) + @NSManaged public func removeFromPositions(at indexes: NSIndexSet) + + @objc(replaceObjectInPositionsAtIndex:withObject:) + @NSManaged public func replacePositions(at idx: Int, with value: PositionEntity) + + @objc(replacePositionsAtIndexes:withPositions:) + @NSManaged public func replacePositions(at indexes: NSIndexSet, with values: [PositionEntity]) + + @objc(addPositionsObject:) + @NSManaged public func addToPositions(_ value: PositionEntity) + + @objc(removePositionsObject:) + @NSManaged public func removeFromPositions(_ value: PositionEntity) + + @objc(addPositions:) + @NSManaged public func addToPositions(_ values: NSOrderedSet) + + @objc(removePositions:) + @NSManaged public func removeFromPositions(_ values: NSOrderedSet) + +} + +// MARK: Generated accessors for telemetries +extension NodeInfoEntity { + + @objc(insertObject:inTelemetriesAtIndex:) + @NSManaged public func insertIntoTelemetries(_ value: TelemetryEntity, at idx: Int) + + @objc(removeObjectFromTelemetriesAtIndex:) + @NSManaged public func removeFromTelemetries(at idx: Int) + + @objc(insertTelemetries:atIndexes:) + @NSManaged public func insertIntoTelemetries(_ values: [TelemetryEntity], at indexes: NSIndexSet) + + @objc(removeTelemetriesAtIndexes:) + @NSManaged public func removeFromTelemetries(at indexes: NSIndexSet) + + @objc(replaceObjectInTelemetriesAtIndex:withObject:) + @NSManaged public func replaceTelemetries(at idx: Int, with value: TelemetryEntity) + + @objc(replaceTelemetriesAtIndexes:withTelemetries:) + @NSManaged public func replaceTelemetries(at indexes: NSIndexSet, with values: [TelemetryEntity]) + + @objc(addTelemetriesObject:) + @NSManaged public func addToTelemetries(_ value: TelemetryEntity) + + @objc(removeTelemetriesObject:) + @NSManaged public func removeFromTelemetries(_ value: TelemetryEntity) + + @objc(addTelemetries:) + @NSManaged public func addToTelemetries(_ values: NSOrderedSet) + + @objc(removeTelemetries:) + @NSManaged public func removeFromTelemetries(_ values: NSOrderedSet) + +} + +// MARK: Generated accessors for traceRoutes +extension NodeInfoEntity { + + @objc(insertObject:inTraceRoutesAtIndex:) + @NSManaged public func insertIntoTraceRoutes(_ value: TraceRouteEntity, at idx: Int) + + @objc(removeObjectFromTraceRoutesAtIndex:) + @NSManaged public func removeFromTraceRoutes(at idx: Int) + + @objc(insertTraceRoutes:atIndexes:) + @NSManaged public func insertIntoTraceRoutes(_ values: [TraceRouteEntity], at indexes: NSIndexSet) + + @objc(removeTraceRoutesAtIndexes:) + @NSManaged public func removeFromTraceRoutes(at indexes: NSIndexSet) + + @objc(replaceObjectInTraceRoutesAtIndex:withObject:) + @NSManaged public func replaceTraceRoutes(at idx: Int, with value: TraceRouteEntity) + + @objc(replaceTraceRoutesAtIndexes:withTraceRoutes:) + @NSManaged public func replaceTraceRoutes(at indexes: NSIndexSet, with values: [TraceRouteEntity]) + + @objc(addTraceRoutesObject:) + @NSManaged public func addToTraceRoutes(_ value: TraceRouteEntity) + + @objc(removeTraceRoutesObject:) + @NSManaged public func removeFromTraceRoutes(_ value: TraceRouteEntity) + + @objc(addTraceRoutes:) + @NSManaged public func addToTraceRoutes(_ values: NSOrderedSet) + + @objc(removeTraceRoutes:) + @NSManaged public func removeFromTraceRoutes(_ values: NSOrderedSet) + +} diff --git a/Meshtastic/CoreData/UserEntity+CoreDataClass.swift b/Meshtastic/CoreData/UserEntity+CoreDataClass.swift new file mode 100644 index 00000000..cd207492 --- /dev/null +++ b/Meshtastic/CoreData/UserEntity+CoreDataClass.swift @@ -0,0 +1,15 @@ +// +// UserEntity+CoreDataClass.swift +// +// +// Created by Brian Floersch on 2/5/25. +// +// + +import Foundation +import CoreData + +@objc(UserEntity) +public class UserEntity: NSManagedObject { + +} diff --git a/Meshtastic/CoreData/UserEntity+CoreDataProperties.swift b/Meshtastic/CoreData/UserEntity+CoreDataProperties.swift new file mode 100644 index 00000000..753bba68 --- /dev/null +++ b/Meshtastic/CoreData/UserEntity+CoreDataProperties.swift @@ -0,0 +1,108 @@ +// +// UserEntity+CoreDataProperties.swift +// +// +// Created by Brian Floersch on 2/5/25. +// +// + +import Foundation +import CoreData + +extension UserEntity { + + @nonobjc public class func fetchRequest() -> NSFetchRequest { + return NSFetchRequest(entityName: "UserEntity") + } + + @NSManaged public var hwDisplayName: String? + @NSManaged public var hwModel: String? + @NSManaged public var hwModelId: Int32 + @NSManaged public var isLicensed: Bool + @NSManaged public var keyMatch: Bool + @NSManaged public var lastMessage: Date? + @NSManaged public var longName: String? + @NSManaged public var mute: Bool + @NSManaged public var newPublicKey: Data? + @NSManaged public var num: Int64 + @NSManaged public var numString: String? + @NSManaged public var pkiEncrypted: Bool + @NSManaged public var publicKey: Data? + @NSManaged public var role: Int32 + @NSManaged public var shortName: String? + @NSManaged public var userId: String? + @NSManaged public var receivedMessages: NSOrderedSet? + @NSManaged public var sentMessages: NSOrderedSet? + @NSManaged public var userNode: NodeInfoEntity? + +} + +// MARK: Generated accessors for receivedMessages +extension UserEntity { + + @objc(insertObject:inReceivedMessagesAtIndex:) + @NSManaged public func insertIntoReceivedMessages(_ value: MessageEntity, at idx: Int) + + @objc(removeObjectFromReceivedMessagesAtIndex:) + @NSManaged public func removeFromReceivedMessages(at idx: Int) + + @objc(insertReceivedMessages:atIndexes:) + @NSManaged public func insertIntoReceivedMessages(_ values: [MessageEntity], at indexes: NSIndexSet) + + @objc(removeReceivedMessagesAtIndexes:) + @NSManaged public func removeFromReceivedMessages(at indexes: NSIndexSet) + + @objc(replaceObjectInReceivedMessagesAtIndex:withObject:) + @NSManaged public func replaceReceivedMessages(at idx: Int, with value: MessageEntity) + + @objc(replaceReceivedMessagesAtIndexes:withReceivedMessages:) + @NSManaged public func replaceReceivedMessages(at indexes: NSIndexSet, with values: [MessageEntity]) + + @objc(addReceivedMessagesObject:) + @NSManaged public func addToReceivedMessages(_ value: MessageEntity) + + @objc(removeReceivedMessagesObject:) + @NSManaged public func removeFromReceivedMessages(_ value: MessageEntity) + + @objc(addReceivedMessages:) + @NSManaged public func addToReceivedMessages(_ values: NSOrderedSet) + + @objc(removeReceivedMessages:) + @NSManaged public func removeFromReceivedMessages(_ values: NSOrderedSet) + +} + +// MARK: Generated accessors for sentMessages +extension UserEntity { + + @objc(insertObject:inSentMessagesAtIndex:) + @NSManaged public func insertIntoSentMessages(_ value: MessageEntity, at idx: Int) + + @objc(removeObjectFromSentMessagesAtIndex:) + @NSManaged public func removeFromSentMessages(at idx: Int) + + @objc(insertSentMessages:atIndexes:) + @NSManaged public func insertIntoSentMessages(_ values: [MessageEntity], at indexes: NSIndexSet) + + @objc(removeSentMessagesAtIndexes:) + @NSManaged public func removeFromSentMessages(at indexes: NSIndexSet) + + @objc(replaceObjectInSentMessagesAtIndex:withObject:) + @NSManaged public func replaceSentMessages(at idx: Int, with value: MessageEntity) + + @objc(replaceSentMessagesAtIndexes:withSentMessages:) + @NSManaged public func replaceSentMessages(at indexes: NSIndexSet, with values: [MessageEntity]) + + @objc(addSentMessagesObject:) + @NSManaged public func addToSentMessages(_ value: MessageEntity) + + @objc(removeSentMessagesObject:) + @NSManaged public func removeFromSentMessages(_ value: MessageEntity) + + @objc(addSentMessages:) + @NSManaged public func addToSentMessages(_ values: NSOrderedSet) + + @objc(removeSentMessages:) + @NSManaged public func removeFromSentMessages(_ values: NSOrderedSet) + +} diff --git a/Meshtastic/Enums/AppSettingsEnums.swift b/Meshtastic/Enums/AppSettingsEnums.swift index 690fe399..b12a10a6 100644 --- a/Meshtastic/Enums/AppSettingsEnums.swift +++ b/Meshtastic/Enums/AppSettingsEnums.swift @@ -51,6 +51,10 @@ enum MeshMapTypes: Int, CaseIterable, Identifiable { } enum MeshMapDistances: Double, CaseIterable, Identifiable { + case twoMiles = 3218.69 + case fiveMiles = 8046.72 + case tenMiles = 16093.4 + case twentyFiveMiles = 40233.6 case fiftyMiles = 80467.2 case oneHundredMiles = 160934 case twoHundredMiles = 321869 diff --git a/Meshtastic/Enums/DeviceEnums.swift b/Meshtastic/Enums/DeviceEnums.swift index 5c980da0..3f76054f 100644 --- a/Meshtastic/Enums/DeviceEnums.swift +++ b/Meshtastic/Enums/DeviceEnums.swift @@ -22,32 +22,35 @@ enum DeviceRoles: Int, CaseIterable, Identifiable { case repeater = 4 case router = 2 case routerClient = 3 + case routerLate = 11 var id: Int { self.rawValue } var name: String { switch self { case .client: - return "Client" + return "device.role.name.client".localized case .clientMute: - return "Client Mute" + return "device.role.name.clientMute".localized case .router: - return "Router" + return "device.role.name.router".localized case .routerClient: - return "Router & Client" + return "device.role.name.routerClient".localized case .repeater: - return "Repeater" + return "device.role.name.repeater".localized case .tracker: - return "Tracker" + return "device.role.name.tracker".localized case .sensor: - return "Sensor" + return "device.role.name.sensor".localized case .tak: - return "TAK" + return "device.role.name.tak".localized case .takTracker: - return "TAK Tracker" + return "device.role.name.takTracker".localized case .clientHidden: - return "Client Hidden" + return "device.role.name.clientHidden".localized case .lostAndFound: - return "Lost and Found" + return "device.role.name.lostAndFound".localized + case .routerLate: + return "device.role.name.routerlate".localized } } @@ -75,6 +78,8 @@ enum DeviceRoles: Int, CaseIterable, Identifiable { return "device.role.clienthidden".localized case .lostAndFound: return "device.role.lostandfound".localized + case .routerLate: + return "device.role.routerlate".localized } } @@ -84,7 +89,7 @@ enum DeviceRoles: Int, CaseIterable, Identifiable { return "apps.iphone" case .clientMute: return "speaker.slash" - case .router, .routerClient: + case .router, .routerClient, .routerLate: return "wifi.router" case .repeater: return "repeat" @@ -127,6 +132,8 @@ enum DeviceRoles: Int, CaseIterable, Identifiable { return Config.DeviceConfig.Role.clientHidden case .lostAndFound: return Config.DeviceConfig.Role.lostAndFound + case .routerLate: + return Config.DeviceConfig.Role.routerLate } } } diff --git a/Meshtastic/Enums/DisplayEnums.swift b/Meshtastic/Enums/DisplayEnums.swift index a540a9d2..8959668a 100644 --- a/Meshtastic/Enums/DisplayEnums.swift +++ b/Meshtastic/Enums/DisplayEnums.swift @@ -149,13 +149,13 @@ enum DisplayModes: Int, CaseIterable, Identifiable { var description: String { switch self { case .defaultMode: - return "Default 128x64 screen layout" + return "default.128x64.screen.layout".localized case .twoColor: - return "Optimized for 2 color displays" + return "optimized.for.2.color.displays".localized case .inverted: - return "Inverted top bar for 2 Color display" + return "inverted.top.bar.for.2.color.display".localized case .color: - return "TFT Full Color Displays" + return "tft.full.color.displays".localized } } func protoEnumValue() -> Config.DisplayConfig.DisplayMode { diff --git a/Meshtastic/Enums/LoraConfigEnums.swift b/Meshtastic/Enums/LoraConfigEnums.swift index 2a2d7090..691a8298 100644 --- a/Meshtastic/Enums/LoraConfigEnums.swift +++ b/Meshtastic/Enums/LoraConfigEnums.swift @@ -83,49 +83,49 @@ enum RegionCodes: Int, CaseIterable, Identifiable { var description: String { switch self { case .unset: - return "Please set a region" + return "please.set.a.region".localized case .us: - return "United States" + return "united.states".localized case .eu433: - return "European Union 433mhz" + return "european.union.433mhz".localized case .eu868: - return "European Union 868mhz" + return "european.union.868mhz".localized case .cn: - return "China" + return "china".localized case .jp: - return "Japan" + return "japan".localized case .anz: - return "Australia / New Zealand" + return "australia.new.zealand".localized case .kr: - return "Korea" + return "korea".localized case .tw: - return "Taiwan" + return "taiwan".localized case .ru: - return "Russia" + return "russia".localized case .in: - return "India" + return "india".localized case .nz865: - return "New Zealand 865mhz" + return "new.zealand.865mhz".localized case .th: - return "Thailand" + return "thailand".localized case .ua433: - return "Ukraine 433mhz" + return "ukraine.433mhz".localized case .ua868: - return "Ukraine 868mhz" + return "ukraine.868mhz".localized case .lora24: - return "2.4 GHZ" + return "2.4ghz".localized case .my433: - return "Malaysia 433mhz" + return "malaysia.433mhz".localized case .my919: - return "Malaysia 919mhz" + return "malaysia.919mhz".localized case .sg923: - return "Singapore 923mhz" + return "singapore.923mhz".localized case .ph433: - return "Philippines 433mhz" + return "philippines.433mhz".localized case .ph868: - return "Philippines 868mhz" + return "philippines.868mhz".localized case .ph915: - return "Philippines 915mhz" + return "philippines.915mhz".localized } } var dutyCycle: Int { @@ -176,6 +176,54 @@ enum RegionCodes: Int, CaseIterable, Identifiable { return 100 } } + var isCountry: Bool { + switch self { + case .unset: + return false + case .us: + return true + case .eu433: + return false + case .eu868: + return false + case .cn: + return true + case .jp: + return true + case .anz: + return false + case .kr: + return true + case .tw: + return true + case .ru: + return true + case .in: + return true + case .nz865: + return true + case .th: + return true + case .ua433: + return true + case .ua868: + return true + case .lora24: + return false + case .my433: + return true + case .my919: + return true + case .sg923: + return true + case .ph433: + return true + case .ph868: + return true + case .ph915: + return true + } + } func protoEnumValue() -> Config.LoRaConfig.RegionCode { switch self { @@ -241,25 +289,25 @@ enum ModemPresets: Int, CaseIterable, Identifiable { var id: Int { self.rawValue } var description: String { - switch self { + switch self { case .longFast: - return "Long Range - Fast" + return "long.range.fast".localized case .longSlow: - return "Long Range - Slow" + return "long.range.slow".localized case .longModerate: - return "Long Range - Moderate" + return "long.range.moderate".localized case .vLongSlow: - return "Very Long Range - Slow" + return "very.long.range.slow".localized case .medSlow: - return "Medium Range - Slow" + return "medium.range.slow".localized case .medFast: - return "Medium Range - Fast" + return "medium.range.fast".localized case .shortSlow: - return "Short Range - Slow" + return "short.range.slow".localized case .shortFast: - return "Short Range - Fast" + return "short.range.fast".localized case .shortTurbo: - return "Short Range - Turbo" + return "short.range.turbo".localized } } var name: String { diff --git a/Meshtastic/Enums/TelemetryEnums.swift b/Meshtastic/Enums/TelemetryEnums.swift index 213d6963..68d65961 100644 --- a/Meshtastic/Enums/TelemetryEnums.swift +++ b/Meshtastic/Enums/TelemetryEnums.swift @@ -20,17 +20,17 @@ enum Aqi: Int, CaseIterable, Identifiable { var description: String { switch self { case .good: - return "Good" + return "telemetry.good".localized case .moderate: - return "Moderate" + return "telemetry.moderate".localized case .sensitive: - return "Unhealthy for Sensitive Groups" + return "telemetry.sensitive".localized case .unhealthy: - return "Unhealthy" + return "telementry.unhealthy".localized case .veryUnhealthy: - return "Very Unhealthy" + return "telementry.veryUnhealthy".localized case .hazardous: - return "Hazardous" + return "telementry.hazardous".localized } } var color: Color { diff --git a/Meshtastic/Export/WriteCsvFile.swift b/Meshtastic/Export/WriteCsvFile.swift index 56776554..40913ea7 100644 --- a/Meshtastic/Export/WriteCsvFile.swift +++ b/Meshtastic/Export/WriteCsvFile.swift @@ -46,6 +46,25 @@ func telemetryToCsvFile(telemetry: [TelemetryEntity], metricsType: Int) -> Strin csvString += ", " csvString += dm.time?.formattedDate(format: dateFormatString) ?? "unknown.age".localized } + } else if metricsType == 2 { + // Create Power Metrics Header + csvString = "Channel 1 Voltage, Channel 1 Current, Channel 2 Voltage, Channel 2 Current, Channel 3 Voltage, Channel 3 Current, \("timestamp".localized)" + for dm in telemetry where dm.metricsType == 2 { + csvString += "\n" + csvString += String(dm.powerCh1Voltage) + csvString += ", " + csvString += String(dm.powerCh1Current) + csvString += ", " + csvString += String(dm.powerCh2Voltage) + csvString += ", " + csvString += String(dm.powerCh2Current) + csvString += ", " + csvString += String(dm.powerCh3Voltage) + csvString += ", " + csvString += String(dm.powerCh3Current) + csvString += ", " + csvString += dm.time?.formattedDate(format: dateFormatString) ?? "unknown.age".localized + } } return csvString } diff --git a/Meshtastic/Extensions/CoreData/NodeInfoEntityExtension.swift b/Meshtastic/Extensions/CoreData/NodeInfoEntityExtension.swift index 7d313191..b1bbb8c6 100644 --- a/Meshtastic/Extensions/CoreData/NodeInfoEntityExtension.swift +++ b/Meshtastic/Extensions/CoreData/NodeInfoEntityExtension.swift @@ -22,6 +22,10 @@ extension NodeInfoEntity { return self.telemetries?.filtered(using: NSPredicate(format: "metricsType == 1")).lastObject as? TelemetryEntity } + var latestPowerMetrics: TelemetryEntity? { + return self.telemetries?.filtered(using: NSPredicate(format: "metricsType == 2")).lastObject as? TelemetryEntity + } + var hasPositions: Bool { return self.positions?.count ?? 0 > 0 } @@ -39,6 +43,11 @@ extension NodeInfoEntity { return user?.sensorMessageList.count ?? 0 > 0 } + var hasPowerMetrics: Bool { + let powerMetrics = telemetries?.filter { ($0 as AnyObject).metricsType == 2 } + return powerMetrics?.count ?? 0 > 0 + } + var hasTraceRoutes: Bool { let routes = traceRoutes?.filter { ($0 as AnyObject).response } return routes?.count ?? 0 > 0 diff --git a/Meshtastic/Extensions/CoreData/PositionEntityExtension.swift b/Meshtastic/Extensions/CoreData/PositionEntityExtension.swift index 90b15b5e..a9eea507 100644 --- a/Meshtastic/Extensions/CoreData/PositionEntityExtension.swift +++ b/Meshtastic/Extensions/CoreData/PositionEntityExtension.swift @@ -13,7 +13,7 @@ import SwiftUI extension PositionEntity { - static func allPositionsFetchRequest() -> NSFetchRequest { + @MainActor static func allPositionsFetchRequest() -> NSFetchRequest { let request: NSFetchRequest = PositionEntity.fetchRequest() request.fetchLimit = 1000 request.returnsObjectsAsFaults = false @@ -22,9 +22,9 @@ extension PositionEntity { request.sortDescriptors = [NSSortDescriptor(key: "time", ascending: false)] let positionPredicate = NSPredicate(format: "nodePosition != nil && (nodePosition.user.shortName != nil || nodePosition.user.shortName != '') && latest == true") - let pointOfInterest = LocationHelper.currentLocation + let pointOfInterest = LocationsHandler.currentLocation - if pointOfInterest.latitude != LocationHelper.DefaultLocation.latitude && pointOfInterest.longitude != LocationHelper.DefaultLocation.longitude { + if pointOfInterest.latitude != LocationsHandler.DefaultLocation.latitude && pointOfInterest.longitude != LocationsHandler.DefaultLocation.longitude { let d: Double = UserDefaults.meshMapDistance * 1.1 let r: Double = 6371009 let meanLatitidue = pointOfInterest.latitude * .pi / 180 @@ -88,7 +88,7 @@ extension PositionEntity { } extension PositionEntity: MKAnnotation { - public var coordinate: CLLocationCoordinate2D { nodeCoordinate ?? LocationHelper.DefaultLocation } + public var coordinate: CLLocationCoordinate2D { nodeCoordinate ?? LocationsHandler.DefaultLocation } public var title: String? { nodePosition?.user?.shortName ?? "unknown".localized } public var subtitle: String? { time?.formatted() } } diff --git a/Meshtastic/Extensions/CoreData/UserEntityExtension.swift b/Meshtastic/Extensions/CoreData/UserEntityExtension.swift index 8c49b322..4030ea6b 100644 --- a/Meshtastic/Extensions/CoreData/UserEntityExtension.swift +++ b/Meshtastic/Extensions/CoreData/UserEntityExtension.swift @@ -33,48 +33,79 @@ extension UserEntity { let unreadMessages = messageList.filter { ($0 as AnyObject).read == false } return unreadMessages.count } - + /// SVG Images for Vendors who are signed project backers var hardwareImage: String? { guard let hwModel else { return nil } switch hwModel { - case "HELTECV1", "HELTECV3", "HELTECV20", "HELTECV21": + /// Heltec + case "HELTECHT62": + return "HELTECHT62" + case "HELTECMESHNODET114": + return "HELTECMESHNODET114" + case "HELTECV3": return "HELTECV3" + case "HELTECVISIONMASTERE213": + return "HELTECVISIONMASTERE213" + case "HELTECVISIONMASTERE290": + return "HELTECVISIONMASTERE290" case "HELTECWIRELESSPAPER", "HELTECWIRELESSPAPERV10": return "HELTECWIRELESSPAPER" case "HELTECWIRELESSTRACKER", "HELTECWIRELESSTRACKERV10": return "HELTECWIRELESSTRACKER" case "HELTECWSLV3": return "HELTECWSLV3" - case "LILYGOTBEAMSCORE": + /// LilyGO + case "TDECK": + return "TDECK" + case "TECHO": + return "TECHO" + case "TWATCHS3": + return "TWATCHS3" + case "LILYGOTBEAMS3CORE": return "LILYGOTBEAMS3CORE" + case "TBEAM", "TBEAM_V0P7": + return "TBEAM" + case "TLORAC6": + return "TLORAC6" + case "TLORAT3S3EPAPER": + return "TLORAT3S3EPAPER" + case "TLORAT3S3V1": + return "TLORAT3S3V1" + case "TLORAV211P6": + return "TLORAV211P6" + case "TLORAV211P8": + return "TLORAV211P8" + /// Seeed Studio + case "SENSECAPINDICATOR": + return "SENSECAPINDICATOR" + case "TRACKERT1000E": + return "TRACKERT1000E" + case "SEEEDXIAOS3": + return "SEEEDXIAOS3" + case "WIOWM1110": + return "WIOWM1110" + /// RAK Wireless + case "RAK4631": + return "RAK4631" + case "RAK11310": + return "RAK11310" + case "WISMESHTAP": + return "WISMESHTAP" + /// B&Q Consulting case "NANOG1", "NANOG1EXPLORER": return "NANOG1" case "NANOG2ULTRA": return "NANOG2ULTRA" - case "RAK4631": - return "RAK4631" - case "RAK11200": - return "RAK11200" - case "SOLAR_NODE": - return "SOLAR_NODE" - case "STATIONG1": - return "STATIONG1" - case "ТВЕАМ", "TBEAMVOP7": - return "ТВЕАМ" - case "TECHO": - return "TECHO" - case "TLORAV1", "TLORAV11P3": - return "TLORAV1" - case "TLORAV2", "TLORAT3S3", "TLORAV211P6", "TLORAV211P8": - return "TLORABOARD" - case "UNPHONE": - return "UNPHONE" + case "STATIONG2": + return "STATIONG2" + /// DIY Devices + case "RPIPICO": + return "RPIPICO" default: return "UNSET" } } } - public func createUser(num: Int64, context: NSManagedObjectContext) -> UserEntity { let newUser = UserEntity(context: context) newUser.num = Int64(num) diff --git a/Meshtastic/Extensions/CoreData/WaypointEntityExtension.swift b/Meshtastic/Extensions/CoreData/WaypointEntityExtension.swift index b17ccecf..2f538b62 100644 --- a/Meshtastic/Extensions/CoreData/WaypointEntityExtension.swift +++ b/Meshtastic/Extensions/CoreData/WaypointEntityExtension.swift @@ -60,7 +60,7 @@ extension WaypointEntity { } extension WaypointEntity: MKAnnotation { - public var coordinate: CLLocationCoordinate2D { waypointCoordinate ?? LocationHelper.DefaultLocation } + public var coordinate: CLLocationCoordinate2D { waypointCoordinate ?? LocationsHandler.DefaultLocation } public var title: String? { name ?? "Dropped Pin" } public var subtitle: String? { (longDescription ?? "") + diff --git a/Meshtastic/Extensions/Date.swift b/Meshtastic/Extensions/Date.swift index 7fc72b33..0736fc63 100644 --- a/Meshtastic/Extensions/Date.swift +++ b/Meshtastic/Extensions/Date.swift @@ -9,6 +9,14 @@ import Foundation extension Date { + var lastHeard: String { + if timeIntervalSince1970 > 0 { + formatted() + } else { + "unknown" + } + } + func formattedDate(format: String) -> String { let dateformat = DateFormatter() dateformat.dateFormat = format diff --git a/Meshtastic/Extensions/String.swift b/Meshtastic/Extensions/String.swift index 4e840a98..b97ad1c5 100644 --- a/Meshtastic/Extensions/String.swift +++ b/Meshtastic/Extensions/String.swift @@ -61,11 +61,10 @@ extension String { } func camelCaseToWords() -> String { - return unicodeScalars.dropFirst().reduce(String(prefix(1))) { - return CharacterSet.uppercaseLetters.contains($1) - ? $0 + " " + String($1) - : $0 + String($1) - } + return self + .replacingOccurrences(of: "([a-z])([A-Z](?=[A-Z])[a-z]*)", with: "$1 $2", options: .regularExpression) + .replacingOccurrences(of: "([A-Z])([A-Z][a-z])", with: "$1 $2", options: .regularExpression) + .replacingOccurrences(of: "([a-z])([A-Z][a-z])", with: "$1 $2", options: .regularExpression) } var length: Int { diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 4d6e2c75..b81c3247 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -27,7 +27,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate @Published var automaticallyReconnect: Bool = true @Published var mqttProxyConnected: Bool = false @Published var mqttError: String = "" - public var minimumVersion = "2.0.0" + public var minimumVersion = "2.3.15" public var connectedVersion: String public var isConnecting: Bool = false public var isConnected: Bool = false @@ -242,14 +242,14 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate if errorCode == 6 { // CBError.Code.connectionTimeout The connection has timed out unexpectedly. // Happens when device is manually reset / powered off lastConnectionError = "🚨" + String.localizedStringWithFormat("ble.errorcode.6 %@".localized, e.localizedDescription) - Logger.services.error("🚨 [BLE] Disconnected: \(peripheral.name ?? "Unknown", privacy: .public) Error Code: \(errorCode, privacy: .public) Error: \(e.localizedDescription, privacy: .public)") + Logger.services.error("🚨 [BLE] Disconnected: \(peripheral.name ?? "Unknown".localized, privacy: .public) Error Code: \(errorCode, privacy: .public) Error: \(e.localizedDescription, privacy: .public)") } else if errorCode == 7 { // CBError.Code.peripheralDisconnected The specified device has disconnected from us. // Seems to be what is received when a tbeam sleeps, immediately recconnecting does not work. if UserDefaults.preferredPeripheralId == peripheral.identifier.uuidString { manager.notifications = [ Notification( id: (peripheral.identifier.uuidString), - title: "Radio Disconnected", + title: "Radio Disconnected".localized, subtitle: "\(peripheral.name ?? "unknown".localized)", content: e.localizedDescription, target: "bluetooth", @@ -258,18 +258,18 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate ] manager.schedule() } - lastConnectionError = "🚨 \(e.localizedDescription)" - Logger.services.error("🚨 [BLE] Disconnected: \(peripheral.name ?? "Unknown", privacy: .public) Error Code: \(errorCode, privacy: .public) Error: \(e.localizedDescription, privacy: .public)") + lastConnectionError = "🚨 \("The specified device has disconnected from us".localized)" + Logger.services.error("🚨 [BLE] Disconnected: \(peripheral.name ?? "Unknown".localized, privacy: .public) Error Code: \(errorCode, privacy: .public) Error: \(e.localizedDescription, privacy: .public)") } else if errorCode == 14 { // Peer removed pairing information // Forgetting and reconnecting seems to be necessary so we need to show the user an error telling them to do that lastConnectionError = "🚨 " + String.localizedStringWithFormat("ble.errorcode.14 %@".localized, e.localizedDescription) - Logger.services.error("🚨 [BLE] Disconnected: \(peripheral.name ?? "Unknown") Error Code: \(errorCode, privacy: .public) Error: \(self.lastConnectionError, privacy: .public)") + Logger.services.error("🚨 [BLE] Disconnected: \(peripheral.name ?? "Unknown".localized) Error Code: \(errorCode, privacy: .public) Error: \(self.lastConnectionError, privacy: .public)") } else { if UserDefaults.preferredPeripheralId == peripheral.identifier.uuidString { manager.notifications = [ Notification( id: (peripheral.identifier.uuidString), - title: "Radio Disconnected", + title: "Radio Disconnected".localized, subtitle: "\(peripheral.name ?? "unknown".localized)", content: e.localizedDescription, target: "bluetooth", @@ -279,12 +279,12 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate manager.schedule() } lastConnectionError = "🚨 \(e.localizedDescription)" - Logger.services.error("🚨 [BLE] Disconnected: \(peripheral.name ?? "Unknown", privacy: .public) Error Code: \(errorCode, privacy: .public) Error: \(e.localizedDescription, privacy: .public)") + Logger.services.error("🚨 [BLE] Disconnected: \(peripheral.name ?? "Unknown".localized, privacy: .public) Error Code: \(errorCode, privacy: .public) Error: \(e.localizedDescription, privacy: .public)") } } else { // Disconnected without error which indicates user intent to disconnect // Happens when swiping to disconnect - Logger.services.info("ℹ️ [BLE] Disconnected: \(peripheral.name ?? "Unknown", privacy: .public): User Initiated Disconnect") + Logger.services.info("ℹ️ [BLE] Disconnected: \(peripheral.name ?? "Unknown".localized, privacy: .public): \(String(describing: "User Initiated Disconnect".localized))") } // Start a scan so the disconnected peripheral is moved to the peripherals[] if it is awake self.startScanning() @@ -765,6 +765,15 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate context: context, appState: appState ) + case .alertApp: + textMessageAppPacket( + packet: decodedInfo.packet, + wantRangeTestPackets: wantRangeTestPackets, + critical: true, + connectedNode: (self.connectedPeripheral != nil ? connectedPeripheral.num : 0), + context: context, + appState: appState + ) case .remoteHardwareApp: MeshLogger.log("🕸️ MESH PACKET received for Remote Hardware App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") case .positionApp: @@ -834,73 +843,81 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate if let routingMessage = try? RouteDiscovery(serializedBytes: decodedInfo.packet.decoded.payload) { let traceRoute = getTraceRoute(id: Int64(decodedInfo.packet.decoded.requestID), context: context) traceRoute?.response = true - if routingMessage.route.count == 0 { - let snr = routingMessage.snrBack.count > 0 ? routingMessage.snrBack[0] / 4 : 0 - traceRoute?.snr = Float(snr) - let logString = String.localizedStringWithFormat("mesh.log.traceroute.received.direct %@".localized, String(snr)) - MeshLogger.log("🪧 \(logString)") - } else { - guard let connectedNode = getNodeInfo(id: Int64(connectedPeripheral.num), context: context) else { - return + guard let connectedNode = getNodeInfo(id: Int64(connectedPeripheral.num), context: context) else { + return + } + var hopNodes: [TraceRouteHopEntity] = [] + let connectedHop = TraceRouteHopEntity(context: context) + connectedHop.time = Date() + connectedHop.num = connectedPeripheral.num + connectedHop.name = connectedNode.user?.longName ?? "???" + // If nil, set to unknown, INT8_MIN (-128) then divide by 4 + connectedHop.snr = Float(routingMessage.snrBack.last ?? -128) / 4 + if let mostRecent = traceRoute?.node?.positions?.lastObject as? PositionEntity, mostRecent.time! >= Calendar.current.date(byAdding: .hour, value: -24, to: Date())! { + connectedHop.altitude = mostRecent.altitude + connectedHop.latitudeI = mostRecent.latitudeI + connectedHop.longitudeI = mostRecent.longitudeI + traceRoute?.hasPositions = true + } + var routeString = "\(connectedNode.user?.longName ?? "???") --> " + hopNodes.append(connectedHop) + traceRoute?.hopsTowards = Int32(routingMessage.route.count) + for (index, node) in routingMessage.route.enumerated() { + var hopNode = getNodeInfo(id: Int64(node), context: context) + if hopNode == nil && hopNode?.num ?? 0 > 0 && node != 4294967295 { + hopNode = createNodeInfo(num: Int64(node), context: context) } - var hopNodes: [TraceRouteHopEntity] = [] - let connectedHop = TraceRouteHopEntity(context: context) - connectedHop.time = Date() - connectedHop.num = connectedPeripheral.num - connectedHop.name = connectedNode.user?.longName ?? "???" - connectedHop.snr = Float(routingMessage.snrBack.last ?? 0 / 4) - if let mostRecent = traceRoute?.node?.positions?.lastObject as? PositionEntity, mostRecent.time! >= Calendar.current.date(byAdding: .hour, value: -24, to: Date())! { - connectedHop.altitude = mostRecent.altitude - connectedHop.latitudeI = mostRecent.latitudeI - connectedHop.longitudeI = mostRecent.longitudeI - traceRoute?.hasPositions = true + let traceRouteHop = TraceRouteHopEntity(context: context) + traceRouteHop.time = Date() + if routingMessage.snrTowards.count >= index + 1 { + traceRouteHop.snr = Float(routingMessage.snrTowards[index]) / 4 + } else { + // If no snr in route, set unknown + traceRouteHop.snr = -32 } - var routeString = "\(connectedNode.user?.longName ?? "???") --> " - hopNodes.append(connectedHop) - traceRoute?.hopsTowards = Int32(routingMessage.route.count) - for (index, node) in routingMessage.route.enumerated() { - var hopNode = getNodeInfo(id: Int64(node), context: context) - if hopNode == nil && hopNode?.num ?? 0 > 0 && node != 4294967295 { - hopNode = createNodeInfo(num: Int64(node), context: context) + if let hn = hopNode, hn.hasPositions { + if let mostRecent = hn.positions?.lastObject as? PositionEntity, mostRecent.time! >= Calendar.current.date(byAdding: .hour, value: -24, to: Date())! { + traceRouteHop.altitude = mostRecent.altitude + traceRouteHop.latitudeI = mostRecent.latitudeI + traceRouteHop.longitudeI = mostRecent.longitudeI + traceRoute?.hasPositions = true } - let traceRouteHop = TraceRouteHopEntity(context: context) - traceRouteHop.time = Date() - if routingMessage.snrTowards.count >= index + 1 { - traceRouteHop.snr = Float(routingMessage.snrTowards[index] / 4) - } - if let hn = hopNode, hn.hasPositions { - if let mostRecent = hn.positions?.lastObject as? PositionEntity, mostRecent.time! >= Calendar.current.date(byAdding: .hour, value: -24, to: Date())! { - traceRouteHop.altitude = mostRecent.altitude - traceRouteHop.latitudeI = mostRecent.latitudeI - traceRouteHop.longitudeI = mostRecent.longitudeI - traceRoute?.hasPositions = true - } - } - traceRouteHop.num = hopNode?.num ?? 0 - if hopNode != nil { - if decodedInfo.packet.rxTime > 0 { - hopNode?.lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(decodedInfo.packet.rxTime))) - } - } - hopNodes.append(traceRouteHop) - routeString += "\(hopNode?.user?.longName ?? (node == 4294967295 ? "Repeater" : String(hopNode?.num.toHex() ?? "unknown".localized))) \(hopNode?.viaMqtt ?? false ? "MQTT" : "") (\(traceRouteHop.snr > 0 ? hopNode?.snr ?? 0.0 : 0.0)dB) --> " } - let destinationHop = TraceRouteHopEntity(context: context) - destinationHop.name = traceRoute?.node?.user?.longName ?? "unknown".localized - destinationHop.time = Date() - destinationHop.snr = Float(routingMessage.snrTowards.last ?? 0 / 4) - destinationHop.num = traceRoute?.node?.num ?? 0 - if let mostRecent = traceRoute?.node?.positions?.lastObject as? PositionEntity, mostRecent.time! >= Calendar.current.date(byAdding: .hour, value: -24, to: Date())! { - destinationHop.altitude = mostRecent.altitude - destinationHop.latitudeI = mostRecent.latitudeI - destinationHop.longitudeI = mostRecent.longitudeI - traceRoute?.hasPositions = true + traceRouteHop.num = hopNode?.num ?? 0 + if hopNode != nil { + if decodedInfo.packet.rxTime > 0 { + hopNode?.lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(decodedInfo.packet.rxTime))) + } } - hopNodes.append(destinationHop) - /// Add the destination node to the end of the route towards string and the beginning of teh route back string - routeString += "\(traceRoute?.node?.user?.longName ?? "unknown".localized) \((traceRoute?.node?.num ?? 0).toHex()) \(traceRoute?.node?.snr ?? 0 > 0 ? traceRoute?.node?.snr ?? 0 : 0.0)dB)" - var routeBackString = "\(traceRoute?.node?.user?.longName ?? "unknown".localized) \((traceRoute?.node?.num ?? 0).toHex()) \(traceRoute?.node?.snr ?? 0 > 0 ? traceRoute?.node?.snr ?? 0 : 0.0)dB) --> " + hopNodes.append(traceRouteHop) + + let hopName = hopNode?.user?.longName ?? (node == 4294967295 ? "Repeater" : String(hopNode?.num.toHex() ?? "unknown".localized)) + let mqttLabel = hopNode?.viaMqtt ?? false ? "MQTT " : "" + let snrLabel = (traceRouteHop.snr != -32) ? String(traceRouteHop.snr) : "unknown ".localized + routeString += "\(hopName) \(mqttLabel)(\(snrLabel)dB) --> " + } + let destinationHop = TraceRouteHopEntity(context: context) + destinationHop.name = traceRoute?.node?.user?.longName ?? "unknown".localized + destinationHop.time = Date() + // If nil, set to unknown, INT8_MIN (-128) then divide by 4 + destinationHop.snr = Float(routingMessage.snrTowards.last ?? -128) / 4 + destinationHop.num = traceRoute?.node?.num ?? 0 + if let mostRecent = traceRoute?.node?.positions?.lastObject as? PositionEntity, mostRecent.time! >= Calendar.current.date(byAdding: .hour, value: -24, to: Date())! { + destinationHop.altitude = mostRecent.altitude + destinationHop.latitudeI = mostRecent.latitudeI + destinationHop.longitudeI = mostRecent.longitudeI + traceRoute?.hasPositions = true + } + hopNodes.append(destinationHop) + /// Add the destination node to the end of the route towards string and the beginning of the route back string + routeString += "\(traceRoute?.node?.user?.longName ?? "unknown".localized) \((traceRoute?.node?.num ?? 0).toHex()) (\(destinationHop.snr != -32 ? String(destinationHop.snr) : "unknown ".localized)dB)" + traceRoute?.routeText = routeString + // Default to -1 only fill in if routeBack is valid below + traceRoute?.hopsBack = -1 + // Only if hopStart is set and there is an SNR entry + if decodedInfo.packet.hopStart > 0 && routingMessage.snrBack.count > 0 { traceRoute?.hopsBack = Int32(routingMessage.routeBack.count) + var routeBackString = "\(traceRoute?.node?.user?.longName ?? "unknown".localized) \((traceRoute?.node?.num ?? 0).toHex()) --> " for (index, node) in routingMessage.routeBack.enumerated() { var hopNode = getNodeInfo(id: Int64(node), context: context) if hopNode == nil && hopNode?.num ?? 0 > 0 && node != 4294967295 { @@ -910,7 +927,10 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate traceRouteHop.time = Date() traceRouteHop.back = true if routingMessage.snrBack.count >= index + 1 { - traceRouteHop.snr = Float(routingMessage.snrBack[index] / 4) + traceRouteHop.snr = Float(routingMessage.snrBack[index]) / 4 + } else { + // If no snr in route, set to unknown + traceRouteHop.snr = -32 } if let hn = hopNode, hn.hasPositions { if let mostRecent = hn.positions?.lastObject as? PositionEntity, mostRecent.time! >= Calendar.current.date(byAdding: .hour, value: -24, to: Date())! { @@ -927,24 +947,45 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } } hopNodes.append(traceRouteHop) - routeBackString += "\(hopNode?.user?.longName ?? (node == 4294967295 ? "Repeater" : String(hopNode?.num.toHex() ?? "unknown".localized))) \(hopNode?.viaMqtt ?? false ? "MQTT" : "") (\(traceRouteHop.snr > 0 ? hopNode?.snr ?? 0.0 : 0.0)dB) --> " + + let hopName = hopNode?.user?.longName ?? (node == 4294967295 ? "Repeater" : String(hopNode?.num.toHex() ?? "unknown".localized)) + let mqttLabel = hopNode?.viaMqtt ?? false ? "MQTT " : "" + let snrLabel = (traceRouteHop.snr != -32) ? String(traceRouteHop.snr) : "unknown ".localized + routeBackString += "\(hopName) \(mqttLabel)(\(snrLabel)dB) --> " } - routeBackString += "\(connectedNode.user?.longName ?? String(connectedNode.num.toHex())) \(connectedNode.snr > 0 ? connectedNode.snr : 0.0)dB)" - traceRoute?.routeText = routeString + // If nil, set to unknown, INT8_MIN (-128) then divide by 4 + let snrBackLast = Float(routingMessage.snrBack.last ?? -128) / 4 + routeBackString += "\(connectedNode.user?.longName ?? String(connectedNode.num.toHex())) (\(snrBackLast != -32 ? String(snrBackLast) : "unknown ".localized)dB)" traceRoute?.routeBackText = routeBackString - traceRoute?.hops = NSOrderedSet(array: hopNodes) - traceRoute?.time = Date() - do { - try context.save() - Logger.data.info("💾 Saved Trace Route") - } catch { - context.rollback() - let nsError = error as NSError - Logger.data.error("Error Updating Core Data TraceRouteHop: \(nsError, privacy: .public)") - } - let logString = String.localizedStringWithFormat("mesh.log.traceroute.received.route %@".localized, routeString) - MeshLogger.log("🪧 \(logString)") } + traceRoute?.hops = NSOrderedSet(array: hopNodes) + traceRoute?.time = Date() + + if let tr = traceRoute { + let manager = LocalNotificationManager() + manager.notifications = [ + Notification( + id: (UUID().uuidString), + title: "Traceroute Complete", + subtitle: "TR received back from \(destinationHop.name ?? "unknown")", + content: "Hops from: \(tr.hopsTowards), Hops back: \(tr.hopsBack)\n\(tr.routeText ?? "unknown".localized)\n\(tr.routeBackText ?? "unknown".localized)", + target: "nodes", + path: "meshtastic:///nodes?nodenum=\(connectedNode.user?.num ?? 0)" + ) + ] + manager.schedule() + } + + do { + try context.save() + Logger.data.info("💾 Saved Trace Route") + } catch { + context.rollback() + let nsError = error as NSError + Logger.data.error("Error Updating Core Data TraceRouteHop: \(nsError, privacy: .public)") + } + let logString = String.localizedStringWithFormat("mesh.log.traceroute.received.route %@".localized, routeString) + MeshLogger.log("🪧 \(logString)") } case .neighborinfoApp: if let neighborInfo = try? NeighborInfo(serializedBytes: decodedInfo.packet.decoded.payload) { @@ -1870,6 +1911,64 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate return false } + public func setIgnoredNode(node: NodeInfoEntity, connectedNodeNum: Int64) -> Bool { + var adminPacket = AdminMessage() + adminPacket.setIgnoredNode = UInt32(node.num) + var meshPacket: MeshPacket = MeshPacket() + meshPacket.to = UInt32(connectedNodeNum) + meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Bool { + var adminPacket = AdminMessage() + adminPacket.removeIgnoredNode = UInt32(node.num) + var meshPacket: MeshPacket = MeshPacket() + meshPacket.to = UInt32(connectedNodeNum) + meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Int64 { var adminPacket = AdminMessage() adminPacket.setHamMode = ham diff --git a/Meshtastic/Helpers/LocalNotificationManager.swift b/Meshtastic/Helpers/LocalNotificationManager.swift index 47f64bce..817c90a4 100644 --- a/Meshtastic/Helpers/LocalNotificationManager.swift +++ b/Meshtastic/Helpers/LocalNotificationManager.swift @@ -67,9 +67,11 @@ class LocalNotificationManager { if notification.userNum != nil { content.userInfo["userNum"] = notification.userNum } + if notification.critical { + content.sound = UNNotificationSound.defaultCritical + } - let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false) - let request = UNNotificationRequest(identifier: notification.id, content: content, trigger: trigger) + let request = UNNotificationRequest(identifier: notification.id, content: content, trigger: nil) UNUserNotificationCenter.current().add(request) { error in if let error { @@ -101,4 +103,5 @@ struct Notification { var messageId: Int64? var channel: Int32? var userNum: Int64? + var critical: Bool = false } diff --git a/Meshtastic/Helpers/LocationsHandler.swift b/Meshtastic/Helpers/LocationsHandler.swift index a215667b..6754891c 100644 --- a/Meshtastic/Helpers/LocationsHandler.swift +++ b/Meshtastic/Helpers/LocationsHandler.swift @@ -113,6 +113,12 @@ import OSLog } static let DefaultLocation = CLLocationCoordinate2D(latitude: 37.3346, longitude: -122.0090) + static var currentLocation: CLLocationCoordinate2D { + guard let location = shared.manager.location else { + return DefaultLocation + } + return location.coordinate + } static var satsInView: Int { var sats = 0 @@ -139,4 +145,5 @@ import OSLog } return sats } + } diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index a5cfa7ba..8c85751f 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -111,6 +111,7 @@ func myInfoPacket (myInfo: MyNodeInfo, peripheralId: String, context: NSManagedO myInfoEntity.peripheralId = peripheralId myInfoEntity.myNodeNum = Int64(myInfo.myNodeNum) myInfoEntity.rebootCount = Int32(myInfo.rebootCount) + myInfoEntity.deviceId = myInfo.deviceID do { try context.save() Logger.data.info("💾 Saved a new myInfo for node: \(myInfo.myNodeNum.toHex(), privacy: .public)") @@ -270,6 +271,7 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje newNode.num = Int64(nodeInfo.num) newNode.channel = Int32(nodeInfo.channel) newNode.favorite = nodeInfo.isFavorite + newNode.ignored = nodeInfo.isIgnored newNode.hopsAway = Int32(nodeInfo.hopsAway) if nodeInfo.hasDeviceMetrics { @@ -358,6 +360,7 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje fetchedNode[0].snr = nodeInfo.snr fetchedNode[0].channel = Int32(nodeInfo.channel) fetchedNode[0].favorite = nodeInfo.isFavorite + fetchedNode[0].ignored = nodeInfo.isIgnored fetchedNode[0].hopsAway = Int32(nodeInfo.hopsAway) if nodeInfo.hasUser { @@ -679,7 +682,7 @@ func telemetryPacket(packet: MeshPacket, connectedNode: Int64, context: NSManage let logString = String.localizedStringWithFormat("mesh.log.telemetry.received %@".localized, String(packet.from)) MeshLogger.log("📈 \(logString)") - 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) { + 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 return } @@ -733,6 +736,38 @@ func telemetryPacket(packet: MeshPacket, connectedNode: Int64, context: NSManage telemetry.numTotalNodes = Int32(truncatingIfNeeded: telemetryMessage.localStats.numTotalNodes) telemetry.metricsType = 4 Logger.statistics.info("📈 [Mesh Statistics] Channel Utilization: \(telemetryMessage.localStats.channelUtilization, privacy: .public) Airtime: \(telemetryMessage.localStats.airUtilTx, privacy: .public) Packets Sent: \(telemetryMessage.localStats.numPacketsTx, privacy: .public) Packets Received: \(telemetryMessage.localStats.numPacketsRx, privacy: .public) Bad Packets Received: \(telemetryMessage.localStats.numPacketsRxBad, privacy: .public) Nodes Online: \(telemetryMessage.localStats.numOnlineNodes, privacy: .public) of \(telemetryMessage.localStats.numTotalNodes, privacy: .public) nodes for Node: \(packet.from.toHex(), privacy: .public)") + } else if telemetryMessage.variant == Telemetry.OneOf_Variant.powerMetrics(telemetryMessage.powerMetrics) { + Logger.data.info("📈 [Power Metrics] Received for Node: \(packet.from.toHex(), privacy: .public)") + + if telemetryMessage.powerMetrics.hasCh1Voltage { + telemetry.powerCh1Voltage = telemetryMessage.powerMetrics.ch1Voltage + telemetry.metricsType = 2 + } + + if telemetryMessage.powerMetrics.hasCh1Current { + telemetry.powerCh1Current = telemetryMessage.powerMetrics.ch1Current + telemetry.metricsType = 2 + } + + if telemetryMessage.powerMetrics.hasCh2Voltage { + telemetry.powerCh2Voltage = telemetryMessage.powerMetrics.ch2Voltage + telemetry.metricsType = 2 + } + + if telemetryMessage.powerMetrics.hasCh1Current { + telemetry.powerCh2Current = telemetryMessage.powerMetrics.ch2Current + telemetry.metricsType = 2 + } + + if telemetryMessage.powerMetrics.hasCh3Voltage { + telemetry.powerCh3Voltage = telemetryMessage.powerMetrics.ch3Voltage + telemetry.metricsType = 2 + } + + if telemetryMessage.powerMetrics.hasCh3Current { + telemetry.powerCh3Current = telemetryMessage.powerMetrics.ch3Current + telemetry.metricsType = 2 + } } telemetry.snr = packet.rxSnr telemetry.rssi = packet.rxRssi @@ -772,7 +807,8 @@ func telemetryPacket(packet: MeshPacket, connectedNode: Int64, context: NSManage } } } else if telemetry.metricsType == 4 { - // Update our live activity if there is one running, not available on mac iOS >= 16.2 + // Update our live activity if there is one running, not available on mac +#if !targetEnvironment(macCatalyst) #if canImport(ActivityKit) let fifteenMinutesLater = Calendar.current.date(byAdding: .minute, value: (Int(15) ), to: Date())! @@ -801,6 +837,7 @@ func telemetryPacket(packet: MeshPacket, connectedNode: Int64, context: NSManage Logger.services.debug("Updated live activity.") } } +#endif #endif } } catch { @@ -816,6 +853,7 @@ func telemetryPacket(packet: MeshPacket, connectedNode: Int64, context: NSManage func textMessageAppPacket( packet: MeshPacket, wantRangeTestPackets: Bool, + critical: Bool = false, connectedNode: Int64, storeForward: Bool = false, context: NSManagedObjectContext, @@ -876,6 +914,9 @@ func textMessageAppPacket( 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 { + /// Make a new to user if they are unknown + newMessage.toUser = createUser(num: Int64(truncatingIfNeeded: packet.to), context: context) } } if fetchedUsers.first(where: { $0.num == packet.from }) != nil { @@ -903,11 +944,14 @@ func textMessageAppPacket( newMessage.fromUser?.publicKey = packet.publicKey } } - if packet.rxTime > 0 { - newMessage.fromUser?.userNode?.lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(packet.rxTime))) - } else { - newMessage.fromUser?.userNode?.lastHeard = Date() - } + } else { + /// Make a new from user if they are unknown + newMessage.fromUser = createUser(num: Int64(truncatingIfNeeded: packet.from), context: context) + } + if packet.rxTime > 0 { + newMessage.fromUser?.userNode?.lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(packet.rxTime))) + } else { + newMessage.fromUser?.userNode?.lastHeard = Date() } newMessage.messagePayload = messageText newMessage.messagePayloadMarkdown = generateMessageMarkdown(message: messageText!) @@ -942,7 +986,8 @@ func textMessageAppPacket( path: "meshtastic:///messages?userNum=\(newMessage.fromUser?.num ?? 0)&messageId=\(newMessage.messageId)", messageId: newMessage.messageId, channel: newMessage.channel, - userNum: Int64(packet.from) + userNum: Int64(packet.from), + critical: critical ) ] manager.schedule() @@ -974,8 +1019,8 @@ func textMessageAppPacket( path: "meshtastic:///messages?channelId=\(newMessage.channel)&messageId=\(newMessage.messageId)", messageId: newMessage.messageId, channel: newMessage.channel, - userNum: Int64(newMessage.fromUser?.userId ?? "0") - ) + userNum: Int64(newMessage.fromUser?.userId ?? "0"), + critical: critical) ] manager.schedule() Logger.services.debug("iOS Notification Scheduled for text message from \(newMessage.fromUser?.longName ?? "unknown".localized)") diff --git a/Meshtastic/Helpers/Mqtt/MqttClientProxyManager.swift b/Meshtastic/Helpers/Mqtt/MqttClientProxyManager.swift index 77cd1088..8fbcaff2 100644 --- a/Meshtastic/Helpers/Mqtt/MqttClientProxyManager.swift +++ b/Meshtastic/Helpers/Mqtt/MqttClientProxyManager.swift @@ -26,7 +26,7 @@ class MqttClientProxyManager { var debugLog = false func connectFromConfigSettings(node: NodeInfoEntity) { let defaultServerAddress = "mqtt.meshtastic.org" - let useSsl = node.mqttConfig?.tlsEnabled == true + var useSsl = node.mqttConfig?.tlsEnabled == true var defaultServerPort = useSsl ? 8883 : 1883 var host = node.mqttConfig?.address if host == nil || host!.isEmpty { @@ -37,17 +37,18 @@ class MqttClientProxyManager { defaultServerPort = Int(fullHost.components(separatedBy: ":")[1]) ?? (useSsl ? 8883 : 1883) } } - let minimumVersion = "2.3.2" - let currentVersion = UserDefaults.firmwareVersion - let supportedVersion = minimumVersion.compare(currentVersion, options: .numeric) == .orderedAscending || minimumVersion.compare(currentVersion, options: .numeric) == .orderedSame if let host = host { let port = defaultServerPort - let username = node.mqttConfig?.username - let password = node.mqttConfig?.password + var username = node.mqttConfig?.username + var password = node.mqttConfig?.password + // if host == defaultServerAddress { + //username = ProcessInfo.processInfo.environment["PUBLIC_MQTT_USERNAME"] + //password = ProcessInfo.processInfo.environment["PUBLIC_MQTT_PASSWORD"] + // } let root = node.mqttConfig?.root?.count ?? 0 > 0 ? node.mqttConfig?.root : "msh" let prefix = root! - topic = prefix + (supportedVersion ? "/2/e" : "/2/c") + "/#" + topic = prefix + "/2/e" + "/#" let qos = CocoaMQTTQoS(rawValue: UInt8(1))! connect(host: host, port: port, useSsl: useSsl, username: username, password: password, topic: topic, qos: qos, cleanSession: true) } diff --git a/Meshtastic/Info.plist b/Meshtastic/Info.plist index dfbd9ce4..a0552164 100644 --- a/Meshtastic/Info.plist +++ b/Meshtastic/Info.plist @@ -71,13 +71,13 @@ NSCameraUsageDescription We use the camera to share channels using a QR Code NSLocationAlwaysAndWhenInUseUsageDescription - We use your location to display it on the mesh map as well as to have GPS coordinatess to send to the connected device. Route Recording uses location in the background. + We use your location to display it on the mesh map as well as to have GPS coordinates to send to the connected device. Route Recording uses location in the background. NSLocationAlwaysUsageDescription - We use your location to display it on the mesh map as well as to have GPS coordinatess to send to the connected device. + We use your location to display it on the mesh map as well as to have GPS coordinates to send to the connected device. NSLocationUsageDescription - We use your location to display it on the mesh map as well as to have GPS coordinatess to send to the connected device. + We use your location to display it on the mesh map as well as to have GPS coordinates to send to the connected device. NSLocationWhenInUseUsageDescription - We use your location to display it on the mesh map as well as to have GPS coordinatess to send to the connected device. + We use your location to display it on the mesh map as well as to have GPS coordinates to send to the connected device. NSSupportsLiveActivities Privacy – Bluetooth Always Usage Description diff --git a/Meshtastic/Meshtastic.entitlements b/Meshtastic/Meshtastic.entitlements index 241de35a..abcef61d 100644 --- a/Meshtastic/Meshtastic.entitlements +++ b/Meshtastic/Meshtastic.entitlements @@ -2,6 +2,8 @@ + com.apple.developer.usernotifications.critical-alerts + com.apple.developer.associated-domains applinks:meshtastic.org/e/* diff --git a/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion b/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion index 6d376a5f..0b4b8e13 100644 --- a/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion +++ b/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion @@ -3,6 +3,6 @@ _XCCurrentVersionName - MeshtasticDataModelV 46.xcdatamodel + MeshtasticDataModelV 49.xcdatamodel diff --git a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 47.xcdatamodel/contents b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 47.xcdatamodel/contents new file mode 100644 index 00000000..095149bd --- /dev/null +++ b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 47.xcdatamodel/contents @@ -0,0 +1,485 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 48.xcdatamodel/contents b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 48.xcdatamodel/contents new file mode 100644 index 00000000..1ad6e791 --- /dev/null +++ b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 48.xcdatamodel/contents @@ -0,0 +1,487 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 49.xcdatamodel/contents b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 49.xcdatamodel/contents new file mode 100644 index 00000000..5f12d9d0 --- /dev/null +++ b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 49.xcdatamodel/contents @@ -0,0 +1,493 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Meshtastic/Model/Metrics Visualization/MetricTableColumn.swift b/Meshtastic/Model/Metrics Visualization/MetricTableColumn.swift new file mode 100644 index 00000000..188e4eba --- /dev/null +++ b/Meshtastic/Model/Metrics Visualization/MetricTableColumn.swift @@ -0,0 +1,84 @@ +// +// SeriesConfigurationEntry.swift +// Meshtastic +// +// Created by Jake Bordens on 12/7/24. +// + +import Charts +import OSLog +import SwiftUI + +// MetricsTableColumn stores metadata about an attribute in TelemetryEntity. +// Given a keypath, this class holds information about how to render the attrbute in +// the table. MetricsTableColumn objects are collected in a MetricsColumnList +class MetricsTableColumn: ObservableObject { + // CoreData Attribute Name on TelemetryEntity + let attribute: String + + // Heading for wider tables + let name: String + + // Heading for space-constrained tables + let abbreviatedName: String + + // Minimum/maximum grid width for this column + let minWidth: CGFloat? + let maxWidth: CGFloat? + + // Recommended spacing, may be overridden + let spacing: CGFloat + // Should this column appear in the table + + var visible: Bool + + // Closure to render the table cell + let tableBodyClosure: (MetricsTableColumn, TelemetryEntity) -> AnyView? + + // Main initializer + init( + keyPath: KeyPath, + name: String, + abbreviatedName: String, + minWidth: CGFloat? = nil, + maxWidth: CGFloat? = nil, + spacing: CGFloat = 0.1, + visible: Bool = true, + @ViewBuilder tableBody: @escaping (MetricsTableColumn, Value) -> TableContent? + ) { + // This works because TelemetryEntity is an NSManagedObject and derrived from NSObject + self.attribute = NSExpression(forKeyPath: keyPath).keyPath + self.name = name + self.abbreviatedName = abbreviatedName + self.minWidth = minWidth + self.maxWidth = maxWidth + self.spacing = spacing + self.visible = visible + self.tableBodyClosure = { config, entity in + AnyView(tableBody(config, entity[keyPath: keyPath])) + } + } + + var gridItemSize: GridItem.Size { + if let minWidth, let maxWidth { + return .flexible(minimum: minWidth, maximum: maxWidth) + } + return .flexible() + } + + func body(_ te: TelemetryEntity) -> AnyView? { + return tableBodyClosure(self, te) + } +} + +extension MetricsTableColumn: Identifiable, Hashable { + var id: String { self.attribute } + + static func == (lhs: MetricsTableColumn, rhs: MetricsTableColumn) -> Bool { + lhs.attribute == rhs.attribute + } + + func hash(into hasher: inout Hasher) { + hasher.combine(attribute) + } +} diff --git a/Meshtastic/Model/Metrics Visualization/MetricsChartSeries.swift b/Meshtastic/Model/Metrics Visualization/MetricsChartSeries.swift new file mode 100644 index 00000000..add0318e --- /dev/null +++ b/Meshtastic/Model/Metrics Visualization/MetricsChartSeries.swift @@ -0,0 +1,113 @@ +// +// MetricsChartSeries.swift +// Meshtastic +// +// Created by Jake Bordens on 12/11/24. +// + +import Charts +import Foundation +import SwiftUI + +// MetricsChartSeries stores metadata about an attribute in TelemetryEntity. +// Given a keypath, this class holds information about how to render the attrbute in a +// the chart. MetricsChartSeries objects are collected in a MetricsSeriesList +class MetricsChartSeries: ObservableObject { + + // CoreData Attribute Name on TelemetryEntity + let attribute: String + + // Heading for areas that have the room + let name: String + + // Heading for space-constrained areas + let abbreviatedName: String + + // Should this column appear in the chart + var visible: Bool + + // A closure that will provide the foreground style given the data set and overall chart range + let foregroundStyle: (ClosedRange?) -> AnyShapeStyle? + + // A closure that will provide the Chart Content for this series + let chartBodyClosure: + (MetricsChartSeries, ClosedRange?, TelemetryEntity) -> AnyChartContent? // Closure to render the chart + + // A closure that will privide the value of a TelemetryEntity for this series + // Possibly converted to the proper units + let valueClosure: (TelemetryEntity) -> Float? + + // Main initializer + init( + keyPath: KeyPath, + name: String, + abbreviatedName: String, + conversion: ((Value) -> Value)? = nil, + visible: Bool = true, + foregroundStyle: @escaping ((ClosedRange?) -> ForegroundStyle?) = { _ in nil }, + @ChartContentBuilder chartBody: @escaping (MetricsChartSeries, ClosedRange?, Date, Value) -> ChartBody? + ) where Value: Plottable & Comparable { + + // This works because TelemetryEntity is an NSManagedObject and derrived from NSObject + self.attribute = NSExpression(forKeyPath: keyPath).keyPath + self.name = name + self.abbreviatedName = abbreviatedName + self.visible = visible + + // By saving these closures, MetricsChartSeries can be type agnostic + // This is a less elegant form of type erasure, but doesn't require a new Any-type + self.foregroundStyle = { range in foregroundStyle(range).map({ AnyShapeStyle($0) }) } + self.chartBodyClosure = { series, range, entity in + AnyChartContent( + chartBody(series, range, entity.time!, entity[keyPath: keyPath])) + } + self.valueClosure = { te in + if let conversion { + return conversion(te[keyPath: keyPath]).floatValue + } + return te[keyPath: keyPath].floatValue + } + } + + // Return the value for this series attribute given a full row of telemetry data + func valueFor(_ te: TelemetryEntity) -> Float? { + return self.valueClosure(te)?.floatValue + } + + // Return the chart content for this series given a full row of telemetry data + func body(_ te: TelemetryEntity, inChartRange chartRange: ClosedRange? = nil) -> AnyChartContent? where T: BinaryFloatingPoint { + let range = chartRange.map { Float($0.lowerBound)...Float($0.upperBound) } + return chartBodyClosure(self, range, te) + } +} + +extension MetricsChartSeries: Identifiable, Hashable { + var id: String { self.attribute } + + static func == (lhs: MetricsChartSeries, rhs: MetricsChartSeries) -> Bool { + lhs.attribute == rhs.attribute + } + + func hash(into hasher: inout Hasher) { + hasher.combine(attribute) + } +} + +extension Plottable { + var floatValue: Float? { + if let integerValue = self.primitivePlottable as? any BinaryInteger { + return Float(integerValue) + } else if let floatingPointValue = self.primitivePlottable as? any BinaryFloatingPoint { + return Float(floatingPointValue) + } + return nil + } + var doubleValue: Double? { + if let integerValue = self.primitivePlottable as? any BinaryInteger { + return Double(integerValue) + } else if let floatingPointValue = self.primitivePlottable as? any BinaryFloatingPoint { + return Double(floatingPointValue) + } + return nil + } +} diff --git a/Meshtastic/Model/Metrics Visualization/MetricsColumnList.swift b/Meshtastic/Model/Metrics Visualization/MetricsColumnList.swift new file mode 100644 index 00000000..ccb0b758 --- /dev/null +++ b/Meshtastic/Model/Metrics Visualization/MetricsColumnList.swift @@ -0,0 +1,98 @@ +// +// SeriesConfiguration.swift +// Meshtastic +// +// Created by Jake Bordens on 12/7/24. +// +import SwiftUI + +class MetricsColumnList: ObservableObject, RandomAccessCollection, RangeReplaceableCollection { + + @Published var columns: [MetricsTableColumn] + + init(columns: [MetricsTableColumn]) { + self.columns = columns + } + + var visible: [MetricsTableColumn] { + return columns.filter { $0.visible } + } + + func toggleVisibity(for column: MetricsTableColumn) { + if columns.contains(column) { + self.objectWillChange.send() + column.visible.toggle() + } + } + + var gridItems: [GridItem] { + var returnValues: [GridItem] = [] + let columnsInChart = self.visible + for i in 0.. MetricsTableColumn? { + return columns.first(where: { $0.attribute == attribute}) + } + + // Collection conformance + typealias Index = Int + typealias Element = MetricsTableColumn + typealias SubSequence = ArraySlice + + required init() { columns = [] } + required init(_ columns: S) where S.Element == Element { + self.columns = Array(columns) + } + + var startIndex: Int { columns.startIndex } + var endIndex: Int { columns.endIndex } + + subscript(position: Int) -> Element { + get { columns[position] } + set { + objectWillChange.send() + columns[position] = newValue + } + } + subscript(bounds: Range) -> ArraySlice { columns[bounds] } + func index(after i: Int) -> Int { columns.index(after: i) } + + func replaceSubrange(_ subrange: Range, with newElements: C) where C.Element == Element { + objectWillChange.send() + columns.replaceSubrange(subrange, with: newElements) + } + + func append(_ newElement: Element) { + columns.append(newElement) + objectWillChange.send() + } + + func remove(at index: Int) -> Element { + objectWillChange.send() + let removedElement = columns.remove(at: index) + return removedElement + } + + func removeAll() { + objectWillChange.send() + columns.removeAll() + } + + func insert(_ newElement: Element, at index: Int) { + objectWillChange.send() + columns.insert(newElement, at: index) + } +} diff --git a/Meshtastic/Model/Metrics Visualization/MetricsSeriesList.swift b/Meshtastic/Model/Metrics Visualization/MetricsSeriesList.swift new file mode 100644 index 00000000..049d1fb4 --- /dev/null +++ b/Meshtastic/Model/Metrics Visualization/MetricsSeriesList.swift @@ -0,0 +1,109 @@ +// +// MetricsChartSeriesList.swift +// Meshtastic +// +// Created by Jake Bordens on 12/11/24. +// + +import Foundation +import SwiftUI +class MetricsSeriesList: ObservableObject, RandomAccessCollection, RangeReplaceableCollection { + + @Published var series: [MetricsChartSeries] + + var visible: [MetricsChartSeries] { + return series.filter { $0.visible } + } + + func toggleVisibity(for aSeries: MetricsChartSeries) { + if series.contains(aSeries) { + self.objectWillChange.send() + aSeries.visible.toggle() + } + } + + func foregroundStyle(forName: String, chartRange: ClosedRange? = nil) -> AnyShapeStyle? where T: BinaryFloatingPoint { + if let selectedSeries = series.first(where: { $0.name == forName }) { + let range = chartRange.map { Float($0.lowerBound)...Float($0.upperBound) } + return selectedSeries.foregroundStyle(range) + } + return nil + } + + func foregroundStyle(forAbbreviatedName: String, chartRange: ClosedRange? = nil) -> AnyShapeStyle? where T: BinaryFloatingPoint { + if let selectedSeries = series.first(where: { $0.abbreviatedName == forAbbreviatedName }) { + let range = chartRange.map { Float($0.lowerBound)...Float($0.upperBound) } + return selectedSeries.foregroundStyle(range) + } + return nil + } + + func chartRange(forData data: [TelemetryEntity]) -> ClosedRange { + var lower: Float? + var upper: Float? + for te in data { + for aSeries in self.visible { + if let value = aSeries.valueFor(te) { + if value > (upper ?? -.infinity) {upper = value} + if value < (lower ?? .infinity) {lower = value} + } + } + } + + // Return default range if no data or nil + guard let lower, let upper else { + return 0.0...100.0 + } + return lower...upper + } + + // Collection conformance + typealias Index = Int + typealias Element = MetricsChartSeries + typealias SubSequence = ArraySlice + + required init() { series = [] } + required init(_ series: S) where S.Element == Element { + self.series = Array(series) + } + + var startIndex: Int { series.startIndex } + var endIndex: Int { series.endIndex } + + subscript(position: Int) -> Element { + get { series[position] } + set { + objectWillChange.send() + series[position] = newValue + } + } + subscript(bounds: Range) -> ArraySlice { series[bounds] } + func index(after i: Int) -> Int { series.index(after: i) } + + func replaceSubrange(_ subrange: Range, with newElements: C) where C.Element == Element { + objectWillChange.send() + series.replaceSubrange(subrange, with: newElements) + } + + func append(_ newElement: Element) { + series.append(newElement) + objectWillChange.send() + } + + func remove(at index: Int) -> Element { + objectWillChange.send() + let removedElement = series.remove(at: index) + return removedElement + } + + func removeAll() { + objectWillChange.send() + series.removeAll() + } + + func insert(_ newElement: Element, at index: Int) { + objectWillChange.send() + series.insert(newElement, at: index) + } + +} diff --git a/Meshtastic/Persistence/UpdateCoreData.swift b/Meshtastic/Persistence/UpdateCoreData.swift index 6ddd664c..15c0d89b 100644 --- a/Meshtastic/Persistence/UpdateCoreData.swift +++ b/Meshtastic/Persistence/UpdateCoreData.swift @@ -199,9 +199,9 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext) manager.notifications = [ Notification( id: (UUID().uuidString), - title: "New Node", + title: "New Node".localized, subtitle: "\(newUser.longName ?? "unknown".localized)", - content: "New Node has been discovered", + content: "New Node has been discovered".localized, target: "nodes", path: "meshtastic:///nodes?nodenum=\(newUser.num)" ) @@ -831,6 +831,10 @@ func upsertSecurityConfigPacket(config: Config.SecurityConfig, nodeNum: Int64, s fetchedNode[0].securityConfig?.privateKey = config.privateKey if config.adminKey.count > 0 { fetchedNode[0].securityConfig?.adminKey = config.adminKey[0] + if config.adminKey.count > 1 { + fetchedNode[0].securityConfig?.adminKey2 = config.adminKey[1] + fetchedNode[0].securityConfig?.adminKey3 = config.adminKey[2] + } } fetchedNode[0].securityConfig?.isManaged = config.isManaged fetchedNode[0].securityConfig?.serialEnabled = config.serialEnabled diff --git a/Meshtastic/Resources/DeviceHardware.json b/Meshtastic/Resources/DeviceHardware.json index 0b88202d..a4a6949c 100644 --- a/Meshtastic/Resources/DeviceHardware.json +++ b/Meshtastic/Resources/DeviceHardware.json @@ -5,7 +5,10 @@ "platformioTarget": "tlora-v2", "architecture": "esp32", "activelySupported": false, - "displayName": "LILYGO T-LoRa V2" + "displayName": "LILYGO T-LoRa V2", + "tags": [ + "LilyGo" + ] }, { "hwModel": 2, @@ -13,7 +16,10 @@ "platformioTarget": "tlora-v1", "architecture": "esp32", "activelySupported": false, - "displayName": "LILYGO T-LoRa V1" + "displayName": "LILYGO T-LoRa V1", + "tags": [ + "LilyGo" + ] }, { "hwModel": 3, @@ -21,7 +27,14 @@ "platformioTarget": "tlora-v2-1-1_6", "architecture": "esp32", "activelySupported": true, - "displayName": "LILYGO T-LoRa V2.1-1.6" + "supportLevel": 1, + "displayName": "LILYGO T-LoRa V2.1-1.6", + "tags": [ + "LilyGo" + ], + "images": [ + "tlora-v2-1-1_6.svg" + ] }, { "hwModel": 4, @@ -29,7 +42,14 @@ "platformioTarget": "tbeam", "architecture": "esp32", "activelySupported": true, - "displayName": "LILYGO T-Beam" + "supportLevel": 1, + "displayName": "LILYGO T-Beam", + "tags": [ + "LilyGo" + ], + "images": [ + "tbeam.svg" + ] }, { "hwModel": 5, @@ -37,7 +57,10 @@ "platformioTarget": "heltec-v2_0", "architecture": "esp32", "activelySupported": false, - "displayName": "Heltec V2.0" + "displayName": "Heltec V2.0", + "tags": [ + "Heltec" + ] }, { "hwModel": 6, @@ -45,15 +68,26 @@ "platformioTarget": "tbeam0_7", "architecture": "esp32", "activelySupported": false, - "displayName": "LILYGO T-Beam V0.7" + "displayName": "LILYGO T-Beam V0.7", + "tags": [ + "LilyGo" + ] }, { "hwModel": 7, "hwModelSlug": "T_ECHO", "platformioTarget": "t-echo", "architecture": "nrf52840", + "supportLevel": 1, "activelySupported": true, - "displayName": "LILYGO T-Echo" + "displayName": "LILYGO T-Echo", + "tags": [ + "LilyGo" + ], + "images": [ + "t-echo.svg" + ], + "requiresDfu": true }, { "hwModel": 8, @@ -61,7 +95,10 @@ "platformioTarget": "tlora-v1_3", "architecture": "esp32", "activelySupported": false, - "displayName": "LILYGO T-LoRa V1.1-1.3" + "displayName": "LILYGO T-LoRa V1.1-1.3", + "tags": [ + "LilyGo" + ] }, { "hwModel": 9, @@ -69,7 +106,16 @@ "platformioTarget": "rak4631", "architecture": "nrf52840", "activelySupported": true, - "displayName": "RAK WisBlock 4631" + "supportLevel": 1, + "displayName": "RAK WisBlock 4631", + "tags": [ + "RAK" + ], + "images": [ + "rak4631.svg", + "rak4631_case.svg" + ], + "requiresDfu": true }, { "hwModel": 10, @@ -77,7 +123,10 @@ "platformioTarget": "heltec-v2_1", "architecture": "esp32", "activelySupported": false, - "displayName": "Heltec V2.1" + "displayName": "Heltec V2.1", + "tags": [ + "Heltec" + ] }, { "hwModel": 11, @@ -85,15 +134,26 @@ "platformioTarget": "heltec-v1", "architecture": "esp32", "activelySupported": false, - "displayName": "Heltec V1" + "displayName": "Heltec V1", + "tags": [ + "Heltec" + ] }, { "hwModel": 12, - "hwModelSlug": "TBEAM_S3_CORE", + "hwModelSlug": "LILYGO_TBEAM_S3_CORE", "platformioTarget": "tbeam-s3-core", "architecture": "esp32-s3", "activelySupported": true, - "displayName": "LILYGO T-Beam S3 Core" + "supportLevel": 1, + "displayName": "LILYGO T-Beam Supreme", + "tags": [ + "LilyGo" + ], + "images": [ + "tbeam-s3-core.svg" + ], + "requiresDfu": true }, { "hwModel": 13, @@ -101,7 +161,10 @@ "platformioTarget": "rak11200", "architecture": "esp32", "activelySupported": false, - "displayName": "RAK WisBlock 11200" + "displayName": "RAK WisBlock 11200", + "tags": [ + "RAK" + ] }, { "hwModel": 14, @@ -109,7 +172,11 @@ "platformioTarget": "nano-g1", "architecture": "esp32", "activelySupported": true, - "displayName": "Nano G1" + "supportLevel": 3, + "displayName": "Nano G1", + "tags": [ + "B&Q" + ] }, { "hwModel": 15, @@ -117,7 +184,15 @@ "platformioTarget": "tlora-v2-1-1_8", "architecture": "esp32", "activelySupported": true, - "displayName": "LILYGO T-LoRa V2.1-1.8" + "supportLevel": 2, + "displayName": "LILYGO T-LoRa V2.1-1.8", + "tags": [ + "LilyGo", + "2.4G LoRA" + ], + "images": [ + "tlora-v2-1-1_8.svg" + ] }, { "hwModel": 16, @@ -125,7 +200,15 @@ "platformioTarget": "tlora-t3s3-v1", "architecture": "esp32-s3", "activelySupported": true, - "displayName": "LILYGO T-LoRa T3-S3" + "displayName": "LILYGO T-LoRa T3-S3", + "supportLevel": 1, + "tags": [ + "LilyGo" + ], + "images": [ + "tlora-t3s3-v1.svg" + ], + "requiresDfu": true }, { "hwModel": 16, @@ -133,7 +216,15 @@ "platformioTarget": "tlora-t3s3-epaper", "architecture": "esp32-s3", "activelySupported": true, - "displayName": "LILYGO T-LoRa T3-S3 E-Paper" + "supportLevel": 1, + "displayName": "LILYGO T-LoRa T3-S3 E-Ink", + "tags": [ + "LilyGo" + ], + "images": [ + "tlora-t3s3-epaper.svg" + ], + "requiresDfu": true }, { "hwModel": 17, @@ -141,7 +232,11 @@ "platformioTarget": "nano-g1-explorer", "architecture": "esp32", "activelySupported": true, - "displayName": "Nano G1 Explorer" + "supportLevel": 3, + "displayName": "Nano G1 Explorer", + "tags": [ + "B&Q" + ] }, { "hwModel": 18, @@ -149,7 +244,15 @@ "platformioTarget": "nano-g2-ultra", "architecture": "nrf52840", "activelySupported": true, - "displayName": "Nano G2 Ultra" + "supportLevel": 2, + "displayName": "Nano G2 Ultra", + "tags": [ + "B&Q" + ], + "requiresDfu": true, + "images": [ + "nano-g2-ultra.svg" + ] }, { "hwModel": 21, @@ -157,7 +260,15 @@ "platformioTarget": "wio-tracker-wm1110", "architecture": "nrf52840", "activelySupported": true, - "displayName": "Seeed Wio WM1110 Tracker" + "supportLevel": 1, + "displayName": "Seeed Wio WM1110 Tracker", + "tags": [ + "Seeed" + ], + "images": [ + "wio-tracker-wm1110.svg" + ], + "requiresDfu": true }, { "hwModel": 25, @@ -165,7 +276,11 @@ "platformioTarget": "station-g1", "architecture": "esp32", "activelySupported": true, - "displayName": "Station G1" + "supportLevel": 3, + "displayName": "Station G1", + "tags": [ + "B&Q" + ] }, { "hwModel": 26, @@ -173,7 +288,15 @@ "platformioTarget": "rak11310", "architecture": "rp2040", "activelySupported": true, - "displayName": "RAK WisBlock 11310" + "supportLevel": 2, + "displayName": "RAK WisBlock 11310", + "tags": [ + "RAK" + ], + "images": [ + "rak11310.svg" + ], + "requiresDfu": true }, { "hwModel": 29, @@ -181,7 +304,12 @@ "platformioTarget": "canaryone", "architecture": "nrf52840", "activelySupported": true, - "displayName": "Canary One" + "supportLevel": 3, + "displayName": "Canary One", + "tags": [ + "Canary" + ], + "requiresDfu": true }, { "hwModel": 30, @@ -189,7 +317,12 @@ "platformioTarget": "rp2040-lora", "architecture": "rp2040", "activelySupported": true, - "displayName": "RP2040 LoRa" + "supportLevel": 2, + "displayName": "RP2040 LoRa", + "tags": [ + "Waveshare" + ], + "requiresDfu": true }, { "hwModel": 31, @@ -197,7 +330,15 @@ "platformioTarget": "station-g2", "architecture": "esp32-s3", "activelySupported": true, - "displayName": "Station G2" + "supportLevel": 2, + "displayName": "Station G2", + "tags": [ + "B&Q" + ], + "requiresDfu": true, + "images": [ + "station-g2.svg" + ] }, { "hwModel": 39, @@ -205,7 +346,14 @@ "platformioTarget": "meshtastic-diy-v1", "architecture": "esp32", "activelySupported": true, - "displayName": "DIY V1" + "supportLevel": 3, + "displayName": "DIY V1", + "tags": [ + "DIY" + ], + "images": [ + "diy.svg" + ] }, { "hwModel": 39, @@ -213,15 +361,22 @@ "platformioTarget": "hydra", "architecture": "esp32", "activelySupported": true, - "displayName": "Hydra" + "supportLevel": 3, + "displayName": "Hydra", + "tags": [ + "DIY" + ] }, { "hwModel": 41, "hwModelSlug": "DR_DEV", "platformioTarget": "meshtastic-dr-dev", "architecture": "esp32", - "activelySupported": true, - "displayName": "DR-DEV" + "activelySupported": false, + "displayName": "DR-DEV", + "tags": [ + "DIY" + ] }, { "hwModel": 42, @@ -229,7 +384,11 @@ "platformioTarget": "m5stack-core", "architecture": "esp32", "activelySupported": true, - "displayName": "M5 Stack" + "supportLevel": 3, + "displayName": "M5 Stack", + "tags": [ + "M5Stack" + ] }, { "hwModel": 43, @@ -237,7 +396,15 @@ "platformioTarget": "heltec-v3", "architecture": "esp32-s3", "activelySupported": true, - "displayName": "Heltec V3" + "supportLevel": 1, + "displayName": "Heltec V3", + "tags": [ + "Heltec" + ], + "images": [ + "heltec-v3.svg", + "heltec-v3-case.svg" + ] }, { "hwModel": 44, @@ -245,7 +412,14 @@ "platformioTarget": "heltec-wsl-v3", "architecture": "esp32-s3", "activelySupported": true, - "displayName": "Heltec Wireless Stick Lite V3" + "supportLevel": 1, + "displayName": "Heltec Wireless Stick Lite V3", + "tags": [ + "Heltec" + ], + "images": [ + "heltec-wsl-v3.svg" + ] }, { "hwModel": 47, @@ -253,7 +427,16 @@ "platformioTarget": "pico", "architecture": "rp2040", "activelySupported": true, - "displayName": "Raspberry Pi Pico" + "supportLevel": 3, + "displayName": "Raspberry Pi Pico", + "tags": [ + "RPi", + "DIY" + ], + "requiresDfu": true, + "images": [ + "pico.svg" + ] }, { "hwModel": 47, @@ -261,7 +444,16 @@ "platformioTarget": "picow", "architecture": "rp2040", "activelySupported": true, - "displayName": "Raspberry Pi Pico W" + "supportLevel": 3, + "displayName": "Raspberry Pi Pico W", + "tags": [ + "RPi", + "DIY" + ], + "requiresDfu": true, + "images": [ + "rpipicow.svg" + ] }, { "hwModel": 48, @@ -269,15 +461,28 @@ "platformioTarget": "heltec-wireless-tracker", "architecture": "esp32-s3", "activelySupported": true, - "displayName": "Heltec Wireless Tracker V1.1" + "supportLevel": 1, + "displayName": "Heltec Wireless Tracker V1.1", + "tags": [ + "Heltec" + ], + "images": [ + "heltec-wireless-tracker.svg" + ], + "requiresDfu": true }, { "hwModel": 58, "hwModelSlug": "HELTEC_WIRELESS_TRACKER_V1_0", "platformioTarget": "heltec-wireless-tracker-V1-0", "architecture": "esp32-s3", - "activelySupported": true, - "displayName": "Heltec Wireless Tracker V1.0" + "activelySupported": false, + "supportLevel": 3, + "displayName": "Heltec Wireless Tracker V1.0", + "images": [ + "heltec-wireless-tracker.svg" + ], + "requiresDfu": true }, { "hwModel": 49, @@ -285,7 +490,14 @@ "platformioTarget": "heltec-wireless-paper", "architecture": "esp32-s3", "activelySupported": true, - "displayName": "Heltec Wireless Paper" + "supportLevel": 1, + "displayName": "Heltec Wireless Paper", + "tags": [ + "Heltec" + ], + "images": [ + "heltec-wireless-paper.svg" + ] }, { "hwModel": 50, @@ -293,7 +505,15 @@ "platformioTarget": "t-deck", "architecture": "esp32-s3", "activelySupported": true, - "displayName": "LILYGO T-Deck" + "supportLevel": 1, + "displayName": "LILYGO T-Deck", + "tags": [ + "LilyGo" + ], + "images": [ + "t-deck.svg" + ], + "requiresDfu": true }, { "hwModel": 51, @@ -301,7 +521,14 @@ "platformioTarget": "t-watch-s3", "architecture": "esp32-s3", "activelySupported": true, - "displayName": "LILYGO T-Watch S3" + "supportLevel": 1, + "displayName": "LILYGO T-Watch S3", + "tags": [ + "LilyGo" + ], + "images": [ + "t-watch-s3.svg" + ] }, { "hwModel": 52, @@ -309,6 +536,7 @@ "platformioTarget": "picomputer-s3", "architecture": "esp32-s3", "activelySupported": true, + "supportLevel": 3, "displayName": "Pi Computer S3" }, { @@ -316,16 +544,30 @@ "hwModelSlug": "HELTEC_HT62", "platformioTarget": "heltec-ht62-esp32c3-sx1262", "architecture": "esp32-c3", + "supportLevel": 1, "activelySupported": true, - "displayName": "Heltec HT62" + "displayName": "Heltec HT62", + "tags": [ + "Heltec" + ], + "images": [ + "heltec-ht62-esp32c3-sx1262.svg" + ] }, { "hwModel": 57, "hwModelSlug": "HELTEC_WIRELESS_PAPER_V1_0", "platformioTarget": "heltec-wireless-paper-v1_0", "architecture": "esp32-s3", - "activelySupported": true, - "displayName": "Heltec Wireless Paper V1.0" + "activelySupported": false, + "supportLevel": 3, + "tags": [ + "Heltec" + ], + "displayName": "Heltec Wireless Paper V1.0", + "images": [ + "heltec-wireless-paper-v1_0.svg" + ] }, { "hwModel": 59, @@ -333,7 +575,9 @@ "platformioTarget": "unphone", "architecture": "esp32-s3", "activelySupported": true, - "displayName": "unPhone" + "supportLevel": 3, + "displayName": "unPhone", + "requiresDfu": true }, { "hwModel": 48, @@ -341,7 +585,9 @@ "platformioTarget": "tracksenger", "architecture": "esp32-s3", "activelySupported": true, - "displayName": "TrackSenger (small TFT)" + "supportLevel": 3, + "displayName": "TrackSenger (small TFT)", + "requiresDfu": true }, { "hwModel": 48, @@ -349,7 +595,9 @@ "platformioTarget": "tracksenger-lcd", "architecture": "esp32-s3", "activelySupported": true, - "displayName": "TrackSenger (big TFT)" + "supportLevel": 3, + "displayName": "TrackSenger (big TFT)", + "requiresDfu": true }, { "hwModel": 48, @@ -357,6 +605,7 @@ "platformioTarget": "tracksenger-oled", "architecture": "esp32-s3", "activelySupported": true, + "supportLevel": 3, "displayName": "TrackSenger (big OLED)" }, { @@ -365,7 +614,12 @@ "platformioTarget": "CDEBYTE_EoRa-S3", "architecture": "esp32-s3", "activelySupported": true, - "displayName": "EBYTE EoRa-S3" + "supportLevel": 3, + "displayName": "EBYTE EoRa-S3", + "tags": [ + "EByte" + ], + "requiresDfu": true }, { "hwModel": 64, @@ -373,7 +627,11 @@ "platformioTarget": "radiomaster_900_bandit_nano", "architecture": "esp32", "activelySupported": true, - "displayName": "RadioMaster 900 Bandit Nano" + "supportLevel": 2, + "displayName": "RadioMaster 900 Bandit Nano", + "tags": [ + "RadioMaster" + ] }, { "hwModel": 66, @@ -381,7 +639,15 @@ "platformioTarget": "heltec-vision-master-t190", "architecture": "esp32-s3", "activelySupported": true, - "displayName": "Heltec Vision Master T190" + "supportLevel": 1, + "displayName": "Heltec Vision Master T190", + "tags": [ + "Heltec" + ], + "images": [ + "heltec-vision-master-t190.svg" + ], + "requiresDfu": true }, { "hwModel": 67, @@ -389,7 +655,15 @@ "platformioTarget": "heltec-vision-master-e213", "architecture": "esp32-s3", "activelySupported": true, - "displayName": "Heltec Vision Master E213" + "supportLevel": 1, + "displayName": "Heltec Vision Master E213", + "tags": [ + "Heltec" + ], + "images": [ + "heltec-vision-master-e213.svg" + ], + "requiresDfu": true }, { "hwModel": 68, @@ -397,15 +671,32 @@ "platformioTarget": "heltec-vision-master-e290", "architecture": "esp32-s3", "activelySupported": true, - "displayName": "Heltec Vision Master E290" + "supportLevel": 1, + "displayName": "Heltec Vision Master E290", + "tags": [ + "Heltec" + ], + "images": [ + "heltec-vision-master-e290.svg" + ], + "requiresDfu": true }, { "hwModel": 69, "hwModelSlug": "HELTEC_MESH_NODE_T114", "platformioTarget": "heltec-mesh-node-t114", "architecture": "nrf52840", - "activelySupported": false, - "displayName": "Heltec Mesh Node T114" + "activelySupported": true, + "supportLevel": 1, + "displayName": "Heltec Mesh Node T114", + "tags": [ + "Heltec" + ], + "images": [ + "heltec-mesh-node-t114.svg", + "heltec-mesh-node-t114-case.svg" + ], + "requiresDfu": true }, { "hwModel": 70, @@ -413,7 +704,14 @@ "platformioTarget": "seeed-sensecap-indicator", "architecture": "esp32-s3", "activelySupported": true, - "displayName": "SenseCAP Indicator" + "supportLevel": 1, + "displayName": "Seeed SenseCAP Indicator", + "tags": [ + "Seeed" + ], + "images": [ + "seeed-sensecap-indicator.svg" + ] }, { "hwModel": 71, @@ -421,7 +719,15 @@ "platformioTarget": "tracker-t1000-e", "architecture": "nrf52840", "activelySupported": true, - "displayName": "Seeed Card Tracker T1000-E" + "supportLevel": 1, + "displayName": "Seeed Card Tracker T1000-E", + "tags": [ + "Seeed" + ], + "images": [ + "tracker-t1000-e.svg" + ], + "requiresDfu": true }, { "hwModel": 72, @@ -429,6 +735,30 @@ "platformioTarget": "seeed-xiao-s3", "architecture": "esp32-s3", "activelySupported": true, - "displayName": "Seeed XIAO S3" + "supportLevel": 1, + "displayName": "Seeed Xiao ESP32-S3", + "tags": [ + "Seeed" + ], + "images": [ + "seeed-xiao-s3.svg" + ], + "requiresDfu": true + }, + { + "hwModel": 84, + "hwModelSlug": "WISMESH_TAP", + "platformioTarget": "rak_wismeshtap", + "architecture": "nrf52840", + "activelySupported": false, + "supportLevel": 1, + "displayName": "RAK WisMesh Tap", + "tags": [ + "RAK" + ], + "images": [ + "rak-wismeshtap.svg" + ], + "requiresDfu": true } ] diff --git a/Meshtastic/Views/Bluetooth/Connect.swift b/Meshtastic/Views/Bluetooth/Connect.swift index be466530..ed9728c6 100644 --- a/Meshtastic/Views/Bluetooth/Connect.swift +++ b/Meshtastic/Views/Bluetooth/Connect.swift @@ -30,7 +30,7 @@ struct Connect: View { let notificationCenter = UNUserNotificationCenter.current() notificationCenter.getNotificationSettings(completionHandler: { (settings) in if settings.authorizationStatus == .notDetermined { - UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { success, error in + UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound, .criticalAlert]) { success, error in if success { Logger.services.info("Notifications are all set!") } else if let error = error { @@ -324,7 +324,8 @@ struct Connect: View { } } } - #if canImport(ActivityKit) +#if !targetEnvironment(macCatalyst) +#if canImport(ActivityKit) func startNodeActivity() { liveActivityStarted = true // 15 Minutes Local Stats Interval @@ -367,8 +368,8 @@ struct Connect: View { } } } - #endif - +#endif +#endif func didDismissSheet() { bleManager.disconnectPeripheral(reconnect: false) } diff --git a/Meshtastic/Views/Bluetooth/InvalidVersion.swift b/Meshtastic/Views/Bluetooth/InvalidVersion.swift index 9c0cca78..ba94e7c1 100644 --- a/Meshtastic/Views/Bluetooth/InvalidVersion.swift +++ b/Meshtastic/Views/Bluetooth/InvalidVersion.swift @@ -41,11 +41,9 @@ struct InvalidVersion: View { .font(.title3) .foregroundColor(.orange) .padding(.bottom) - Text("Version \(minimumVersion) includes breaking changes to devices and the client apps. Only nodes version \(minimumVersion) and above are supported.") + Text("Version \(minimumVersion) includes substantial network optimizations and extensive changes to devices and client apps. Only nodes version \(minimumVersion) and above are supported.") .font(.callout) .padding([.leading, .trailing, .bottom]) - Link("Version 1.2 End of life (EOL) Info", destination: URL(string: "https://meshtastic.org/docs/1.2-End-of-life/")!) - .font(.callout) #if targetEnvironment(macCatalyst) Button { diff --git a/Meshtastic/Views/Helpers/LastHeardText.swift b/Meshtastic/Views/Helpers/LastHeardText.swift deleted file mode 100644 index 5981d9c2..00000000 --- a/Meshtastic/Views/Helpers/LastHeardText.swift +++ /dev/null @@ -1,28 +0,0 @@ -import SwiftUI -// -// LastHeardText.swift -// Meshtastic Apple -// -// Created by Garth Vander Houwen on 5/25/22. -// -struct LastHeardText: View { - var lastHeard: Date? - - var body: some View { - if let lastHeard, lastHeard.timeIntervalSince1970 > 0 { - Text(lastHeard.formatted()) - } else { - Text("unknown") - } - } -} -struct LastHeardText_Previews: PreviewProvider { - static var previews: some View { - LastHeardText(lastHeard: Date()) - .previewLayout(.fixed(width: 300, height: 100)) - .environment(\.locale, .init(identifier: "en")) - LastHeardText(lastHeard: Date()) - .previewLayout(.fixed(width: 300, height: 100)) - .environment(\.locale, .init(identifier: "de")) - } -} diff --git a/Meshtastic/Views/Helpers/LoRaSignalStrengthIndicator.swift b/Meshtastic/Views/Helpers/LoRaSignalStrengthIndicator.swift index 03735ae2..688dcc24 100644 --- a/Meshtastic/Views/Helpers/LoRaSignalStrengthIndicator.swift +++ b/Meshtastic/Views/Helpers/LoRaSignalStrengthIndicator.swift @@ -47,13 +47,13 @@ enum LoRaSignalStrength: Int { var description: String { switch self { case .none: - return "None" + return "lora.signal.strength.none".localized case .bad: - return "Bad" + return "lora.signal.strength.bad".localized case .fair: - return "Fair" + return "lora.signal.strength.fair".localized case .good: - return "Good" + return "lora.signal.strength.good".localized } } } diff --git a/Meshtastic/Views/Helpers/MQTTIcon.swift b/Meshtastic/Views/Helpers/MQTTIcon.swift index 79821dd1..914c6043 100644 --- a/Meshtastic/Views/Helpers/MQTTIcon.swift +++ b/Meshtastic/Views/Helpers/MQTTIcon.swift @@ -27,7 +27,7 @@ struct MQTTIcon: View { .symbolRenderingMode(.hierarchical) }.popover(isPresented: self.$isPopoverOpen, arrowEdge: .bottom, content: { VStack(spacing: 0.5) { - Text("Topic: " + topic) + Text("Topic: \(topic)".localized) .padding(20) Button("close", action: { self.isPopoverOpen = false }).padding([.bottom], 20) } diff --git a/Meshtastic/Views/Helpers/PowerMetrics.swift b/Meshtastic/Views/Helpers/PowerMetrics.swift new file mode 100644 index 00000000..cbff60a2 --- /dev/null +++ b/Meshtastic/Views/Helpers/PowerMetrics.swift @@ -0,0 +1,96 @@ +// +// PowerMetrics.swift +// Meshtastic +// +// Created by Matthew Davies on 1/24/25. +// + +import Foundation +import SwiftUI + +struct PowerMetrics: View { + private let gridItemLayout = Array(repeating: GridItem(.flexible(), spacing: 10), count: 2) + + let metric: TelemetryEntity + + var body: some View { + + LazyVGrid(columns: gridItemLayout) { + + if metric.powerCh1Voltage != nil { + PowerMetricCompactWidget( + type: .voltage, + value: metric.powerCh1Voltage, + title: "Channel 1 Voltage" + ) + } + + if metric.powerCh1Current != nil { + PowerMetricCompactWidget( + type: .current, + value: metric.powerCh1Current, + title: "Channel 1 Current" + ) + } + + if metric.powerCh2Voltage != nil { + PowerMetricCompactWidget( + type: .voltage, + value: metric.powerCh2Voltage, + title: "Channel 2 Voltage" + ) + } + + if metric.powerCh2Current != nil { + PowerMetricCompactWidget( + type: .current, + value: metric.powerCh2Current, + title: "Channel 2 Current" + ) + } + + if metric.powerCh3Voltage != nil { + PowerMetricCompactWidget( + type: .voltage, + value: metric.powerCh3Voltage, + title: "Channel 3 Voltage" + ) + } + + if metric.powerCh3Current != nil { + PowerMetricCompactWidget( + type: .current, + value: metric.powerCh3Current, + title: "Channel 3 Current" + ) + } + } + } +} + +enum PowerMetricType: String { + case current = "current" + case voltage = "voltage" +} + +struct PowerMetricCompactWidget: View { + let type: PowerMetricType + let value: Float + let title: String + var body: some View { + VStack(alignment: .leading) { + HStack(spacing: 5.0) { + Image(systemName: type == .current ? "bolt.fill" : "powerplug.fill") + .foregroundColor(.accentColor) + .font(.callout) + Text(title) + .font(.caption) + } + Text("\(value, specifier: type == .current ? "%.1f" : "%.2f") \(type == .current ? "mA" : "V")") + .font(type == .current ? .system(size: 35) : .system(size: 30)) + } + .frame(minWidth: 100, idealWidth: 125, maxWidth: 150, minHeight: 120, idealHeight: 130, maxHeight: 140) + .padding() + .background(.tertiary, in: RoundedRectangle(cornerRadius: 20, style: .continuous)) + } +} diff --git a/Meshtastic/Views/Helpers/Weather/NodeWeatherForecast.swift b/Meshtastic/Views/Helpers/Weather/NodeWeatherForecast.swift index 15198c50..5fec377e 100644 --- a/Meshtastic/Views/Helpers/Weather/NodeWeatherForecast.swift +++ b/Meshtastic/Views/Helpers/Weather/NodeWeatherForecast.swift @@ -211,7 +211,7 @@ struct NodeWeatherForecast { struct NodeWeatherForecastView_Previews: PreviewProvider { static var previews: some View { - NodeWeatherForecastView(location: CLLocation(latitude: LocationHelper.currentLocation.latitude, longitude: LocationHelper.currentLocation.longitude) ) + NodeWeatherForecastView(location: CLLocation(latitude: LocationsHandler.currentLocation.latitude, longitude: LocationsHandler.currentLocation.longitude) ) .aspectRatio(2, contentMode: .fit) .padding() .previewLayout(.sizeThatFits) diff --git a/Meshtastic/Views/Messages/ChannelMessageList.swift b/Meshtastic/Views/Messages/ChannelMessageList.swift index 1b783427..573a7dc8 100644 --- a/Meshtastic/Views/Messages/ChannelMessageList.swift +++ b/Meshtastic/Views/Messages/ChannelMessageList.swift @@ -127,13 +127,17 @@ struct ChannelMessageList: View { } } } - .padding([.top]) - .scrollDismissesKeyboard(.immediately) + .scrollDismissesKeyboard(.interactively) .onFirstAppear { withAnimation { scrollView.scrollTo(channel.allPrivateMessages.last?.messageId ?? 0, anchor: .bottom) } } + .onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardDidShowNotification)) { _ in + withAnimation { + scrollView.scrollTo(channel.allPrivateMessages.last?.messageId ?? 0, anchor: .bottom) + } + } .onChange(of: channel.allPrivateMessages) { withAnimation { scrollView.scrollTo(channel.allPrivateMessages.last?.messageId ?? 0, anchor: .bottom) diff --git a/Meshtastic/Views/Messages/UserList.swift b/Meshtastic/Views/Messages/UserList.swift index 14c826ed..b0602d5f 100644 --- a/Meshtastic/Views/Messages/UserList.swift +++ b/Meshtastic/Views/Messages/UserList.swift @@ -20,6 +20,7 @@ struct UserList: View { @State private var isOnline = false @State private var isPkiEncrypted = false @State private var isFavorite = false + @State private var isIgnored = false @State private var isEnvironment = false @State private var distanceFilter = false @State private var maxDistance: Double = 800000 @@ -44,8 +45,9 @@ struct UserList: View { NSSortDescriptor(key: "pkiEncrypted", ascending: false), NSSortDescriptor(key: "userNode.lastHeard", ascending: false), NSSortDescriptor(key: "longName", ascending: true)], - predicate: NSPredicate(format: "longName != ''"), - animation: .default + predicate: NSPredicate( + format: "userNode.ignored == false && longName != '' AND NOT (userNode.viaMqtt == YES AND userNode.hopsAway > 0)" + ), animation: .default ) var users: FetchedResults @@ -194,7 +196,7 @@ struct UserList: View { .listStyle(.plain) .navigationTitle(String.localizedStringWithFormat("contacts %@".localized, String(users.count == 0 ? 0 : users.count))) .sheet(isPresented: $editingFilters) { - NodeListFilter(filterTitle: "Contact Filters", viaLora: $viaLora, viaMqtt: $viaMqtt, isOnline: $isOnline, isPkiEncrypted: $isPkiEncrypted, isFavorite: $isFavorite, isEnvironment: $isEnvironment, distanceFilter: $distanceFilter, maximumDistance: $maxDistance, hopsAway: $hopsAway, roleFilter: $roleFilter, deviceRoles: $deviceRoles) + NodeListFilter(filterTitle: "Contact Filters", viaLora: $viaLora, viaMqtt: $viaMqtt, isOnline: $isOnline, isPkiEncrypted: $isPkiEncrypted, isFavorite: $isFavorite, isIgnored: $isIgnored, isEnvironment: $isEnvironment, distanceFilter: $distanceFilter, maximumDistance: $maxDistance, hopsAway: $hopsAway, roleFilter: $roleFilter, deviceRoles: $deviceRoles) } .sheet(isPresented: $showingHelp) { DirectMessagesHelp() @@ -296,7 +298,7 @@ struct UserList: View { let textSearchPredicate = NSCompoundPredicate(type: .or, subpredicates: searchPredicates) /// Create an array of predicates to hold our AND predicates var predicates: [NSPredicate] = [] - /// Mqtt + /// Mqtt and lora if !(viaLora && viaMqtt) { if viaLora { let loraPredicate = NSPredicate(format: "userNode.viaMqtt == NO") @@ -306,9 +308,8 @@ struct UserList: View { predicates.append(mqttPredicate) } } else { - /// Only show mqtt nodes that can be contacted (zero hops) on the default key - // let bothPredicate = NSPredicate(format: "userNode.viaMqtt == YES AND userNode.hopsAway == 0 OR userNode.viaMqtt == NO") - // predicates.append(bothPredicate) + let mqttPredicate = NSPredicate(format: "NOT (userNode.viaMqtt == YES AND userNode.hopsAway > 0)") + predicates.append(mqttPredicate) } /// Roles if roleFilter && deviceRoles.count > 0 { @@ -345,9 +346,9 @@ struct UserList: View { } /// Distance if distanceFilter { - let pointOfInterest = LocationHelper.currentLocation + let pointOfInterest = LocationsHandler.currentLocation - if pointOfInterest.latitude != LocationHelper.DefaultLocation.latitude && pointOfInterest.longitude != LocationHelper.DefaultLocation.longitude { + if pointOfInterest.latitude != LocationsHandler.DefaultLocation.latitude && pointOfInterest.longitude != LocationsHandler.DefaultLocation.longitude { let d: Double = maxDistance * 1.1 let r: Double = 6371009 let meanLatitidue = pointOfInterest.latitude * .pi / 180 diff --git a/Meshtastic/Views/Messages/UserMessageList.swift b/Meshtastic/Views/Messages/UserMessageList.swift index 7ce190fc..68db6280 100644 --- a/Meshtastic/Views/Messages/UserMessageList.swift +++ b/Meshtastic/Views/Messages/UserMessageList.swift @@ -115,13 +115,17 @@ struct UserMessageList: View { } } } - .padding([.top]) - .scrollDismissesKeyboard(.immediately) + .scrollDismissesKeyboard(.interactively) .onFirstAppear { withAnimation { scrollView.scrollTo(user.messageList.last?.messageId ?? 0, anchor: .bottom) } } + .onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardDidShowNotification)) { _ in + withAnimation { + scrollView.scrollTo(user.messageList.last?.messageId ?? 0, anchor: .bottom) + } + } .onChange(of: user.messageList) { withAnimation { scrollView.scrollTo(user.messageList.last?.messageId ?? 0, anchor: .bottom) diff --git a/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift b/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift index 56b0c82c..b7d5449c 100644 --- a/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift +++ b/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift @@ -17,6 +17,11 @@ struct EnvironmentMetricsLog: View { @State var exportString = "" @ObservedObject var node: NodeInfoEntity + @StateObject var columnList = MetricsColumnList.environmentDefaultColumns + @StateObject var seriesList = MetricsSeriesList.environmentDefaultChartSeries + + @State var isEditingColumnConfiguration = false + var body: some View { VStack { if node.hasEnvironmentMetrics { @@ -25,129 +30,70 @@ struct EnvironmentMetricsLog: View { let chartData = environmentMetrics .filter { $0.time != nil && $0.time! >= oneWeekAgo! } .sorted { $0.time! < $1.time! } - 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 chartRange = applyMargins(seriesList.chartRange(forData: chartData)) VStack { if chartData.count > 0 { GroupBox(label: Label("\(environmentMetrics.count) Readings Total", systemImage: "chart.xyaxis.line")) { - Chart { + Chart(seriesList.visible) { series in ForEach(chartData, id: \.time) { dataPoint in - AreaMark( - x: .value("Time", dataPoint.time!), - y: .value("Temperature", dataPoint.temperature.localeTemperature()), - stacking: .unstacked - ) - .interpolationMethod(.cardinal) - .foregroundStyle( - .linearGradient( - colors: [.blue, .yellow, .orange, .red, .red], - startPoint: .bottom, endPoint: .top - ) - .opacity(0.6) - ) - .alignsMarkStylesWithPlotArea() - .accessibilityHidden(true) - LineMark( - x: .value("Time", dataPoint.time!), - y: .value("Temperature", dataPoint.temperature.localeTemperature()) - ) - .interpolationMethod(.cardinal) - .foregroundStyle( - .linearGradient( - colors: [.blue, .yellow, .orange, .red, .red], - startPoint: .bottom, endPoint: .top - ) - ) - .lineStyle(StrokeStyle(lineWidth: 4)) - .alignsMarkStylesWithPlotArea() + series.body(dataPoint, inChartRange: chartRange) } } .chartXAxis(content: { AxisMarks(position: .top) }) - .chartYScale(domain: format == .celsius ? -20...55 : 0...125) - .chartForegroundStyleScale([ - "Temperature": .clear - ]) + .chartYScale(domain: chartRange) + .chartForegroundStyleScale { (seriesName: String) -> AnyShapeStyle in + return seriesList.foregroundStyle(forAbbreviatedName: seriesName, chartRange: chartRange) ?? AnyShapeStyle(Color.clear) + } .chartLegend(position: .automatic, alignment: .bottom) } } - let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMddjmma", options: 0, locale: Locale.current) - let dateFormatString = (localeDateFormat ?? "MM/dd/YY j:mma").replacingOccurrences(of: ",", with: "") + + // Dynamic table column using SwiftUI Table requires TableColumnForEach which requires the target + // to be bumped to 17.4 -- Until that happens, the existing non-configurable table is used. if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac { // Add a table for mac and ipad Table(environmentMetrics) { TableColumn("Temperature") { em in - Text(em.temperature.formattedTemperature()) + columnList.column(forAttribute: "temperature")?.body(em) } TableColumn("Humidity") { em in - Text("\(String(format: "%.0f", em.relativeHumidity))%") + columnList.column(forAttribute: "relativeHumidity")?.body(em) } TableColumn("Barometric Pressure") { em in - Text("\(String(format: "%.1f", em.barometricPressure)) hPa") + columnList.column(forAttribute: "barometricPressure")?.body(em) } TableColumn("Indoor Air Quality") { em in - HStack { - Text("IAQ") - IndoorAirQuality(iaq: Int(em.iaq), displayMode: IaqDisplayMode.dot ) - } + columnList.column(forAttribute: "iaq")?.body(em) } TableColumn("Wind Speed") { em in - let windSpeed = Measurement(value: Double(em.windSpeed), unit: UnitSpeed.kilometersPerHour) - Text(windSpeed.formatted(.measurement(width: .abbreviated, numberFormatStyle: .number.precision(.fractionLength(0))))) + columnList.column(forAttribute: "windSpeed")?.body(em) } TableColumn("Wind Direction") { em in - let direction = cardinalValue(from: Double(em.windDirection)) - Text(direction) + columnList.column(forAttribute: "windDirection")?.body(em) } TableColumn("timestamp") { em in - Text(em.time?.formattedDate(format: dateFormatString) ?? "unknown.age".localized) + columnList.column(forAttribute: "time")?.body(em) } .width(min: 180) } } else { ScrollView { - let columns = [ - GridItem(.flexible(minimum: 30, maximum: 50), spacing: 0.1), - GridItem(.flexible(minimum: 30, maximum: 60), spacing: 0.1), - GridItem(.flexible(minimum: 30, maximum: 60), spacing: 0.1), - GridItem(.flexible(minimum: 30, maximum: 70), spacing: 0.1), - GridItem(spacing: 0) - ] - LazyVGrid(columns: columns, alignment: .leading, spacing: 1, pinnedViews: [.sectionHeaders]) { - + LazyVGrid(columns: columnList.gridItems, alignment: .leading, spacing: 1, pinnedViews: [.sectionHeaders]) { GridRow { - Text("Temp") - .font(.caption) - .fontWeight(.bold) - Text("Hum") - .font(.caption) - .fontWeight(.bold) - Text("Bar") - .font(.caption) - .fontWeight(.bold) - Text("IAQ") - .font(.caption) - .fontWeight(.bold) - Text("timestamp") - .font(.caption) - .fontWeight(.bold) + ForEach(columnList.visible) { col in + Text(col.abbreviatedName) + .font(.caption) + .fontWeight(.bold) + } } ForEach(environmentMetrics, id: \.self) { em in - GridRow { - - Text(em.temperature.formattedTemperature()) - .font(.caption) - Text("\(String(format: "%.0f", em.relativeHumidity))%") - .font(.caption) - Text("\(String(format: "%.1f", em.barometricPressure))") - .font(.caption) - IndoorAirQuality(iaq: Int(em.iaq), displayMode: .dot) - .font(.caption) - Text(em.time?.formattedDate(format: dateFormatString) ?? "unknown.age".localized) - .font(.caption) + ForEach(columnList.visible) { col in + col.body(em) + .font(.caption) + } } } } @@ -157,17 +103,33 @@ struct EnvironmentMetricsLog: View { } } HStack { - + let isPadOrCatalyst = UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac + let buttonSize: ControlSize = isPadOrCatalyst ? .large : .small + let imageScale: Image.Scale = isPadOrCatalyst ? .medium : .small + Button { + self.isEditingColumnConfiguration = true + } label: { + Label("Config", systemImage: "tablecells") + .imageScale(imageScale) + } + .buttonStyle(.bordered) + .buttonBorderShape(.capsule) + .controlSize(buttonSize) + .padding(.bottom) + .padding(.leading) + .sheet(isPresented: self.$isEditingColumnConfiguration) { + MetricsColumnDetail(columnList: columnList, seriesList: seriesList) + } Button(role: .destructive) { isPresentingClearLogConfirm = true } label: { Label("clear.log", systemImage: "trash.fill") + .imageScale(imageScale) } .buttonStyle(.bordered) .buttonBorderShape(.capsule) - .controlSize(.large) + .controlSize(buttonSize) .padding(.bottom) - .padding(.leading) .confirmationDialog( "are.you.sure", isPresented: $isPresentingClearLogConfirm, @@ -184,10 +146,11 @@ struct EnvironmentMetricsLog: View { isExporting = true } label: { Label("save", systemImage: "square.and.arrow.down") + .imageScale(imageScale) } .buttonStyle(.bordered) .buttonBorderShape(.capsule) - .controlSize(.large) + .controlSize(buttonSize) .padding(.bottom) .padding(.trailing) } @@ -219,4 +182,13 @@ struct EnvironmentMetricsLog: View { } ) } + + // Helper. Adds a little buffer to the Y axis range, but keeps Y=0 + func applyMargins(_ range: ClosedRange) -> ClosedRange where T: BinaryFloatingPoint { + let span = range.upperBound - range.lowerBound + let margin = span * 0.1 + let lower = range.lowerBound == 0.0 ? 0.0 : range.lowerBound - margin + let upper = range.upperBound + margin + return lower...upper + } } diff --git a/Meshtastic/Views/Nodes/Helpers/Actions/IgnoreNodeButton.swift b/Meshtastic/Views/Nodes/Helpers/Actions/IgnoreNodeButton.swift new file mode 100644 index 00000000..84fdf4d3 --- /dev/null +++ b/Meshtastic/Views/Nodes/Helpers/Actions/IgnoreNodeButton.swift @@ -0,0 +1,45 @@ +import CoreData +import OSLog +import SwiftUI + +struct IgnoreNodeButton: View { + var bleManager: BLEManager + var context: NSManagedObjectContext + + @ObservedObject + var node: NodeInfoEntity + + var body: some View { + Button(role: .destructive) { + guard let connectedNodeNum = bleManager.connectedPeripheral?.num else { return } + let success = if node.ignored { + bleManager.removeIgnoredNode( + node: node, + connectedNodeNum: Int64(connectedNodeNum) + ) + } else { + bleManager.setIgnoredNode( + node: node, + connectedNodeNum: Int64(connectedNodeNum) + ) + } + if success { + node.ignored = !node.ignored + do { + try context.save() + } catch { + context.rollback() + Logger.data.error("Save Ignored Node Error") + } + Logger.data.debug("Ignored a node") + } + } label: { + Label { + Text(node.ignored ? "Remove from ignored" : "Ignore Node") + } icon: { + Image(systemName: node.ignored ? "minus.circle.fill" : "minus.circle") + .symbolRenderingMode(.multicolor) + } + } + } +} diff --git a/Meshtastic/Views/Nodes/Helpers/Actions/NavigateToButton.swift b/Meshtastic/Views/Nodes/Helpers/Actions/NavigateToButton.swift new file mode 100644 index 00000000..e7a3567e --- /dev/null +++ b/Meshtastic/Views/Nodes/Helpers/Actions/NavigateToButton.swift @@ -0,0 +1,58 @@ +// +// NavigateToButton.swift +// Meshtastic +// +// Created by Benjamin Faershtein on 2/8/25. +// + +import SwiftUI +import CoreLocation +import CoreData +import OSLog + +struct NavigateToButton: View { + var node: NodeInfoEntity + + var body: some View { + Button { + guard let userNum = node.user?.num else { + Logger.services.error("NavigateToAction: Selected node does not exist") + return + } + + Logger.services.info("Fetching NodeInfoEntity for userNum: \(userNum)") + + let fetchRequest: NSFetchRequest = NSFetchRequest(entityName: "NodeInfoEntity") + fetchRequest.predicate = NSPredicate(format: "num == %lld", Int64(userNum)) + + do { + let fetchedNodes = try PersistenceController.shared.container.viewContext.fetch(fetchRequest) + + guard let nodeInfo = fetchedNodes.first else { + Logger.services.error("NavigateToAction: Node with userNum \(userNum) not found in Core Data") + return + } + + if let latitude = nodeInfo.latestPosition?.latitude, + let longitude = nodeInfo.latestPosition?.longitude { + if let url = URL(string: "maps://?saddr=&daddr=\(latitude),\(longitude)") { + UIApplication.shared.open(url, options: [:], completionHandler: nil) + } else { + Logger.services.error("Failed to create URL for navigation") + } + } else { + Logger.services.warning("NavigateToAction: Node \(userNum) has invalid or missing coordinates") + } + } catch { + Logger.services.error("NavigateToAction: Failed to fetch node with userNum \(userNum): \(error.localizedDescription)") + } + } label: { + Label { + Text("Navigate to node") + } icon: { + Image(systemName: "map") + .symbolRenderingMode(.hierarchical) + } + } + } +} diff --git a/Meshtastic/Views/Nodes/Helpers/Map/MapContent/MeshMapContent.swift b/Meshtastic/Views/Nodes/Helpers/Map/MapContent/MeshMapContent.swift index dd49484a..6a0374f2 100644 --- a/Meshtastic/Views/Nodes/Helpers/Map/MapContent/MeshMapContent.swift +++ b/Meshtastic/Views/Nodes/Helpers/Map/MapContent/MeshMapContent.swift @@ -85,7 +85,7 @@ struct MeshMapContent: MapContent { let nodePositions = Array(positions) as? [PositionEntity] { if showRouteLines { let routeCoords = nodePositions.compactMap({(pos) -> CLLocationCoordinate2D in - return pos.nodeCoordinate ?? LocationHelper.DefaultLocation + return pos.nodeCoordinate ?? LocationsHandler.DefaultLocation }) let gradient = LinearGradient( colors: [Color(nodeColor.lighter().lighter()), Color(nodeColor.lighter()), Color(nodeColor)], @@ -148,9 +148,9 @@ struct MeshMapContent: MapContent { ForEach(routes) { route in if let routeLocations = route.locations, let locations = Array(routeLocations) as? [LocationEntity] { let routeCoords = locations.compactMap {(loc) -> CLLocationCoordinate2D in - return loc.locationCoordinate ?? LocationHelper.DefaultLocation + return loc.locationCoordinate ?? LocationsHandler.DefaultLocation } - Annotation("Start", coordinate: routeCoords.first ?? LocationHelper.DefaultLocation) { + Annotation("Start", coordinate: routeCoords.first ?? LocationsHandler.DefaultLocation) { ZStack { Circle() .fill(Color(.green)) @@ -159,7 +159,7 @@ struct MeshMapContent: MapContent { } } .annotationTitles(.automatic) - Annotation("Finish", coordinate: routeCoords.last ?? LocationHelper.DefaultLocation) { + Annotation("Finish", coordinate: routeCoords.last ?? LocationsHandler.DefaultLocation) { ZStack { Circle() .fill(Color(.black)) diff --git a/Meshtastic/Views/Nodes/Helpers/Map/PositionPopover.swift b/Meshtastic/Views/Nodes/Helpers/Map/PositionPopover.swift index fb019e0b..f68354c0 100644 --- a/Meshtastic/Views/Nodes/Helpers/Map/PositionPopover.swift +++ b/Meshtastic/Views/Nodes/Helpers/Map/PositionPopover.swift @@ -55,7 +55,7 @@ struct PositionPopover: View { if idiom != .phone { Text("heard".localized + ":") } - LastHeardText(lastHeard: position.time) + Text(position.time?.lastHeard ?? "unknown") .foregroundColor(.primary) .font(idiom == .phone ? .callout : .body) .allowsTightening(/*@START_MENU_TOKEN@*/true/*@END_MENU_TOKEN@*/) diff --git a/Meshtastic/Views/Nodes/Helpers/Map/WaypointForm.swift b/Meshtastic/Views/Nodes/Helpers/Map/WaypointForm.swift index 71d62590..2c2c267a 100644 --- a/Meshtastic/Views/Nodes/Helpers/Map/WaypointForm.swift +++ b/Meshtastic/Views/Nodes/Helpers/Map/WaypointForm.swift @@ -38,7 +38,7 @@ struct WaypointForm: View { .font(.largeTitle) Divider() Form { - let distance = CLLocation(latitude: LocationHelper.currentLocation.latitude, longitude: LocationHelper.currentLocation.longitude).distance(from: CLLocation(latitude: waypoint.coordinate.latitude, longitude: waypoint.coordinate.longitude )) + let distance = CLLocation(latitude: LocationsHandler.currentLocation.latitude, longitude: LocationsHandler.currentLocation.longitude).distance(from: CLLocation(latitude: waypoint.coordinate.latitude, longitude: waypoint.coordinate.longitude )) Section(header: Text("Coordinate") ) { HStack { Text("Location:") @@ -335,8 +335,8 @@ struct WaypointForm: View { .padding(.bottom, 5) } /// Distance - if LocationHelper.currentLocation.distance(from: LocationHelper.DefaultLocation) > 0.0 { - let metersAway = waypoint.coordinate.distance(from: LocationHelper.currentLocation) + if LocationsHandler.currentLocation.distance(from: LocationsHandler.DefaultLocation) > 0.0 { + let metersAway = waypoint.coordinate.distance(from: LocationsHandler.currentLocation) Label { Text("distance".localized + ": \(distanceFormatter.string(fromDistance: Double(metersAway)))") .foregroundColor(.primary) diff --git a/Meshtastic/Views/Nodes/Helpers/Metrics Columns/EnviornmentDefaultSeries.swift b/Meshtastic/Views/Nodes/Helpers/Metrics Columns/EnviornmentDefaultSeries.swift new file mode 100644 index 00000000..810eaab7 --- /dev/null +++ b/Meshtastic/Views/Nodes/Helpers/Metrics Columns/EnviornmentDefaultSeries.swift @@ -0,0 +1,216 @@ +// +// 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( + keyPath: \.temperature, + name: "Temperature", + abbreviatedName: "Temp", + conversion: { 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 + 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( + 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 + 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( + 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 + 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( + keyPath: \.iaq, + name: "Indoor Air Quality", + abbreviatedName: "IAQ", + visible: false, + foregroundStyle: { _ in .gray }, + chartBody: { series, _, time, iaq in + 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( + 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 + // 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 { + Image(systemName: "location.north.circle.fill") + .symbolRenderingMode(.palette) + .foregroundStyle(Color.white, Color(UIColor.yellow.darker(componentDelta: 0.3))) + .rotationEffect( + .degrees(Double(wsad.windDirection))) + }.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 { + return WindSpeedAndDirection( + windSpeed: self.windSpeed, windDirection: self.windDirection) + } +} + +// From: https://github.com/meshtastic/Meshtastic-Apple/pull/1013/commits/bc932567c742c8fa9fd30752237b10cb762c5ef3 +// Set up gradient stops relative to the scale of the temperature chart +func generateStops(minTemp: Double, maxTemp: Double, tempUnit: UnitTemperature, opacity: Double) -> [Gradient.Stop] { + var gradientStops = [Gradient.Stop]() + + let stopTargets: [(Double, Color)] = [ + ((tempUnit == .celsius ? 0 : 32), .blue), + ((tempUnit == .celsius ? 20 : 68), .yellow), + ((tempUnit == .celsius ? 30 : 86), .orange), + ((tempUnit == .celsius ? 55 : 125), .red) + ] + for (stopValue, color) in stopTargets { + let stopLocation = transform(stopValue, from: minTemp...maxTemp, to: 0...1) + gradientStops.append(Gradient.Stop(color: color.opacity(opacity), location: stopLocation)) + } + return gradientStops +} + +// Map inputRange to outputRange +func transform(_ input: T, from inputRange: ClosedRange, to outputRange: ClosedRange) -> T { + // need to determine what that value would be in (to.low, to.high) + // difference in output range / difference in input range = slope + let slope = (outputRange.upperBound - outputRange.lowerBound) / (inputRange.upperBound - inputRange.lowerBound) + // slope * normalized input + output lower + let output = slope * (input - inputRange.lowerBound) + outputRange.lowerBound + return output +} diff --git a/Meshtastic/Views/Nodes/Helpers/Metrics Columns/EnvironmentDefaultColumns.swift b/Meshtastic/Views/Nodes/Helpers/Metrics Columns/EnvironmentDefaultColumns.swift new file mode 100644 index 00000000..7df0dbd3 --- /dev/null +++ b/Meshtastic/Views/Nodes/Helpers/Metrics Columns/EnvironmentDefaultColumns.swift @@ -0,0 +1,123 @@ +// +// EnvironmentDefaultColumns.swift +// Meshtastic +// +// Created by Jake Bordens on 12/10/24. +// + +import Charts +import Foundation +import SwiftUI + +// This is the default configuration used by the EnvironmentMetricsLog view for the table +extension MetricsColumnList { + static var environmentDefaultColumns: MetricsColumnList { + MetricsColumnList(columns: [ + // Temperature Series Configuration + MetricsTableColumn( + keyPath: \.temperature, + name: "Temperature", + abbreviatedName: "Temp", + minWidth: 30, maxWidth: 45, + tableBody: { _, temp in + Text(temp.formattedTemperature()) + }), + + // Relative Humidity Series Configuration + MetricsTableColumn( + keyPath: \.relativeHumidity, + name: "Relative Humidity", + abbreviatedName: "Hum", + minWidth: 30, maxWidth: 45, + tableBody: { _, humidity in + Text("\(String(format: "%.0f", humidity))%") + }), + + // Barometric Pressure Series Configuration + MetricsTableColumn( + keyPath: \.barometricPressure, + name: "Barometric Pressure", + abbreviatedName: "Bar", + minWidth: 30, maxWidth: 50, + tableBody: { _, pressure in + if (UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac) { + Text("\(String(format: "%.1f hPa", pressure))") + } else { + Text("\(String(format: "%.1f", pressure))") + } + }), + + // Indoor Air Quality Series Configuration + MetricsTableColumn( + keyPath: \.iaq, + name: "Indoor Air Quality", + abbreviatedName: "IAQ", + minWidth: 30, maxWidth: 50, + tableBody: { _, iaq in + IndoorAirQuality(iaq: Int(iaq), displayMode: .dot) + }), + + // Wind Direction Series Configuration + MetricsTableColumn( + keyPath: \.windDirection, + name: "Wind Direction", + abbreviatedName: "Dir", + minWidth: 30, maxWidth: 40, + visible: false, + tableBody: { _, wind in + HStack(spacing: 1.0) { + // debug data: let wind = Double.random(in: 0..<360.0) + let wind = Double(wind) + Image(systemName: "location.north") + .imageScale(.small) + .scaleEffect(0.9, anchor: .center) + .rotationEffect(.degrees(wind)) + .foregroundStyle(.blue) + if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac { + Text(cardinalValue(from: wind)) + } else { + Text(abbreviatedCardinalValue(from: wind)) + } + } + }), + + // Wind Speed Series Configuration + MetricsTableColumn( + keyPath: \.windSpeed, + name: "Wind Speed", + abbreviatedName: "Wind", + minWidth: 30, maxWidth: 60, + visible: false, + tableBody: { _, speed in + let windSpeed = Measurement( + value: Double(speed), unit: UnitSpeed.kilometersPerHour) + Text( + windSpeed.formatted( + .measurement( + width: .abbreviated, + numberFormatStyle: .number.precision( + .fractionLength(0)))) + ) + }), + + // Timestamp Series Configuration -- for use in table only + MetricsTableColumn( + keyPath: \.time, + name: "Timestamp", + abbreviatedName: "Time", + minWidth: 140.0, maxWidth: 2000.0, + tableBody: { _, time in + let localeDateFormat = DateFormatter.dateFormat( + fromTemplate: "yyMMddjmma", options: 0, + locale: Locale.current) + let dateFormatString = + (localeDateFormat ?? "MM/dd/YY j:mma") + .replacingOccurrences(of: ",", with: "") + Text( + time?.formattedDate(format: dateFormatString) + ?? "unknown.age".localized + ) + }) + ]) + } +} diff --git a/Meshtastic/Views/Nodes/Helpers/Metrics Columns/MetricsColumnDetail.swift b/Meshtastic/Views/Nodes/Helpers/Metrics Columns/MetricsColumnDetail.swift new file mode 100644 index 00000000..1f384cb2 --- /dev/null +++ b/Meshtastic/Views/Nodes/Helpers/Metrics Columns/MetricsColumnDetail.swift @@ -0,0 +1,80 @@ +// +// MetricsColumnDetail.swift +// Meshtastic +// +// Created by Jake Bordens on 12/10/24. +// + +import SwiftUI + +struct MetricsColumnDetail: View { + @ObservedObject var columnList: MetricsColumnList + @ObservedObject var seriesList: MetricsSeriesList + + @State private var currentDetent = PresentationDetent.medium + + @Environment(\.dismiss) private var dismiss + + var body: some View { + NavigationStack { + Form { + Section("Chart") { + ForEach(seriesList) { series in + HStack { + Circle() + .fill(series.foregroundStyle(0.0...100.0) ?? AnyShapeStyle(.clear)) + .frame(width: 20.0, height: 20.0) + Text(series.name) + Spacer() + if series.visible { + Image(systemName: "checkmark") + .foregroundColor(.blue) + } + }.contentShape(Rectangle()) // Ensures the entire row is tappable + .onTapGesture { + seriesList.toggleVisibity(for: series) + } + } + } + // Dynamic table column using SwiftUI Table requires TableColumnForEach which requires the target + // to be bumped to 17.4 -- Until that happens, the existing non-configurable table is used. + if !(UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac) { + Section("Table") { + ForEach(columnList.columns) { column in + HStack { + Text(column.name) + Spacer() + if column.visible { + Image(systemName: "checkmark") + .foregroundColor(.blue) + } + }.contentShape(Rectangle()) // Ensures the entire row is tappable + .onTapGesture { + columnList.objectWillChange.send() + columnList.toggleVisibity(for: column) + } + } + } + } + } + .listStyle(.insetGrouped) +#if targetEnvironment(macCatalyst) + Spacer() + Button { + dismiss() + } label: { + Label("close", systemImage: "xmark") + } + .buttonStyle(.bordered) + .buttonBorderShape(.capsule) + .controlSize(.large) + .padding(.bottom) +#endif + } + .presentationDetents([.medium, .large], selection: $currentDetent) + .presentationContentInteraction(.scrolls) + .presentationDragIndicator(.visible) + .presentationBackgroundInteraction(.enabled(upThrough: .medium)) + .interactiveDismissDisabled(false) + } +} diff --git a/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift b/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift index a9f7bcfe..e6348d5a 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift @@ -16,6 +16,9 @@ struct NodeDetail: View { formatter.unitsStyle = .full return formatter }() + var modemPreset: ModemPresets = ModemPresets( + rawValue: UserDefaults.modemPreset + ) ?? ModemPresets.longFast @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager @@ -44,6 +47,34 @@ struct NodeDetail: View { NodeInfoItem(node: node) } Section("Node") { + HStack(alignment: .center) { + Spacer() + CircleText( + text: node.user?.shortName ?? "?", + color: Color(UIColor(hex: UInt32(node.num))), + circleSize: 75 + ) + if node.snr != 0 && !node.viaMqtt && node.hopsAway == 0 { + Spacer() + VStack { + let signalStrength = getLoRaSignalStrength(snr: node.snr, rssi: node.rssi, preset: modemPreset) + LoRaSignalStrengthIndicator(signalStrength: signalStrength) + Text("Signal \(signalStrength.description)").font(.footnote) + Text("SNR \(String(format: "%.2f", node.snr))dB") + .foregroundColor(getSnrColor(snr: node.snr, preset: modemPreset)) + .font(.caption) + Text("RSSI \(node.rssi)dB") + .foregroundColor(getRssiColor(rssi: node.rssi)) + .font(.caption) + } + } + if node.telemetries?.count ?? 0 > 0 { + Spacer() + BatteryGauge(node: node) + } + Spacer() + } + .listRowSeparator(.hidden) if let user = node.user { if !user.keyMatch { Label { @@ -206,6 +237,15 @@ struct NodeDetail: View { } } } + if node.hasPowerMetrics && node.latestPowerMetrics != nil { + Section("Power") { + VStack { + if let metric = node.latestPowerMetrics { + PowerMetrics(metric: metric) + } + } + } + } Section("Logs") { // Metrics NavigationLink { @@ -220,6 +260,18 @@ struct NodeDetail: View { } .disabled(!node.hasDeviceMetrics) + NavigationLink { + PowerMetricsLog(node: node) + } label: { + Label { + Text("Power Metrics Log") + } icon: { + Image(systemName: "bolt") + .symbolRenderingMode(.multicolor) + } + } + .disabled(!node.hasPowerMetrics) + NavigationLink { NodeMapSwiftUI(node: node, showUserLocation: connectedNode?.num ?? 0 == node.num) } label: { @@ -326,6 +378,14 @@ struct NodeDetail: View { node: node ) } + if node.hasPositions { + NavigateToButton(node: node) + } + IgnoreNodeButton( + bleManager: bleManager, + context: context, + node: node + ) DeleteNodeButton( bleManager: bleManager, context: context, @@ -434,3 +494,28 @@ func cardinalValue(from heading: Double) -> String { return "" } } + +func abbreviatedCardinalValue(from heading: Double) -> String { + switch heading { + case 0 ..< 22.5: + return "N" + case 22.5 ..< 67.5: + return "NE" + case 67.5 ..< 112.5: + return "E" + case 112.5 ..< 157.5: + return "E" + case 157.5 ..< 202.5: + return "S" + case 202.5 ..< 247.5: + return "SW" + case 247.5 ..< 292.5: + return "W" + case 292.5 ..< 337.5: + return "NW" + case 337.5 ... 360.0: + return "N" + default: + return "" + } +} diff --git a/Meshtastic/Views/Nodes/Helpers/NodeInfoItem.swift b/Meshtastic/Views/Nodes/Helpers/NodeInfoItem.swift index e0da484e..9f3b8105 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeInfoItem.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeInfoItem.swift @@ -12,61 +12,71 @@ import MapKit struct NodeInfoItem: View { @ObservedObject var node: NodeInfoEntity - - var modemPreset: ModemPresets = ModemPresets( - rawValue: UserDefaults.modemPreset - ) ?? ModemPresets.longFast + @State private var currentDevice: DeviceHardware? var body: some View { + if let user = node.user { ViewThatFits(in: .horizontal) { - VStack { - if let user = node.user { - HStack(alignment: .center) { - if user.hwModel != "UNSET" { - Image(user.hardwareImage ?? "UNSET") + HStack { + Spacer() + if user.hwModel != "UNSET" { + VStack(alignment: .center) { + Spacer() + Image(systemName: currentDevice?.activelySupported ?? false ? "checkmark.seal.fill" : "x.circle") .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 65, height: 65) - .cornerRadius(5) - Text(String(node.user?.hwDisplayName ?? (node.user?.hwModel ?? "unset".localized))) - .font(.callout) - } else { - Image(systemName: "person.crop.circle.badge.questionmark") - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 65, height: 65) - .cornerRadius(5) - Text(String("incomplete".localized)) + .aspectRatio(contentMode: .fill) + .frame(width: 75, height: 75) + .foregroundStyle(currentDevice?.activelySupported ?? false ? .green : .red) + Text( currentDevice?.activelySupported ?? false ? "Supported" : "Unsupported") + .foregroundStyle(.gray) .font(.callout) } + Spacer() + } + VStack(alignment: .center) { + HStack { + if user.hardwareImage != "UNSET" { + Image(user.hardwareImage ?? "UNSET") + .resizable() + .aspectRatio(contentMode: .fit) + .frame(maxHeight: 150) + .cornerRadius(5) + } else { + Image(systemName: "person.crop.circle.badge.questionmark") + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 75, height: 75) + .cornerRadius(5) + } + } + } + Spacer() + } + .onAppear { + Api().loadDeviceHardwareData { (hw) in + for device in hw { + let currentHardware = node.user?.hwModel ?? "UNSET" + let deviceString = device.hwModelSlug.replacingOccurrences(of: "_", with: "").uppercased() + if deviceString == currentHardware { + currentDevice = device + } + } } } - HStack(alignment: .center) { - Spacer() - CircleText( - text: node.user?.shortName ?? "?", - color: Color(UIColor(hex: UInt32(node.num))), - circleSize: 75 - ) - if node.snr != 0 && !node.viaMqtt && node.hopsAway == 0 { - Spacer() - VStack { - let signalStrength = getLoRaSignalStrength(snr: node.snr, rssi: node.rssi, preset: modemPreset) - LoRaSignalStrengthIndicator(signalStrength: signalStrength) - Text("Signal \(signalStrength.description)").font(.footnote) - Text("SNR \(String(format: "%.2f", node.snr))dB") - .foregroundColor(getSnrColor(snr: node.snr, preset: modemPreset)) - .font(.caption) - Text("RSSI \(node.rssi)dB") - .foregroundColor(getRssiColor(rssi: node.rssi)) - .font(.caption) - } - } - if node.telemetries?.count ?? 0 > 0 { - Spacer() - BatteryGauge(node: node) - } - Spacer() + } + .listRowSeparator(.hidden) + HStack { + Label { + Text("Model") + } icon: { + Image(systemName: "flipphone") + .symbolRenderingMode(.hierarchical) + } + Spacer() + if user.hwModel != "UNSET" { + Text(String(node.user?.hwDisplayName ?? (node.user?.hwModel ?? "unset".localized))) + } else { + Text(String("incomplete".localized)) } } } diff --git a/Meshtastic/Views/Nodes/Helpers/NodeListFilter.swift b/Meshtastic/Views/Nodes/Helpers/NodeListFilter.swift index 210e58d1..454b3607 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeListFilter.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeListFilter.swift @@ -17,6 +17,7 @@ struct NodeListFilter: View { @Binding var isOnline: Bool @Binding var isPkiEncrypted: Bool @Binding var isFavorite: Bool + @Binding var isIgnored: Bool @Binding var isEnvironment: Bool @Binding var distanceFilter: Bool @Binding var maximumDistance: Double @@ -90,6 +91,18 @@ struct NodeListFilter: View { } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) .listRowSeparator(.visible) + Toggle(isOn: $isIgnored) { + + Label { + Text("Ignored") + } icon: { + + Image(systemName: "minus.circle.fill") + .symbolRenderingMode(.multicolor) + } + } + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + .listRowSeparator(.visible) if filterTitle == "Node Filters" { Toggle(isOn: $isEnvironment) { diff --git a/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift b/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift index b75d5822..5cae352d 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift @@ -15,12 +15,43 @@ struct NodeListItem: View { var connectedNode: Int64 var modemPreset: ModemPresets = ModemPresets(rawValue: UserDefaults.modemPreset) ?? ModemPresets.longFast - var body: some View { + var userKeyStatus: (String, Color) { + var image = "lock.open.fill" + var color = Color.yellow + if node.user?.pkiEncrypted ?? false { + if !(node.user?.keyMatch ?? false) { + /// Public Key on the User and the Public Key on the Last Message don't match + image = "key.slash" + color = .red + } else { + image = "lock.fill" + color = .green + } + } + return (image, color) + } + var locationData: (PositionEntity, CLLocation)? { + guard let lastPostion = node.positions?.lastObject as? PositionEntity else { + return nil + } + guard let currentLocation = LocationsHandler.shared.locationsArray.last else { + return nil + } + + let myCoord = CLLocation(latitude: currentLocation.coordinate.latitude, longitude: currentLocation.coordinate.longitude) + + if lastPostion.nodeCoordinate != nil && myCoord.coordinate.longitude != LocationsHandler.DefaultLocation.longitude && myCoord.coordinate.latitude != LocationsHandler.DefaultLocation.latitude { + return (lastPostion, myCoord) + } + return nil + } + + var body: some View { NavigationLink(value: node) { LazyVStack(alignment: .leading) { HStack { - VStack(alignment: .leading) { + VStack(alignment: .center) { CircleText(text: node.user?.shortName ?? "?", color: Color(UIColor(hex: UInt32(node.num))), circleSize: 70) .padding(.trailing, 5) if node.latestDeviceMetrics != nil { @@ -30,23 +61,11 @@ struct NodeListItem: View { } VStack(alignment: .leading) { HStack { - if node.user?.pkiEncrypted ?? false { - if !(node.user?.keyMatch ?? false) { - /// Public Key on the User and the Public Key on the Last Message don't match - Image(systemName: "key.slash") - .foregroundColor(.red) - } else { - Image(systemName: "lock.fill") - .foregroundColor(.green) - } - } else { - Image(systemName: "lock.open.fill") - .foregroundColor(.yellow) - } - Text(node.user?.longName ?? "unknown".localized) - .font(.headline) - .fontWeight(.regular) - .allowsTightening(true) + let (image, color) = userKeyStatus + IconAndText(systemName: image, + imageColor: color, + text: node.user?.longName ?? "unknown".localized, + textColor: .primary) if node.favorite { Spacer() Image(systemName: "star.fill") @@ -54,149 +73,82 @@ struct NodeListItem: View { } } if connected { - HStack { - Image(systemName: "antenna.radiowaves.left.and.right.circle.fill") - .font(.callout) - .symbolRenderingMode(.hierarchical) - .foregroundColor(.green) - .frame(width: 30) - Text("connected") - .font(UIDevice.current.userInterfaceIdiom == .phone ? .callout : .caption) - .foregroundColor(.gray) - } - } - HStack { - Image(systemName: node.isOnline ? "checkmark.circle.fill" : "moon.circle.fill") - .font(.callout) - .symbolRenderingMode(.hierarchical) - .foregroundColor(node.isOnline ? .green : .orange) - .frame(width: 30) - LastHeardText(lastHeard: node.lastHeard) - .font(UIDevice.current.userInterfaceIdiom == .phone ? .callout : .caption) - .foregroundColor(.gray) - } - HStack { - let role = DeviceRoles(rawValue: Int(node.user?.role ?? 0)) - Image(systemName: role?.systemName ?? "figure") - .font(.callout) - .symbolRenderingMode(.hierarchical) - .frame(width: 30) - Text("Role: \(role?.name ?? "unknown".localized)") - .font(UIDevice.current.userInterfaceIdiom == .phone ? .callout : .caption) - .foregroundColor(.gray) - + IconAndText(systemName: "antenna.radiowaves.left.and.right.circle.fill", + imageColor: .green, + text: "connected".localized) } + IconAndText(systemName: node.isOnline ? "checkmark.circle.fill" : "moon.circle.fill", + imageColor: node.isOnline ? .green : .orange, + text: node.lastHeard?.lastHeard ?? "unknown") + let role = DeviceRoles(rawValue: Int(node.user?.role ?? 0)) + IconAndText(systemName: role?.systemName ?? "figure", + text: "Role: \(role?.name ?? "unknown".localized)") if node.isStoreForwardRouter { - HStack { - Image(systemName: "envelope.arrow.triangle.branch") - .font(.callout) - .symbolRenderingMode(.multicolor) - .frame(width: 30) - Text("storeforward".localized) - .font(UIDevice.current.userInterfaceIdiom == .phone ? .callout : .caption) - .foregroundColor(.secondary) - } + IconAndText(systemName: "envelope.arrow.triangle.branch", + renderingMode: .multicolor, + text: "storeforward".localized) } if node.positions?.count ?? 0 > 0 && connectedNode != node.num { HStack { - if let lastPostion = node.positions?.lastObject as? PositionEntity { - if let currentLocation = LocationsHandler.shared.locationsArray.last { - let myCoord = CLLocation(latitude: currentLocation.coordinate.latitude, longitude: currentLocation.coordinate.longitude) - if lastPostion.nodeCoordinate != nil && myCoord.coordinate.longitude != LocationsHandler.DefaultLocation.longitude && myCoord.coordinate.latitude != LocationsHandler.DefaultLocation.latitude { - let nodeCoord = CLLocation(latitude: lastPostion.nodeCoordinate!.latitude, longitude: lastPostion.nodeCoordinate!.longitude) - let metersAway = nodeCoord.distance(from: myCoord) - Image(systemName: "lines.measurement.horizontal") - .font(.callout) - .symbolRenderingMode(.multicolor) - .frame(width: 30) - DistanceText(meters: metersAway) - .font(UIDevice.current.userInterfaceIdiom == .phone ? .callout : .caption) - .foregroundColor(.gray) - let trueBearing = getBearingBetweenTwoPoints(point1: myCoord, point2: nodeCoord) - let headingDegrees = Angle.degrees(trueBearing) - Image(systemName: "location.north") - .font(.callout) - .symbolRenderingMode(.multicolor) - .clipShape(Circle()) - .rotationEffect(headingDegrees) - let heading = Measurement(value: trueBearing, unit: UnitAngle.degrees) - Text("\(heading.formatted(.measurement(width: .narrow, numberFormatStyle: .number.precision(.fractionLength(0)))))") - .font(UIDevice.current.userInterfaceIdiom == .phone ? .callout : .caption) - .foregroundColor(.gray) - } - } + if let (lastPostion, myCoord) = locationData { + let nodeCoord = CLLocation(latitude: lastPostion.nodeCoordinate!.latitude, longitude: lastPostion.nodeCoordinate!.longitude) + let metersAway = nodeCoord.distance(from: myCoord) + Image(systemName: "lines.measurement.horizontal") + .font(.callout) + .symbolRenderingMode(.multicolor) + .frame(width: 30) + DistanceText(meters: metersAway) + .font(UIDevice.current.userInterfaceIdiom == .phone ? .callout : .caption) + .foregroundColor(.gray) + let trueBearing = getBearingBetweenTwoPoints(point1: myCoord, point2: nodeCoord) + let headingDegrees = Measurement(value: trueBearing, unit: UnitAngle.degrees).reciprocal() + Image(systemName: "location.north") + .font(.callout) + .symbolRenderingMode(.multicolor) + .clipShape(Circle()) + .rotationEffect(Angle(degrees: headingDegrees.value)) + let heading = Measurement(value: trueBearing, unit: UnitAngle.degrees).reciprocal() + Text("\(heading.formatted(.measurement(width: .narrow, numberFormatStyle: .number.precision(.fractionLength(0)))))") + .font(UIDevice.current.userInterfaceIdiom == .phone ? .callout : .caption) + .foregroundColor(.gray) } } } HStack { if node.channel > 0 { - HStack { - Image(systemName: "\(node.channel).circle.fill") - .font(.title2) - .frame(width: 30) - Text("Channel") - .foregroundColor(.secondary) - .font(UIDevice.current.userInterfaceIdiom == .phone ? .callout : .caption) - } + IconAndText(systemName: "\(node.channel).circle.fill", text: "Channel") } if node.viaMqtt && connectedNode != node.num { - Image(systemName: "dot.radiowaves.up.forward") - .symbolRenderingMode(.multicolor) - .font(.callout) - .frame(width: 30) - Text("MQTT") - .foregroundColor(.gray) - .font(UIDevice.current.userInterfaceIdiom == .phone ? .callout : .caption) + IconAndText(systemName: "dot.radiowaves.up.forward", + renderingMode: .multicolor, + text: "MQTT") } } if node.hasPositions || node.hasEnvironmentMetrics || node.hasDetectionSensorMetrics || node.hasTraceRoutes { HStack { - Image(systemName: "scroll") - .symbolRenderingMode(.hierarchical) - .font(.callout) - Text("Logs:") - .foregroundColor(.gray) - .font(UIDevice.current.userInterfaceIdiom == .phone ? .callout : .caption2) - .allowsTightening(true) + IconAndText(systemName: "scroll", text: "Logs:") if node.hasDeviceMetrics { - Image(systemName: "flipphone") - .symbolRenderingMode(.hierarchical) - .font(.callout) + DefaultIcon(systemName: "flipphone") } if node.hasPositions { - Image(systemName: "mappin.and.ellipse") - .symbolRenderingMode(.hierarchical) - .font(.callout) - + DefaultIcon(systemName: "mappin.and.ellipse") } if node.hasEnvironmentMetrics { - Image(systemName: "cloud.sun.rain") - .symbolRenderingMode(.hierarchical) - .font(.callout) - + DefaultIcon(systemName: "cloud.sun.rain") } if node.hasDetectionSensorMetrics { - Image(systemName: "sensor") - .symbolRenderingMode(.hierarchical) - .font(.callout) + DefaultIcon(systemName: "sensor") } if node.hasTraceRoutes { - Image(systemName: "signpost.right.and.left") - .symbolRenderingMode(.hierarchical) - .font(.callout) + DefaultIcon(systemName: "signpost.right.and.left") } } } if node.hopsAway > 0 { HStack { - Image(systemName: "hare") - .font(.callout) - .symbolRenderingMode(.multicolor) - Text("Hops Away:") - .foregroundColor(.secondary) - .font(UIDevice.current.userInterfaceIdiom == .phone ? .callout : .caption) + IconAndText(systemName: "hare", text: "Hops Away:") Image(systemName: "\(node.hopsAway).square") .font(.title2) } @@ -215,3 +167,60 @@ struct NodeListItem: View { .padding(.bottom, 4) } } + +struct DefaultIcon: View { + let systemName: String + + var body: some View { + Image(systemName: systemName) + .symbolRenderingMode(.hierarchical) + .font(.callout) + } +} + +struct IconAndText: View { + let systemName: String + var imageColor: Color? + var renderingMode: SymbolRenderingMode = .hierarchical + let text: String + var textColor: Color = .gray + + @ViewBuilder + var image: some View { + if let color = imageColor { + Image(systemName: systemName) + .foregroundColor(color) + } else { + Image(systemName: systemName) + } + } + + var body: some View { + HStack { + image + .font(.callout) + .symbolRenderingMode(renderingMode) + .frame(width: 30) + Text(text) + .font(UIDevice.current.userInterfaceIdiom == .phone ? .callout : .caption) + .foregroundColor(textColor) + .allowsTightening(true) + } + } +} + +#Preview { + VStack(alignment: .leading) { + IconAndText(systemName: "antenna.radiowaves.left.and.right.circle.fill", text: "foo") + IconAndText(systemName: "antenna.radiowaves.left.and.right.circle", text: "bar") + NodeListItem(node: { + let context = PersistenceController.preview.container.viewContext + let nodeInfo = NodeInfoEntity(context: context) + let user = UserEntity(context: context) + user.longName = "Test User" + user.shortName = "TU" + nodeInfo.user = user + return nodeInfo + }(), connected: true, connectedNode: 0, modemPreset: .longFast) + } +} diff --git a/Meshtastic/Views/Nodes/MeshMap.swift b/Meshtastic/Views/Nodes/MeshMap.swift index 447f6fdd..65928ba3 100644 --- a/Meshtastic/Views/Nodes/MeshMap.swift +++ b/Meshtastic/Views/Nodes/MeshMap.swift @@ -46,6 +46,7 @@ struct MeshMap: View { @State private var isOnline = false @State private var isPkiEncrypted = false @State private var isFavorite = false + @State private var isIgnored = false @State private var isEnvironment = false @State private var distanceFilter = false @State private var maxDistance: Double = 800000 @@ -161,6 +162,7 @@ struct MeshMap: View { isOnline: $isOnline, isPkiEncrypted: $isPkiEncrypted, isFavorite: $isFavorite, + isIgnored: $isIgnored, isEnvironment: $isEnvironment, distanceFilter: $distanceFilter, maximumDistance: $maxDistance, diff --git a/Meshtastic/Views/Nodes/NodeList.swift b/Meshtastic/Views/Nodes/NodeList.swift index 756f1179..6d007c56 100644 --- a/Meshtastic/Views/Nodes/NodeList.swift +++ b/Meshtastic/Views/Nodes/NodeList.swift @@ -26,6 +26,7 @@ struct NodeList: View { @State private var isOnline = false @State private var isPkiEncrypted = false @State private var isFavorite = false + @State private var isIgnored = false @State private var isEnvironment = false @State private var distanceFilter = false @State private var maxDistance: Double = 800000 @@ -40,6 +41,7 @@ struct NodeList: View { var boolFilters: [Bool] {[ isFavorite, + isIgnored, isOnline, isPkiEncrypted, isEnvironment, @@ -53,6 +55,7 @@ struct NodeList: View { @FetchRequest( sortDescriptors: [ + NSSortDescriptor(key: "ignored", ascending: true), NSSortDescriptor(key: "favorite", ascending: false), NSSortDescriptor(key: "lastHeard", ascending: false), NSSortDescriptor(key: "user.longName", ascending: true) @@ -90,12 +93,14 @@ struct NodeList: View { ) /// Don't show message, trace route, position exchange or delete context menu items for the connected node if connectedNode.num != node.num { - Button(action: { - if let url = URL(string: "meshtastic:///messages?userNum=\(node.num)") { - UIApplication.shared.open(url) + if !node.viaMqtt || node.viaMqtt && node.hopsAway == 0 { + Button(action: { + if let url = URL(string: "meshtastic:///messages?userNum=\(node.num)") { + UIApplication.shared.open(url) + } + }) { + Label("Message", systemImage: "message") } - }) { - Label("Message", systemImage: "message") } Button { let traceRouteSent = bleManager.sendTraceRouteRequest( @@ -132,6 +137,11 @@ struct NodeList: View { } label: { Label("Exchange Positions", systemImage: "arrow.triangle.2.circlepath") } + IgnoreNodeButton( + bleManager: bleManager, + context: context, + node: node + ) Button(role: .destructive) { deleteNodeId = node.num isPresentingDeleteNodeAlert = true @@ -164,6 +174,7 @@ struct NodeList: View { isOnline: $isOnline, isPkiEncrypted: $isPkiEncrypted, isFavorite: $isFavorite, + isIgnored: $isIgnored, isEnvironment: $isEnvironment, distanceFilter: $distanceFilter, maximumDistance: $maxDistance, @@ -189,7 +200,6 @@ struct NodeList: View { .controlSize(.regular) .padding(5) } - .padding(.bottom, 5) .searchable(text: $searchText, placement: .automatic, prompt: "Find a node") .disableAutocorrection(true) .scrollDismissesKeyboard(.immediately) @@ -392,6 +402,14 @@ struct NodeList: View { let isFavoritePredicate = NSPredicate(format: "favorite == YES") predicates.append(isFavoritePredicate) } + /// Ignored + if isIgnored { + let isIgnoredPredicate = NSPredicate(format: "ignored == YES") + predicates.append(isIgnoredPredicate) + } else if !isIgnored { + let isIgnoredPredicate = NSPredicate(format: "ignored == NO") + predicates.append(isIgnoredPredicate) + } /// Environment if isEnvironment { let environmentPredicate = NSPredicate(format: "SUBQUERY(telemetries, $tel, $tel.metricsType == 1).@count > 0") @@ -399,9 +417,9 @@ struct NodeList: View { } /// Distance if distanceFilter { - let pointOfInterest = LocationHelper.currentLocation + let pointOfInterest = LocationsHandler.currentLocation - if pointOfInterest.latitude != LocationHelper.DefaultLocation.latitude && pointOfInterest.longitude != LocationHelper.DefaultLocation.longitude { + if pointOfInterest.latitude != LocationsHandler.DefaultLocation.latitude && pointOfInterest.longitude != LocationsHandler.DefaultLocation.longitude { let d: Double = maxDistance * 1.1 let r: Double = 6371009 let meanLatitidue = pointOfInterest.latitude * .pi / 180 diff --git a/Meshtastic/Views/Nodes/PositionLog.swift b/Meshtastic/Views/Nodes/PositionLog.swift index 3abcb791..7c35b545 100644 --- a/Meshtastic/Views/Nodes/PositionLog.swift +++ b/Meshtastic/Views/Nodes/PositionLog.swift @@ -54,6 +54,7 @@ struct PositionLog: View { let degrees = Angle.degrees(Double(position.heading)) let heading = Measurement(value: degrees.degrees, unit: UnitAngle.degrees) Text(heading.formatted(.measurement(width: .narrow, numberFormatStyle: .number.precision(.fractionLength(0))))) + .textSelection(.enabled) } TableColumn("SNR") { position in Text("\(String(format: "%.2f", position.snr)) dB") @@ -63,6 +64,8 @@ struct PositionLog: View { } .width(min: 180) } + .textSelection(.enabled) + } else { ScrollView { diff --git a/Meshtastic/Views/Nodes/PowerMetricsLog.swift b/Meshtastic/Views/Nodes/PowerMetricsLog.swift new file mode 100644 index 00000000..4e0629a6 --- /dev/null +++ b/Meshtastic/Views/Nodes/PowerMetricsLog.swift @@ -0,0 +1,293 @@ +// +// PowerMetricsLog.swift +// Meshtastic +// +// Created by Matthew Davies on 1/24/25. +// + +import Foundation +import SwiftUI +import Charts +import OSLog + +struct PowerMetricsLog: View { + + @Environment(\.managedObjectContext) var context + @EnvironmentObject var bleManager: BLEManager + @ObservedObject var node: NodeInfoEntity + private var idiom: UIUserInterfaceIdiom { UIDevice.current.userInterfaceIdiom } + @State private var sortOrder = [KeyPathComparator(\TelemetryEntity.time, order: .reverse)] + @State private var selection: TelemetryEntity.ID? + @State private var chartSelection: Date? + + @State private var isPresentingClearLogConfirm: Bool = false + @State var isExporting = false + @State var exportString = "" + + @State private var channelSelection = 0 + + var powerMetrics: [TelemetryEntity] { + let telemetries = node.telemetries?.filtered(using: NSPredicate(format: "metricsType == 2")) + return (telemetries?.reversed() as? [TelemetryEntity]) ?? [] + } + + var minMax: (min: Double, max: Double) { + let allValues = powerMetrics.flatMap { [ + $0.powerCh1Voltage, + $0.powerCh1Current, + $0.powerCh2Voltage, + $0.powerCh2Current, + $0.powerCh3Voltage, + $0.powerCh3Current + ]} + + guard !allValues.isEmpty else { + return (min: -10, max: 10) + } + + return (min: floor(Double(allValues.min()!)), max: ceil(Double(allValues.max()!))) + } + + var body: some View { + VStack { + if node.hasPowerMetrics { + let oneWeekAgo = Calendar.current.date(byAdding: .day, value: -7, to: Date()) + + let chartData = powerMetrics + .filter { $0.time != nil && $0.time! >= oneWeekAgo! } + .sorted { $0.time! < $1.time! } + if chartData.count > 0 { + GroupBox(label: Label("\(powerMetrics.count) Readings Total", systemImage: "chart.xyaxis.line")) { + + // allow switching between different channels + Picker("Select Channel", selection: $channelSelection) { + Text("Channel 1").tag(0) + Text("Channel 2").tag(1) + Text("Channel 3").tag(2) + } + + Chart { + ForEach(chartData, id: \.self) { point in + + let voltage = channelSelection == 0 ? point.powerCh1Voltage : channelSelection == 1 ? point.powerCh2Voltage : point.powerCh3Voltage + let current = channelSelection == 0 ? point.powerCh1Current : channelSelection == 1 ? point.powerCh2Current : point.powerCh3Current + + LineMark( + x: .value("Time", point.time ?? Date()), + y: .value("Voltage", voltage) + ) + .foregroundStyle(by: .value("Series", "Voltage")) + .interpolationMethod(.linear) + .accessibilityLabel("Voltage") + .accessibilityValue("X: \(point.time ?? Date()), Y: \(voltage)") + + LineMark( + x: .value("Time", point.time ?? Date()), + y: .value("Current", current) + ) + .foregroundStyle(by: .value("Series", "Current")) + .interpolationMethod(.linear) + .accessibilityLabel("Current") + .accessibilityValue("X: \(point.time ?? Date()), Y: \(current)") + + } + + if let chartSelection { + RuleMark(x: .value("Second", chartSelection, unit: .second)) + .foregroundStyle(.tertiary.opacity(0.5)) + } + + } + .chartXAxis(content: { + AxisMarks(position: .top) + }) + .chartXAxis(.automatic) + .chartXSelection(value: $chartSelection) + .chartYScale(domain: minMax.min...minMax.max) + .chartForegroundStyleScale([ + "Voltage": .blue, + "Current": .green + ]) + .chartLegend(position: .automatic, alignment: .bottom) + } + } + let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMdjmma", options: 0, locale: Locale.current) + let dateFormatString = (localeDateFormat ?? "M/d/YY j:mma").replacingOccurrences(of: ",", with: "") + + if idiom == .phone { + Table(powerMetrics, selection: $selection, sortOrder: $sortOrder) { + TableColumn("Timestamp") { m in + HStack { + Text(m.time?.formattedDate(format: dateFormatString) ?? "unknown.age".localized) + Spacer() + HStack { + VStack { + Text("Channel 1") + HStack { + Image(systemName: "powerplug.fill") + .font(.caption) + .symbolRenderingMode(.multicolor) + Text("\(String(format: "%.2f", m.powerCh1Voltage))V") + } + HStack { + Image(systemName: "bolt.fill") + .font(.caption) + .symbolRenderingMode(.multicolor) + Text("\(String(format: "%.2f", m.powerCh1Current))mA") + } + } + } + Spacer() + HStack { + VStack { + Text("Channel 2") + HStack { + Image(systemName: "powerplug.fill") + .font(.caption) + .symbolRenderingMode(.multicolor) + Text("\(String(format: "%.2f", m.powerCh2Voltage))V") + } + HStack { + Image(systemName: "bolt.fill") + .font(.caption) + .symbolRenderingMode(.multicolor) + Text("\(String(format: "%.2f", m.powerCh2Current))mA") + } + } + } + Spacer() + HStack { + VStack { + Text("Channel 3") + HStack { + Image(systemName: "powerplug.fill") + .font(.caption) + .symbolRenderingMode(.multicolor) + Text("\(String(format: "%.2f", m.powerCh3Voltage))V") + } + HStack { + Image(systemName: "bolt.fill") + .font(.caption) + .symbolRenderingMode(.multicolor) + Text("\(String(format: "%.2f", m.powerCh3Current))mA") + } + } + } + } + } + } + .onChange(of: selection) { _, newSelection in + guard let metrics = powerMetrics.first(where: { $0.id == newSelection }) else { + return + } + chartSelection = metrics.time + } + } else { + Table(powerMetrics, selection: $selection, sortOrder: $sortOrder) { + TableColumn("Ch1 Voltage") { dm in + Text("\(String(format: "%.2f", dm.powerCh1Voltage))V") + } + .width(min: 75) + TableColumn("Ch1 Current") { dm in + Text("\(String(format: "%.2f", dm.powerCh1Current))mA") + } + .width(min: 75) + TableColumn("Ch2 Voltage") { dm in + Text("\(String(format: "%.2f", dm.powerCh2Voltage))V") + } + .width(min: 75) + TableColumn("Ch2 Current") { dm in + Text("\(String(format: "%.2f", dm.powerCh2Current))mA") + } + .width(min: 75) + TableColumn("Ch3 Voltage") { dm in + Text("\(String(format: "%.2f", dm.powerCh3Voltage))V") + } + .width(min: 75) + TableColumn("Ch3 Current") { dm in + Text("\(String(format: "%.2f", dm.powerCh3Current))mA") + } + .width(min: 75) + TableColumn("timestamp") { dm in + Text(dm.time?.formattedDate(format: dateFormatString) ?? "unknown.age".localized) + } + .width(min: 180) + + } + .onChange(of: selection) { _, newSelection in + guard let metrics = powerMetrics.first(where: { $0.id == newSelection }) else { + return + } + chartSelection = metrics.time + } + } + HStack { + Button(role: .destructive) { + isPresentingClearLogConfirm = true + } label: { + Label("clear.log", systemImage: "trash.fill") + } + .buttonStyle(.bordered) + .buttonBorderShape(.capsule) + .controlSize(idiom == .phone ? .regular : .large) + .padding(.bottom) + .padding(.leading) + .confirmationDialog( + "are.you.sure", + isPresented: $isPresentingClearLogConfirm, + titleVisibility: .visible + ) { + Button("power.metrics.delete", role: .destructive) { + if clearTelemetry(destNum: node.num, metricsType: 2, context: context) { + Logger.data.notice("Cleared Power Metrics for \(node.num)") + } else { + Logger.data.error("Clear Power Metrics Log Failed") + } + } + } + + Button { + exportString = telemetryToCsvFile(telemetry: powerMetrics, metricsType: 2) + isExporting = true + } label: { + Label("save", systemImage: "square.and.arrow.down") + } + .buttonStyle(.bordered) + .buttonBorderShape(.capsule) + .controlSize(idiom == .phone ? .regular : .large) + .padding(.bottom) + .padding(.trailing) + } + .onChange(of: selection) { _, newSelection in + guard let metrics = powerMetrics.first(where: { $0.id == newSelection }) else { + return + } + chartSelection = metrics.time + } + } else { + ContentUnavailableView("No Power Metrics", systemImage: "slash.circle") + } + } + .navigationTitle("power.metrics.log") + .navigationBarTitleDisplayMode(.inline) + .navigationBarItems(trailing: + ZStack { + ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?") + }) + .fileExporter( + isPresented: $isExporting, + document: CsvDocument(emptyCsv: exportString), + contentType: .commaSeparatedText, + defaultFilename: String("\(node.user?.longName ?? "Node") \("power.metrics.log".localized)"), + onCompletion: { result in + switch result { + case .success: + 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)") + } + } + ) + } +} diff --git a/Meshtastic/Views/Nodes/TraceRouteLog.swift b/Meshtastic/Views/Nodes/TraceRouteLog.swift index 45206cb0..f10dad58 100644 --- a/Meshtastic/Views/Nodes/TraceRouteLog.swift +++ b/Meshtastic/Views/Nodes/TraceRouteLog.swift @@ -37,24 +37,25 @@ struct TraceRouteLog: View { VStack { List(node.traceRoutes?.reversed() as? [TraceRouteEntity] ?? [], id: \.self, selection: $selectedRoute) { route in Label { - if route.response && route.hopsTowards == 0 { - Text("\(route.time?.formatted() ?? "unknown".localized) - Direct") - .font(.caption) - } else if route.response && route.hopsTowards == 1 { - Text("\(route.time?.formatted() ?? "unknown".localized) - 1 Hop") + let routeTime = route.time?.formatted() ?? "unknown".localized + if route.response && route.hopsTowards == route.hopsBack { + let hopString = String(localized: "\(route.hopsTowards) Hops") + Text("\(routeTime) - \(hopString)") .font(.caption) } else if route.response { - Text("\(route.time?.formatted() ?? "unknown".localized) - \(route.hopsTowards) Hops Towards \(route.hopsBack) Hops Back") + let hopTowardsString = String(localized: "\(route.hopsTowards) Hops") + let hopBackString = route.hopsBack >= 0 ? String(localized: "\(route.hopsBack) Hops") : String(localized: "unknown") + Text("\(routeTime) - \(hopTowardsString) Towards \(hopBackString) Back") .font(.caption) } else if route.sent { - Text("\(route.time?.formatted() ?? "unknown".localized) - No Response") + Text("\(routeTime) - No Response") .font(.caption) } else { - Text("\(route.time?.formatted() ?? "unknown".localized) - Not Sent") + Text("\(routeTime) - Not Sent") .font(.caption) } } icon: { - Image(systemName: route.response ? (route.hops?.count == 0 && route.response ? "person.line.dotted.person" : "point.3.connected.trianglepath.dotted") : "person.slash") + Image(systemName: route.response ? (route.hopsTowards == 0 && route.response ? "person.line.dotted.person" : "point.3.connected.trianglepath.dotted") : "person.slash") .symbolRenderingMode(.hierarchical) } .swipeActions { @@ -72,20 +73,10 @@ struct TraceRouteLog: View { } .listStyle(.plain) } - .frame(minHeight: CGFloat(node.traceRoutes?.count ?? 0 * 40), maxHeight: 250) Divider() ScrollView { if selectedRoute != nil { - - if selectedRoute?.response ?? false && selectedRoute?.hopsTowards ?? 0 == 0 { - Label { - Text("Trace route received directly by \(selectedRoute?.node?.user?.longName ?? "unknown".localized) with a SNR of \(String(format: "%.2f", selectedRoute?.node?.snr ?? 0.0)) dB") - } icon: { - Image(systemName: "signpost.right.and.left") - .symbolRenderingMode(.hierarchical) - } - .font(.title3) - } else if selectedRoute?.response ?? false && selectedRoute?.hopsTowards ?? 0 > 0 { + if selectedRoute?.response ?? false && selectedRoute?.hopsTowards ?? 0 >= 0 { Label { Text("Route: \(selectedRoute?.routeText ?? "unknown".localized)") } icon: { @@ -131,7 +122,7 @@ struct TraceRouteLog: View { .symbolRenderingMode(.hierarchical) } } - if false {//selectedRoute?.hops?.count ?? 0 >= 3 { + if false {// selectedRoute?.hops?.count ?? 0 >= 3 { HStack(alignment: .center) { GeometryReader { geometry in let size = ((geometry.size.width >= geometry.size.height ? geometry.size.height : geometry.size.width) / 2) - (idiom == .phone ? 45 : 85) diff --git a/Meshtastic/Views/Settings/Channels/ChannelForm.swift b/Meshtastic/Views/Settings/Channels/ChannelForm.swift index 72d6f58b..06a4a260 100644 --- a/Meshtastic/Views/Settings/Channels/ChannelForm.swift +++ b/Meshtastic/Views/Settings/Channels/ChannelForm.swift @@ -138,7 +138,7 @@ struct ChannelForm: View { } if positionsEnabled { - if channelKey != "AQ==" && channelRole > 0 { + if (channelKey != "AQ==" && channelKeySize > 1) && channelRole > 0 { VStack(alignment: .leading) { Toggle(isOn: $preciseLocation) { Label("Precise Location", systemImage: "scope") @@ -212,12 +212,11 @@ struct ChannelForm: View { } .onChange(of: preciseLocation) { _, loc in if loc == true { - if channelKey == "AQ==" { + if channelKey == "AQ==" || channelKeySize <= 1 { preciseLocation = false } else { positionPrecision = 32 } - positionPrecision = 32 } else { positionPrecision = 14 } diff --git a/Meshtastic/Views/Settings/Config/BluetoothConfig.swift b/Meshtastic/Views/Settings/Config/BluetoothConfig.swift index 37f989a5..d57dacba 100644 --- a/Meshtastic/Views/Settings/Config/BluetoothConfig.swift +++ b/Meshtastic/Views/Settings/Config/BluetoothConfig.swift @@ -103,7 +103,7 @@ struct BluetoothConfig: View { .onFirstAppear { // Need to request a BluetoothConfig from the remote node before allowing changes if let connectedPeripheral = bleManager.connectedPeripheral, let node { - Logger.mesh.info("empty bluetooth config") + let connectedNode = getNodeInfo(id: connectedPeripheral.num, context: context) if let connectedNode { if node.num != connectedNode.num { @@ -111,10 +111,12 @@ struct BluetoothConfig: View { /// 2.5 Administration with session passkey let expiration = node.sessionExpiration ?? Date() if expiration < Date() || node.bluetoothConfig == nil { + Logger.mesh.info("⚙️ Empty or expired bluetooth config requesting via PKI admin") _ = bleManager.requestBluetoothConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) } } else { /// Legacy Administration + Logger.mesh.info("☠️ Using insecure legacy admin, empty bluetooth config") _ = bleManager.requestBluetoothConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) } } diff --git a/Meshtastic/Views/Settings/Config/DeviceConfig.swift b/Meshtastic/Views/Settings/Config/DeviceConfig.swift index f5fca283..da2978cf 100644 --- a/Meshtastic/Views/Settings/Config/DeviceConfig.swift +++ b/Meshtastic/Views/Settings/Config/DeviceConfig.swift @@ -78,7 +78,7 @@ struct DeviceConfig: View { .toggleStyle(SwitchToggleStyle(tint: .accentColor)) Toggle(isOn: $tripleClickAsAdHocPing) { - Label("Triple Click Ad Hoc Ping", systemImage: "map.pin") + Label("Triple Click Ad Hoc Ping", systemImage: "mappin") Text("Send a position on the primary channel when the user button is triple clicked.") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) @@ -229,7 +229,6 @@ struct DeviceConfig: View { .onFirstAppear { // Need to request a DeviceConfig from the remote node before allowing changes if let connectedPeripheral = bleManager.connectedPeripheral, let node { - Logger.mesh.info("empty device config") let connectedNode = getNodeInfo(id: connectedPeripheral.num, context: context) if let connectedNode { if node.num != connectedNode.num { @@ -237,11 +236,13 @@ struct DeviceConfig: View { /// 2.5 Administration with session passkey let expiration = node.sessionExpiration ?? Date() if expiration < Date() || node.deviceConfig == nil { + Logger.mesh.info("⚙️ Empty or expired device config requesting via PKI admin") _ = bleManager.requestDeviceConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) } } else { if node.deviceConfig == nil { /// Legacy Administration + Logger.mesh.info("☠️ Using insecure legacy admin, empty device config") _ = bleManager.requestDeviceConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) } } diff --git a/Meshtastic/Views/Settings/Config/DisplayConfig.swift b/Meshtastic/Views/Settings/Config/DisplayConfig.swift index 07975419..88839956 100644 --- a/Meshtastic/Views/Settings/Config/DisplayConfig.swift +++ b/Meshtastic/Views/Settings/Config/DisplayConfig.swift @@ -166,7 +166,6 @@ struct DisplayConfig: View { .onFirstAppear { // Need to request a DisplayConfig from the remote node before allowing changes if let connectedPeripheral = bleManager.connectedPeripheral, let node { - Logger.mesh.info("empty display config") let connectedNode = getNodeInfo(id: connectedPeripheral.num, context: context) if let connectedNode { if node.num != connectedNode.num { @@ -174,10 +173,12 @@ struct DisplayConfig: View { /// 2.5 Administration with session passkey let expiration = node.sessionExpiration ?? Date() if expiration < Date() || node.displayConfig == nil { + Logger.mesh.info("⚙️ Empty or expired display config requesting via PKI admin") _ = bleManager.requestDisplayConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) } } else { /// Legacy Administration + Logger.mesh.info("☠️ Using insecure legacy admin, empty display config") _ = bleManager.requestDisplayConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) } } diff --git a/Meshtastic/Views/Settings/Config/LoRaConfig.swift b/Meshtastic/Views/Settings/Config/LoRaConfig.swift index 4c12c598..81cfa1ba 100644 --- a/Meshtastic/Views/Settings/Config/LoRaConfig.swift +++ b/Meshtastic/Views/Settings/Config/LoRaConfig.swift @@ -168,7 +168,7 @@ struct LoRaConfig: View { .focused($focusedField, equals: .channelNum) .disabled(overrideFrequency > 0.0) } - Text("This determines the actual frequency you are transmitting on in the band. If set to 0 this value will be calculated automatically based on the primary channel name.") + Text("Your node’s operating frequency is calculated based on the region, modem preset, and this field. When 0, the slot is automatically calculated based on the primary channel name.") .foregroundColor(.gray) .font(.callout) } @@ -241,7 +241,6 @@ struct LoRaConfig: View { .onFirstAppear { // Need to request a LoRaConfig from the remote node before allowing changes if let connectedPeripheral = bleManager.connectedPeripheral, let node { - Logger.mesh.info("empty lora config") let connectedNode = getNodeInfo(id: connectedPeripheral.num, context: context) if let connectedNode { if node.num != connectedNode.num { @@ -249,10 +248,13 @@ struct LoRaConfig: View { /// 2.5 Administration with session passkey 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 { /// Legacy Administration + Logger.mesh.info("☠️ Using insecure legacy admin, empty lora config") _ = bleManager.requestLoRaConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) } } diff --git a/Meshtastic/Views/Settings/Config/Module/AmbientLightingConfig.swift b/Meshtastic/Views/Settings/Config/Module/AmbientLightingConfig.swift index fc82a4ca..62340da8 100644 --- a/Meshtastic/Views/Settings/Config/Module/AmbientLightingConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/AmbientLightingConfig.swift @@ -88,7 +88,6 @@ struct AmbientLightingConfig: View { .onFirstAppear { // Need to request a Ambient Lighting Config from the remote node before allowing changes if let connectedPeripheral = bleManager.connectedPeripheral, let node { - Logger.mesh.info("empty ambient lighting config") let connectedNode = getNodeInfo(id: connectedPeripheral.num, context: context) if let connectedNode { if node.num != connectedNode.num { @@ -96,10 +95,12 @@ struct AmbientLightingConfig: View { /// 2.5 Administration with session passkey let expiration = node.sessionExpiration ?? Date() if expiration < Date() || node.ambientLightingConfig == nil { + Logger.mesh.info("⚙️ Empty or expired ambient lighting module config requesting via PKI admin") _ = bleManager.requestAmbientLightingConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) } } else { /// Legacy Administration + Logger.mesh.info("☠️ Using insecure legacy admin, empty ambient lighting module config") _ = bleManager.requestAmbientLightingConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) } } diff --git a/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift b/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift index 4f580b2a..b5dfee63 100644 --- a/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift @@ -236,7 +236,6 @@ struct CannedMessagesConfig: View { .onFirstAppear { // Need to request a CannedMessagesModuleConfig from the remote node before allowing changes if let connectedPeripheral = bleManager.connectedPeripheral, let node { - Logger.mesh.info("empty canned message config") let connectedNode = getNodeInfo(id: connectedPeripheral.num, context: context) if let connectedNode { if node.num != connectedNode.num { @@ -244,10 +243,12 @@ struct CannedMessagesConfig: View { /// 2.5 Administration with session passkey let expiration = node.sessionExpiration ?? Date() if expiration < Date() || node.cannedMessageConfig == nil { + Logger.mesh.info("⚙️ Empty or expired canned messages module config requesting via PKI admin") _ = bleManager.requestCannedMessagesModuleConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) } } else { /// Legacy Administration + Logger.mesh.info("☠️ Using insecure legacy admin, empty canned messages module config") _ = bleManager.requestCannedMessagesModuleConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) } } diff --git a/Meshtastic/Views/Settings/Config/Module/DetectionSensorConfig.swift b/Meshtastic/Views/Settings/Config/Module/DetectionSensorConfig.swift index aff3dfea..59ee4f3f 100644 --- a/Meshtastic/Views/Settings/Config/Module/DetectionSensorConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/DetectionSensorConfig.swift @@ -194,7 +194,6 @@ struct DetectionSensorConfig: View { .onFirstAppear { // Need to request a DetectionSensorModuleConfig from the remote node before allowing changes if let connectedPeripheral = bleManager.connectedPeripheral, let node { - Logger.mesh.info("empty detection sensor config") let connectedNode = getNodeInfo(id: connectedPeripheral.num, context: context) if let connectedNode { if node.num != connectedNode.num { @@ -202,10 +201,12 @@ struct DetectionSensorConfig: View { /// 2.5 Administration with session passkey let expiration = node.sessionExpiration ?? Date() if expiration < Date() || node.detectionSensorConfig == nil { + Logger.mesh.info("⚙️ Empty or expired detection sensor module config requesting via PKI admin") _ = bleManager.requestDetectionSensorModuleConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) } } else { /// Legacy Administration + Logger.mesh.info("☠️ Using insecure legacy admin, empty detection sensor module config") _ = bleManager.requestDetectionSensorModuleConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) } } diff --git a/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift b/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift index a0f14c7f..9602c44b 100644 --- a/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift @@ -202,7 +202,6 @@ struct ExternalNotificationConfig: View { .onFirstAppear { // Need to request a ExternalNotificationModuleConfig from the remote node before allowing changes if let connectedPeripheral = bleManager.connectedPeripheral, let node { - Logger.mesh.info("empty external notificaiton module config") let connectedNode = getNodeInfo(id: connectedPeripheral.num, context: context) if let connectedNode { if node.num != connectedNode.num { @@ -210,10 +209,12 @@ struct ExternalNotificationConfig: View { /// 2.5 Administration with session passkey let expiration = node.sessionExpiration ?? Date() if expiration < Date() || node.externalNotificationConfig == nil { + Logger.mesh.info("⚙️ Empty or expired external notificaiton module config requesting via PKI admin") _ = bleManager.requestExternalNotificationModuleConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) } } else { /// Legacy Administration + Logger.mesh.info("☠️ Using insecure legacy admin, empty external notificaiton module config") _ = bleManager.requestExternalNotificationModuleConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) } } diff --git a/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift b/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift index 224fd43c..796a7ce0 100644 --- a/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift @@ -64,7 +64,7 @@ struct MQTTConfig: View { } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - if enabled && proxyToClientEnabled && node!.mqttConfig!.proxyToClientEnabled == true { + if enabled && proxyToClientEnabled && node?.mqttConfig?.proxyToClientEnabled ?? false == true { Toggle(isOn: $mqttConnected) { Label(mqttConnected ? "mqtt.disconnect".localized : "mqtt.connect".localized, systemImage: "server.rack") if bleManager.mqttError.count > 0 { @@ -174,51 +174,55 @@ struct MQTTConfig: View { .keyboardType(.default) } .autocorrectionDisabled() - - HStack { - Label("mqtt.username", systemImage: "person.text.rectangle") - TextField("mqtt.username", text: $username) - .foregroundColor(.gray) - .autocapitalization(.none) - .disableAutocorrection(true) - .onChange(of: username) { - var totalBytes = username.utf8.count - // Only mess with the value if it is too big - while totalBytes > 62 { - username = String(username.dropLast()) - totalBytes = username.utf8.count + if address != "mqtt.meshtastic.org" { + HStack { + Label("mqtt.username", systemImage: "person.text.rectangle") + TextField("mqtt.username", text: $username) + .foregroundColor(.gray) + .autocapitalization(.none) + .disableAutocorrection(true) + .onChange(of: username) { + var totalBytes = username.utf8.count + // Only mess with the value if it is too big + while totalBytes > 62 { + username = String(username.dropLast()) + totalBytes = username.utf8.count + } + hasChanges = true } - hasChanges = true - } - .foregroundColor(.gray) - } - .keyboardType(.default) - .scrollDismissesKeyboard(.interactively) - HStack { - Label("password", systemImage: "wallet.pass") - TextField("password", text: $password) - .foregroundColor(.gray) - .autocapitalization(.none) - .disableAutocorrection(true) - .onChange(of: password) { - var totalBytes = password.utf8.count - // Only mess with the value if it is too big - while totalBytes > 62 { - password = String(password.dropLast()) - totalBytes = password.utf8.count + .foregroundColor(.gray) + } + .keyboardType(.default) + .scrollDismissesKeyboard(.interactively) + + HStack { + Label("password", systemImage: "wallet.pass") + TextField("password", text: $password) + .foregroundColor(.gray) + .autocapitalization(.none) + .disableAutocorrection(true) + .onChange(of: password) { + var totalBytes = password.utf8.count + // Only mess with the value if it is too big + while totalBytes > 62 { + password = String(password.dropLast()) + totalBytes = password.utf8.count + } + hasChanges = true } - hasChanges = true + .foregroundColor(.gray) + } + .keyboardType(.default) + .scrollDismissesKeyboard(.interactively) + .listRowSeparator(/*@START_MENU_TOKEN@*/.visible/*@END_MENU_TOKEN@*/) + if !proxyToClientEnabled { + Toggle(isOn: $tlsEnabled) { + Label("TLS Enabled", systemImage: "checkmark.shield.fill") + Text("Your MQTT Server must support TLS.") } - .foregroundColor(.gray) + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + } } - .keyboardType(.default) - .scrollDismissesKeyboard(.interactively) - .listRowSeparator(/*@START_MENU_TOKEN@*/.visible/*@END_MENU_TOKEN@*/) - Toggle(isOn: $tlsEnabled) { - Label("TLS Enabled", systemImage: "checkmark.shield.fill") - Text("Your MQTT Server must support TLS. Not available via the public mqtt server.") - } - .toggleStyle(SwitchToggleStyle(tint: .accentColor)) } Text("For all Mqtt functionality other than the map report you must also set uplink and downlink for each channel you want to bridge over Mqtt.") .font(.callout) @@ -268,6 +272,7 @@ struct MQTTConfig: View { .onChange(of: proxyToClientEnabled) { _, newProxyToClientEnabled in if newProxyToClientEnabled { jsonEnabled = false + tlsEnabled = false } if newProxyToClientEnabled != node?.mqttConfig?.proxyToClientEnabled { hasChanges = true } } @@ -322,7 +327,6 @@ struct MQTTConfig: View { .onFirstAppear { // Need to request a MqttModuleConfig from the remote node before allowing changes if let connectedPeripheral = bleManager.connectedPeripheral, let node { - Logger.mesh.info("empty mqtt module config") let connectedNode = getNodeInfo(id: connectedPeripheral.num, context: context) if let connectedNode { if node.num != connectedNode.num { @@ -330,10 +334,12 @@ struct MQTTConfig: View { /// 2.5 Administration with session passkey let expiration = node.sessionExpiration ?? Date() if expiration < Date() || node.mqttConfig == nil { + Logger.mesh.info("⚙️ Empty or expired mqtt module config requesting via PKI admin") _ = bleManager.requestMqttModuleConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) } } else { /// Legacy Administration + Logger.mesh.info("☠️ Using insecure legacy admin, empty mqtt module config") _ = bleManager.requestMqttModuleConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) } } @@ -346,8 +352,8 @@ struct MQTTConfig: View { nearbyTopics = [] let geocoder = CLGeocoder() if LocationsHandler.shared.locationsArray.count > 0 { - let region = RegionCodes(rawValue: Int(node?.loRaConfig?.regionCode ?? 0))?.topic - defaultTopic = "msh/" + (region ?? "UNSET") + let region = RegionCodes(rawValue: Int(node?.loRaConfig?.regionCode ?? 0)) + 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)") @@ -356,8 +362,8 @@ struct MQTTConfig: View { if let placemarks = placemarks, let placemark = placemarks.first { let cc = locale.region?.identifier ?? "UNK" - /// Country Topic unless you are US - if placemark.isoCountryCode ?? "unknown" != cc { + /// Country Topic unless your region is a country + if !(region?.isCountry ?? false) { let countryTopic = defaultTopic + "/" + (placemark.isoCountryCode ?? "") if !countryTopic.isEmpty { nearbyTopics.append(countryTopic) diff --git a/Meshtastic/Views/Settings/Config/Module/PaxCounterConfig.swift b/Meshtastic/Views/Settings/Config/Module/PaxCounterConfig.swift index 2fd97646..24af3504 100644 --- a/Meshtastic/Views/Settings/Config/Module/PaxCounterConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/PaxCounterConfig.swift @@ -61,7 +61,6 @@ struct PaxCounterConfig: View { .onFirstAppear { // Need to request a PaxCounterModuleConfig from the remote node before allowing changes if let connectedPeripheral = bleManager.connectedPeripheral, let node { - Logger.mesh.info("empty pax counter module config") let connectedNode = getNodeInfo(id: connectedPeripheral.num, context: context) if let connectedNode { if node.num != connectedNode.num { @@ -69,10 +68,12 @@ struct PaxCounterConfig: View { /// 2.5 Administration with session passkey let expiration = node.sessionExpiration ?? Date() if expiration < Date() || node.paxCounterConfig == nil { + Logger.mesh.info("⚙️ Empty or expired pax counter module config requesting via PKI admin") _ = bleManager.requestPaxCounterModuleConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) } } else { /// Legacy Administration + Logger.mesh.info("☠️ Using insecure legacy admin, empty pax counter module config") _ = bleManager.requestPaxCounterModuleConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) } } diff --git a/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift b/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift index ae4797fc..979eb736 100644 --- a/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift @@ -84,7 +84,6 @@ struct RangeTestConfig: View { .onFirstAppear { // Need to request a RangeTestModuleConfig from the remote node before allowing changes if let connectedPeripheral = bleManager.connectedPeripheral, let node { - Logger.mesh.info("empty range test module config") let connectedNode = getNodeInfo(id: connectedPeripheral.num, context: context) if let connectedNode { if node.num != connectedNode.num { @@ -92,10 +91,12 @@ struct RangeTestConfig: View { /// 2.5 Administration with session passkey let expiration = node.sessionExpiration ?? Date() if expiration < Date() || node.rangeTestConfig == nil { + Logger.mesh.info("⚙️ Empty or expired range test module config requesting via PKI admin") _ = bleManager.requestRangeTestModuleConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) } } else { /// Legacy Administration + Logger.mesh.info("☠️ Using insecure legacy admin, empty range test module config") _ = bleManager.requestRangeTestModuleConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) } } diff --git a/Meshtastic/Views/Settings/Config/Module/RtttlConfig.swift b/Meshtastic/Views/Settings/Config/Module/RtttlConfig.swift index 2e0931a7..b81e2348 100644 --- a/Meshtastic/Views/Settings/Config/Module/RtttlConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/RtttlConfig.swift @@ -75,7 +75,6 @@ struct RtttlConfig: View { .onFirstAppear { // Need to request a RtttlConfig from the remote node before allowing changes if let connectedPeripheral = bleManager.connectedPeripheral, let node { - Logger.mesh.info("empty range test module config") let connectedNode = getNodeInfo(id: connectedPeripheral.num, context: context) if let connectedNode { if node.num != connectedNode.num { @@ -83,10 +82,12 @@ struct RtttlConfig: View { /// 2.5 Administration with session passkey let expiration = node.sessionExpiration ?? Date() if expiration < Date() || node.rtttlConfig == nil { + Logger.mesh.info("⚙️ Empty or expired ringtone module config requesting via PKI admin") _ = bleManager.requestRtttlConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) } } else { /// Legacy Administration + Logger.mesh.info("☠️ Using insecure legacy admin, empty ringtone module config") _ = bleManager.requestRtttlConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) } } diff --git a/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift b/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift index a7fd5bdb..fd2c0c99 100644 --- a/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift @@ -139,7 +139,6 @@ struct SerialConfig: View { .onFirstAppear { // Need to request a SerialModuleConfig from the remote node before allowing changes if let connectedPeripheral = bleManager.connectedPeripheral, let node { - Logger.mesh.info("empty serial module config") let connectedNode = getNodeInfo(id: connectedPeripheral.num, context: context) if let connectedNode { if node.num != connectedNode.num { @@ -147,10 +146,12 @@ struct SerialConfig: View { /// 2.5 Administration with session passkey let expiration = node.sessionExpiration ?? Date() if expiration < Date() || node.serialConfig == nil { + Logger.mesh.info("⚙️ Empty or expired serial module config requesting via PKI admin") _ = bleManager.requestSerialModuleConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) } } else { /// Legacy Administration + Logger.mesh.info("☠️ Using insecure legacy admin, empty serial module config") _ = bleManager.requestSerialModuleConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) } } diff --git a/Meshtastic/Views/Settings/Config/Module/StoreForwardConfig.swift b/Meshtastic/Views/Settings/Config/Module/StoreForwardConfig.swift index d90ea3fb..9c247acf 100644 --- a/Meshtastic/Views/Settings/Config/Module/StoreForwardConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/StoreForwardConfig.swift @@ -149,7 +149,6 @@ struct StoreForwardConfig: View { .onFirstAppear { // Need to request a StoreForwardModuleConfig from the remote node before allowing changes if let connectedPeripheral = bleManager.connectedPeripheral, let node { - Logger.mesh.info("empty store & forward module config") let connectedNode = getNodeInfo(id: connectedPeripheral.num, context: context) if let connectedNode { if node.num != connectedNode.num { @@ -157,10 +156,12 @@ struct StoreForwardConfig: View { /// 2.5 Administration with session passkey let expiration = node.sessionExpiration ?? Date() if expiration < Date() || node.storeForwardConfig == nil { + Logger.mesh.info("⚙️ Empty or expired store & forward module config requesting via PKI admin") _ = bleManager.requestStoreAndForwardModuleConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) } } else { /// Legacy Administration + Logger.mesh.info("☠️ Using insecure legacy admin, empty store & forward module config") _ = bleManager.requestStoreAndForwardModuleConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) } } diff --git a/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift b/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift index 4ba39834..ad173dae 100644 --- a/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift @@ -137,7 +137,6 @@ struct TelemetryConfig: View { .onFirstAppear { // Need to request a TelemetryModuleConfig from the remote node before allowing changes if let connectedPeripheral = bleManager.connectedPeripheral, let node { - Logger.mesh.info("empty telemetry module config") let connectedNode = getNodeInfo(id: connectedPeripheral.num, context: context) if let connectedNode { if node.num != connectedNode.num { @@ -145,10 +144,12 @@ struct TelemetryConfig: View { /// 2.5 Administration with session passkey let expiration = node.sessionExpiration ?? Date() if expiration < Date() || node.telemetryConfig == nil { + Logger.mesh.info("⚙️ Empty or expired telemetry module config requesting via PKI admin") _ = bleManager.requestTelemetryModuleConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) } } else { /// Legacy Administration + Logger.mesh.info("☠️ Using insecure legacy admin, empty telemetry module config") _ = bleManager.requestTelemetryModuleConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) } } diff --git a/Meshtastic/Views/Settings/Config/NetworkConfig.swift b/Meshtastic/Views/Settings/Config/NetworkConfig.swift index 8cebde85..84f48c41 100644 --- a/Meshtastic/Views/Settings/Config/NetworkConfig.swift +++ b/Meshtastic/Views/Settings/Config/NetworkConfig.swift @@ -133,7 +133,6 @@ struct NetworkConfig: View { .onFirstAppear { // Need to request a NetworkConfig from the remote node before allowing changes if let connectedPeripheral = bleManager.connectedPeripheral, let node { - Logger.mesh.info("empty network config") let connectedNode = getNodeInfo(id: connectedPeripheral.num, context: context) if let connectedNode { if node.num != connectedNode.num { @@ -141,10 +140,12 @@ struct NetworkConfig: View { /// 2.5 Administration with session passkey let expiration = node.sessionExpiration ?? Date() if expiration < Date() || node.networkConfig == nil { + Logger.mesh.info("⚙️ Empty or expired network config requesting via PKI admin") _ = bleManager.requestNetworkConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) } } else { /// Legacy Administration + Logger.mesh.info("☠️ Using insecure legacy admin, empty network config") _ = bleManager.requestNetworkConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) } } diff --git a/Meshtastic/Views/Settings/Config/PositionConfig.swift b/Meshtastic/Views/Settings/Config/PositionConfig.swift index fe61d1fa..f963b02b 100644 --- a/Meshtastic/Views/Settings/Config/PositionConfig.swift +++ b/Meshtastic/Views/Settings/Config/PositionConfig.swift @@ -189,32 +189,49 @@ struct PositionConfig: View { Label("Altitude", systemImage: "arrow.up") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + .onChange(of: includeAltitude) { _, newIncludeAltitude in + if newIncludeAltitude != PositionFlags(rawValue: self.positionFlags).contains(.Altitude) { hasChanges = true } + } Toggle(isOn: $includeSatsinview) { Label("Number of satellites", systemImage: "skew") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + .onChange(of: includeSatsinview) { _, newIncludeSatsinview in + if newIncludeSatsinview != PositionFlags(rawValue: self.positionFlags).contains(.Satsinview) { hasChanges = true } + } Toggle(isOn: $includeSeqNo) { // 64 Label("Sequence number", systemImage: "number") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + .onChange(of: includeSeqNo) { _, newIncludeSeqNo in + if newIncludeSeqNo != PositionFlags(rawValue: self.positionFlags).contains(.SeqNo) { hasChanges = true } + } Toggle(isOn: $includeTimestamp) { // 128 Label("timestamp", systemImage: "clock") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + .onChange(of: includeTimestamp) { _, newIncludeTimestamp in + if newIncludeTimestamp != PositionFlags(rawValue: self.positionFlags).contains(.Timestamp) { hasChanges = true } + } Toggle(isOn: $includeHeading) { // 128 Label("Vehicle heading", systemImage: "location.circle") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + .onChange(of: includeHeading) { _, newIncludeHeading in + if newIncludeHeading != PositionFlags(rawValue: self.positionFlags).contains(.Heading) { hasChanges = true } + } Toggle(isOn: $includeSpeed) { // 128 - Label("Vehicle speed", systemImage: "speedometer") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + .onChange(of: includeSpeed) { _, newIncludeSpeed in + if newIncludeSpeed != PositionFlags(rawValue: self.positionFlags).contains(.Speed) { hasChanges = true } + } } } @@ -227,22 +244,35 @@ struct PositionConfig: View { Label("Altitude is Mean Sea Level", systemImage: "arrow.up.to.line.compact") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + .onChange(of: includeAltitudeMsl) { _, newIncludeAltitudeMsl in + if newIncludeAltitudeMsl != PositionFlags(rawValue: self.positionFlags).contains(.AltitudeMsl) { hasChanges = true } + } + Toggle(isOn: $includeGeoidalSeparation) { Label("Altitude Geoidal Separation", systemImage: "globe.americas") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + .onChange(of: includeGeoidalSeparation) { _, newIncludeGeoidalSeparation in + if newIncludeGeoidalSeparation != PositionFlags(rawValue: self.positionFlags).contains(.GeoidalSeparation) { hasChanges = true } + } } Toggle(isOn: $includeDop) { Text("Dilution of precision (DOP) PDOP used by default") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + .onChange(of: includeDop) { _, newIncludeDop in + if newIncludeDop != PositionFlags(rawValue: self.positionFlags).contains(.Dop) { hasChanges = true } + } if includeDop { Toggle(isOn: $includeHvdop) { Text("If DOP is set, use HDOP / VDOP values instead of PDOP") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + .onChange(of: includeHvdop) { _, newIncludeHvdop in + if newIncludeHvdop != PositionFlags(rawValue: self.positionFlags).contains(.Hvdop) { hasChanges = true } + } } } } @@ -380,7 +410,6 @@ struct PositionConfig: View { supportedVersion = bleManager.connectedVersion == "0.0.0" || self.minimumVersion.compare(bleManager.connectedVersion, options: .numeric) == .orderedAscending || minimumVersion.compare(bleManager.connectedVersion, options: .numeric) == .orderedSame // Need to request a NetworkConfig from the remote node before allowing changes if let connectedPeripheral = bleManager.connectedPeripheral, let node { - Logger.mesh.info("empty position config") let connectedNode = getNodeInfo(id: connectedPeripheral.num, context: context) if let connectedNode { if node.num != connectedNode.num { @@ -388,10 +417,12 @@ struct PositionConfig: View { /// 2.5 Administration with session passkey let expiration = node.sessionExpiration ?? Date() if expiration < Date() || node.positionConfig == nil { + Logger.mesh.info("⚙️ Empty or expired position config requesting via PKI admin") _ = bleManager.requestPositionConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) } } else { /// Legacy Administration + Logger.mesh.info("☠️ Using insecure legacy admin, empty position config") _ = bleManager.requestPositionConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) } } diff --git a/Meshtastic/Views/Settings/Config/PowerConfig.swift b/Meshtastic/Views/Settings/Config/PowerConfig.swift index 822fd1e0..e9f7c0e5 100644 --- a/Meshtastic/Views/Settings/Config/PowerConfig.swift +++ b/Meshtastic/Views/Settings/Config/PowerConfig.swift @@ -130,7 +130,7 @@ struct PowerConfig: View { } // Need to request a NetworkConfig from the remote node before allowing changes if let connectedPeripheral = bleManager.connectedPeripheral, let node { - Logger.mesh.info("empty power config") + let connectedNode = getNodeInfo(id: connectedPeripheral.num, context: context) if let connectedNode { if node.num != connectedNode.num { @@ -138,10 +138,12 @@ struct PowerConfig: View { /// 2.5 Administration with session passkey let expiration = node.sessionExpiration ?? Date() if expiration < Date() || node.powerConfig == nil { + Logger.mesh.info("⚙️ Empty or expired power config requesting via PKI admin") _ = bleManager.requestPowerConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) } } else { /// Legacy Administration + Logger.mesh.info("☠️ Using insecure legacy admin, empty power config") _ = bleManager.requestPowerConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) } } diff --git a/Meshtastic/Views/Settings/Config/SecurityConfig.swift b/Meshtastic/Views/Settings/Config/SecurityConfig.swift index f8d64c85..6f16c8ff 100644 --- a/Meshtastic/Views/Settings/Config/SecurityConfig.swift +++ b/Meshtastic/Views/Settings/Config/SecurityConfig.swift @@ -85,7 +85,7 @@ struct SecurityConfig: View { .font(idiom == .phone ? .caption : .callout) Divider() Label("Tertiary Admin Key", systemImage: "key.viewfinder") - SecureInput("Tertiary Admin Key", text: $adminKey2, isValid: $hasValidAdminKey2) + SecureInput("Tertiary Admin Key", text: $adminKey3, isValid: $hasValidAdminKey3) .background( RoundedRectangle(cornerRadius: 10.0) .stroke(hasValidAdminKey3 ? Color.clear : Color.red, lineWidth: 2.0) @@ -198,7 +198,6 @@ struct SecurityConfig: View { .onFirstAppear { // Need to request a DeviceConfig from the remote node before allowing changes if let connectedPeripheral = bleManager.connectedPeripheral, let node { - Logger.mesh.info("empty security config") let connectedNode = getNodeInfo(id: connectedPeripheral.num, context: context) if let connectedNode { if node.num != connectedNode.num { @@ -206,11 +205,13 @@ struct SecurityConfig: View { /// 2.5 Administration with session passkey let expiration = node.sessionExpiration ?? Date() if expiration < Date() || node.securityConfig == nil { + Logger.mesh.info("⚙️ Empty or expired security config requesting via PKI admin") _ = bleManager.requestSecurityConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) } } else { if node.deviceConfig == nil { /// Legacy Administration + Logger.mesh.info("☠️ Using insecure legacy admin, empty security config") _ = bleManager.requestSecurityConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) } } @@ -259,8 +260,8 @@ struct SecurityConfig: View { self.publicKey = node?.securityConfig?.publicKey?.base64EncodedString() ?? "" self.privateKey = node?.securityConfig?.privateKey?.base64EncodedString() ?? "" self.adminKey = node?.securityConfig?.adminKey?.base64EncodedString() ?? "" - self.adminKey2 = node?.securityConfig?.adminKey?.base64EncodedString() ?? "" - self.adminKey3 = node?.securityConfig?.adminKey?.base64EncodedString() ?? "" + self.adminKey2 = node?.securityConfig?.adminKey2?.base64EncodedString() ?? "" + self.adminKey3 = node?.securityConfig?.adminKey3?.base64EncodedString() ?? "" self.isManaged = node?.securityConfig?.isManaged ?? false self.serialEnabled = node?.securityConfig?.serialEnabled ?? false self.debugLogApiEnabled = node?.securityConfig?.debugLogApiEnabled ?? false diff --git a/Meshtastic/Views/Settings/Firmware.swift b/Meshtastic/Views/Settings/Firmware.swift index 9962e69d..76922d54 100644 --- a/Meshtastic/Views/Settings/Firmware.swift +++ b/Meshtastic/Views/Settings/Firmware.swift @@ -148,7 +148,7 @@ struct Firmware: View { VStack(alignment: .leading) { Text("ESP32 Device Firmware Update") .font(.title3) - Text("Currently the reccomended 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.") + Text("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.") .font(.caption) Link("Web Flasher", destination: URL(string: "https://flash.meshtastic.org")!) .font(.callout) @@ -185,7 +185,7 @@ struct Firmware: View { } .padding() .padding(.bottom, 5) - .onAppear { + .onFirstAppear { Api().loadDeviceHardwareData { (hw) in for device in hw { let currentHardware = node?.user?.hwModel ?? "UNSET" diff --git a/Meshtastic/Views/Settings/Routes.swift b/Meshtastic/Views/Settings/Routes.swift index 174221d2..74f9ac72 100644 --- a/Meshtastic/Views/Settings/Routes.swift +++ b/Meshtastic/Views/Settings/Routes.swift @@ -165,7 +165,7 @@ struct Routes: View { if selectedRoute != nil { let locationArray = selectedRoute?.locations?.array as? [LocationEntity] ?? [] let lineCoords = locationArray.compactMap({(location) -> CLLocationCoordinate2D in - return location.locationCoordinate ?? LocationHelper.DefaultLocation + return location.locationCoordinate ?? LocationsHandler.DefaultLocation }) Form { TextField( @@ -248,7 +248,7 @@ struct Routes: View { hasChanges = true } Map { - Annotation("Start", coordinate: lineCoords.first ?? LocationHelper.DefaultLocation) { + Annotation("Start", coordinate: lineCoords.first ?? LocationsHandler.DefaultLocation) { ZStack { Circle() .fill(Color(.green)) @@ -257,7 +257,7 @@ struct Routes: View { } } .annotationTitles(.automatic) - Annotation("Finish", coordinate: lineCoords.last ?? LocationHelper.DefaultLocation) { + Annotation("Finish", coordinate: lineCoords.last ?? LocationsHandler.DefaultLocation) { ZStack { Circle() .fill(Color(.black)) diff --git a/MeshtasticProtobufs/Sources/meshtastic/admin.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/admin.pb.swift index 53d244d1..15510b87 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/admin.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/admin.pb.swift @@ -1,5 +1,6 @@ // DO NOT EDIT. // swift-format-ignore-file +// swiftlint:disable all // // Generated by the Swift generator plugin for the protocol buffer compiler. // Source: meshtastic/admin.proto @@ -24,7 +25,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP /// This message is handled by the Admin module and is responsible for all settings/channel read/write operations. /// This message is used to do settings operations to both remote AND local nodes. /// (Prior to 1.2 these operations were done via special ToRadio operations) -public struct AdminMessage { +public struct AdminMessage: @unchecked Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -386,6 +387,56 @@ public struct AdminMessage { set {payloadVariant = .setTimeOnly(newValue)} } + /// + /// Tell the node to send the stored ui data. + public var getUiConfigRequest: Bool { + get { + if case .getUiConfigRequest(let v)? = payloadVariant {return v} + return false + } + set {payloadVariant = .getUiConfigRequest(newValue)} + } + + /// + /// Reply stored device ui data. + public var getUiConfigResponse: DeviceUIConfig { + get { + if case .getUiConfigResponse(let v)? = payloadVariant {return v} + return DeviceUIConfig() + } + set {payloadVariant = .getUiConfigResponse(newValue)} + } + + /// + /// Tell the node to store UI data persistently. + public var storeUiConfig: DeviceUIConfig { + get { + if case .storeUiConfig(let v)? = payloadVariant {return v} + return DeviceUIConfig() + } + set {payloadVariant = .storeUiConfig(newValue)} + } + + /// + /// Set specified node-num to be ignored on the NodeDB on the device + public var setIgnoredNode: UInt32 { + get { + if case .setIgnoredNode(let v)? = payloadVariant {return v} + return 0 + } + set {payloadVariant = .setIgnoredNode(newValue)} + } + + /// + /// Set specified node-num to be un-ignored on the NodeDB on the device + public var removeIgnoredNode: UInt32 { + get { + if case .removeIgnoredNode(let v)? = payloadVariant {return v} + return 0 + } + set {payloadVariant = .removeIgnoredNode(newValue)} + } + /// /// Begins an edit transaction for config, module config, owner, and channel settings changes /// This will delay the standard *implicit* save to the file system and subsequent reboot behavior until committed (commit_edit_settings) @@ -483,7 +534,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) @@ -594,6 +645,21 @@ public struct AdminMessage { /// Convenience method to set the time on the node (as Net quality) without any other position data case setTimeOnly(UInt32) /// + /// Tell the node to send the stored ui data. + case getUiConfigRequest(Bool) + /// + /// Reply stored device ui data. + case getUiConfigResponse(DeviceUIConfig) + /// + /// Tell the node to store UI data persistently. + case storeUiConfig(DeviceUIConfig) + /// + /// Set specified node-num to be ignored on the NodeDB on the device + case setIgnoredNode(UInt32) + /// + /// Set specified node-num to be un-ignored on the NodeDB on the device + case removeIgnoredNode(UInt32) + /// /// Begins an edit transaction for config, module config, owner, and channel settings changes /// This will delay the standard *implicit* save to the file system and subsequent reboot behavior until committed (commit_edit_settings) case beginEditSettings(Bool) @@ -624,193 +690,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 (.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 /// @@ -845,6 +729,10 @@ public struct AdminMessage { /// TODO: REPLACE case securityConfig // = 7 case sessionkeyConfig // = 8 + + /// + /// device-ui config + case deviceuiConfig // = 9 case UNRECOGNIZED(Int) public init() { @@ -862,6 +750,7 @@ public struct AdminMessage { case 6: self = .bluetoothConfig case 7: self = .securityConfig case 8: self = .sessionkeyConfig + case 9: self = .deviceuiConfig default: self = .UNRECOGNIZED(rawValue) } } @@ -877,15 +766,30 @@ public struct AdminMessage { case .bluetoothConfig: return 6 case .securityConfig: return 7 case .sessionkeyConfig: return 8 + case .deviceuiConfig: return 9 case .UNRECOGNIZED(let i): return i } } + // 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 /// @@ -983,52 +887,31 @@ 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 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, - ] -} - -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. @@ -1058,7 +941,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. @@ -1072,15 +955,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" @@ -1123,6 +997,11 @@ extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat 41: .standard(proto: "set_fixed_position"), 42: .standard(proto: "remove_fixed_position"), 43: .standard(proto: "set_time_only"), + 44: .standard(proto: "get_ui_config_request"), + 45: .standard(proto: "get_ui_config_response"), + 46: .standard(proto: "store_ui_config"), + 47: .standard(proto: "set_ignored_node"), + 48: .standard(proto: "remove_ignored_node"), 64: .standard(proto: "begin_edit_settings"), 65: .standard(proto: "commit_edit_settings"), 94: .standard(proto: "factory_reset_device"), @@ -1477,6 +1356,56 @@ extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat self.payloadVariant = .setTimeOnly(v) } }() + case 44: try { + var v: Bool? + try decoder.decodeSingularBoolField(value: &v) + if let v = v { + if self.payloadVariant != nil {try decoder.handleConflictingOneOf()} + self.payloadVariant = .getUiConfigRequest(v) + } + }() + case 45: try { + var v: DeviceUIConfig? + var hadOneofValue = false + if let current = self.payloadVariant { + hadOneofValue = true + if case .getUiConfigResponse(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.payloadVariant = .getUiConfigResponse(v) + } + }() + case 46: try { + var v: DeviceUIConfig? + var hadOneofValue = false + if let current = self.payloadVariant { + hadOneofValue = true + if case .storeUiConfig(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.payloadVariant = .storeUiConfig(v) + } + }() + case 47: try { + var v: UInt32? + try decoder.decodeSingularUInt32Field(value: &v) + if let v = v { + if self.payloadVariant != nil {try decoder.handleConflictingOneOf()} + self.payloadVariant = .setIgnoredNode(v) + } + }() + case 48: try { + var v: UInt32? + try decoder.decodeSingularUInt32Field(value: &v) + if let v = v { + if self.payloadVariant != nil {try decoder.handleConflictingOneOf()} + self.payloadVariant = .removeIgnoredNode(v) + } + }() case 64: try { var v: Bool? try decoder.decodeSingularBoolField(value: &v) @@ -1697,6 +1626,26 @@ extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat guard case .setTimeOnly(let v)? = self.payloadVariant else { preconditionFailure() } try visitor.visitSingularFixed32Field(value: v, fieldNumber: 43) }() + case .getUiConfigRequest?: try { + guard case .getUiConfigRequest(let v)? = self.payloadVariant else { preconditionFailure() } + try visitor.visitSingularBoolField(value: v, fieldNumber: 44) + }() + case .getUiConfigResponse?: try { + guard case .getUiConfigResponse(let v)? = self.payloadVariant else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 45) + }() + case .storeUiConfig?: try { + guard case .storeUiConfig(let v)? = self.payloadVariant else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 46) + }() + case .setIgnoredNode?: try { + guard case .setIgnoredNode(let v)? = self.payloadVariant else { preconditionFailure() } + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 47) + }() + case .removeIgnoredNode?: try { + guard case .removeIgnoredNode(let v)? = self.payloadVariant else { preconditionFailure() } + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 48) + }() case .beginEditSettings?: try { guard case .beginEditSettings(let v)? = self.payloadVariant else { preconditionFailure() } try visitor.visitSingularBoolField(value: v, fieldNumber: 64) @@ -1760,6 +1709,7 @@ extension AdminMessage.ConfigType: SwiftProtobuf._ProtoNameProviding { 6: .same(proto: "BLUETOOTH_CONFIG"), 7: .same(proto: "SECURITY_CONFIG"), 8: .same(proto: "SESSIONKEY_CONFIG"), + 9: .same(proto: "DEVICEUI_CONFIG"), ] } @@ -1812,7 +1762,7 @@ extension HamParameters: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementa if self.txPower != 0 { try visitor.visitSingularInt32Field(value: self.txPower, fieldNumber: 2) } - if self.frequency != 0 { + if self.frequency.bitPattern != 0 { try visitor.visitSingularFloatField(value: self.frequency, fieldNumber: 3) } if !self.shortName.isEmpty { diff --git a/MeshtasticProtobufs/Sources/meshtastic/apponly.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/apponly.pb.swift index 0457077c..52dac5ca 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/apponly.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/apponly.pb.swift @@ -1,5 +1,6 @@ // DO NOT EDIT. // swift-format-ignore-file +// swiftlint:disable all // // Generated by the Swift generator plugin for the protocol buffer compiler. // Source: meshtastic/apponly.proto @@ -7,7 +8,6 @@ // For information on using the generated types, please see the documentation: // https://github.com/apple/swift-protobuf/ -import Foundation import SwiftProtobuf // If the compiler emits an error on this type, it is because this file @@ -26,7 +26,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP /// any SECONDARY channels. /// No DISABLED channels are included. /// This abstraction is used only on the the 'app side' of the world (ie python, javascript and android etc) to show a group of Channels as a (long) URL -public struct ChannelSet { +public struct ChannelSet: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -53,10 +53,6 @@ public struct ChannelSet { fileprivate var _loraConfig: Config.LoRaConfig? = nil } -#if swift(>=5.5) && canImport(_Concurrency) -extension ChannelSet: @unchecked Sendable {} -#endif // swift(>=5.5) && canImport(_Concurrency) - // MARK: - Code below here is support for the SwiftProtobuf runtime. fileprivate let _protobuf_package = "meshtastic" diff --git a/MeshtasticProtobufs/Sources/meshtastic/atak.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/atak.pb.swift index 867648a9..06d6af88 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/atak.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/atak.pb.swift @@ -1,5 +1,6 @@ // DO NOT EDIT. // swift-format-ignore-file +// swiftlint:disable all // // Generated by the Swift generator plugin for the protocol buffer compiler. // Source: meshtastic/atak.proto @@ -20,7 +21,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP typealias Version = _2 } -public enum Team: SwiftProtobuf.Enum { +public enum Team: SwiftProtobuf.Enum, Swift.CaseIterable { public typealias RawValue = Int /// @@ -130,11 +131,6 @@ public enum Team: SwiftProtobuf.Enum { } } -} - -#if swift(>=4.2) - -extension Team: CaseIterable { // The compiler won't synthesize support with the UNRECOGNIZED case. public static let allCases: [Team] = [ .unspecifedColor, @@ -153,13 +149,12 @@ extension Team: CaseIterable { .darkGreen, .brown, ] -} -#endif // swift(>=4.2) +} /// /// Role of the group member -public enum MemberRole: SwiftProtobuf.Enum { +public enum MemberRole: SwiftProtobuf.Enum, Swift.CaseIterable { public typealias RawValue = Int /// @@ -233,11 +228,6 @@ public enum MemberRole: SwiftProtobuf.Enum { } } -} - -#if swift(>=4.2) - -extension MemberRole: CaseIterable { // The compiler won't synthesize support with the UNRECOGNIZED case. public static let allCases: [MemberRole] = [ .unspecifed, @@ -250,13 +240,12 @@ extension MemberRole: CaseIterable { .rto, .k9, ] -} -#endif // swift(>=4.2) +} /// /// Packets for the official ATAK Plugin -public struct TAKPacket { +public struct TAKPacket: @unchecked Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -337,7 +326,7 @@ public struct TAKPacket { /// /// The payload of the packet - public enum OneOf_PayloadVariant: Equatable { + public enum OneOf_PayloadVariant: Equatable, @unchecked Sendable { /// /// TAK position report case pli(PLI) @@ -349,28 +338,6 @@ public struct TAKPacket { /// May be compressed / truncated by the sender (EUD) case detail(Data) - #if !swift(>=4.1) - public static func ==(lhs: TAKPacket.OneOf_PayloadVariant, rhs: TAKPacket.OneOf_PayloadVariant) -> Bool { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch (lhs, rhs) { - case (.pli, .pli): return { - guard case .pli(let l) = lhs, case .pli(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.chat, .chat): return { - guard case .chat(let l) = lhs, case .chat(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.detail, .detail): return { - guard case .detail(let l) = lhs, case .detail(let r) = rhs else { preconditionFailure() } - return l == r - }() - default: return false - } - } - #endif } public init() {} @@ -382,7 +349,7 @@ public struct TAKPacket { /// /// ATAK GeoChat message -public struct GeoChat { +public struct GeoChat: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -424,7 +391,7 @@ public struct GeoChat { /// /// ATAK Group /// <__group role='Team Member' name='Cyan'/> -public struct Group { +public struct Group: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -446,7 +413,7 @@ public struct Group { /// /// ATAK EUD Status /// -public struct Status { +public struct Status: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -463,7 +430,7 @@ public struct Status { /// /// ATAK Contact /// -public struct Contact { +public struct Contact: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -483,7 +450,7 @@ public struct Contact { /// /// Position Location Information from ATAK -public struct PLI { +public struct PLI: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -515,18 +482,6 @@ public struct PLI { public init() {} } -#if swift(>=5.5) && canImport(_Concurrency) -extension Team: @unchecked Sendable {} -extension MemberRole: @unchecked Sendable {} -extension TAKPacket: @unchecked Sendable {} -extension TAKPacket.OneOf_PayloadVariant: @unchecked Sendable {} -extension GeoChat: @unchecked Sendable {} -extension Group: @unchecked Sendable {} -extension Status: @unchecked Sendable {} -extension Contact: @unchecked Sendable {} -extension PLI: @unchecked Sendable {} -#endif // swift(>=5.5) && canImport(_Concurrency) - // MARK: - Code below here is support for the SwiftProtobuf runtime. fileprivate let _protobuf_package = "meshtastic" diff --git a/MeshtasticProtobufs/Sources/meshtastic/cannedmessages.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/cannedmessages.pb.swift index 1b8c84de..ce1f0503 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/cannedmessages.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/cannedmessages.pb.swift @@ -1,5 +1,6 @@ // DO NOT EDIT. // swift-format-ignore-file +// swiftlint:disable all // // Generated by the Swift generator plugin for the protocol buffer compiler. // Source: meshtastic/cannedmessages.proto @@ -7,7 +8,6 @@ // For information on using the generated types, please see the documentation: // https://github.com/apple/swift-protobuf/ -import Foundation import SwiftProtobuf // If the compiler emits an error on this type, it is because this file @@ -22,7 +22,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP /// /// Canned message module configuration. -public struct CannedMessageModuleConfig { +public struct CannedMessageModuleConfig: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -36,10 +36,6 @@ public struct CannedMessageModuleConfig { public init() {} } -#if swift(>=5.5) && canImport(_Concurrency) -extension CannedMessageModuleConfig: @unchecked Sendable {} -#endif // swift(>=5.5) && canImport(_Concurrency) - // MARK: - Code below here is support for the SwiftProtobuf runtime. fileprivate let _protobuf_package = "meshtastic" diff --git a/MeshtasticProtobufs/Sources/meshtastic/channel.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/channel.pb.swift index 5b9c7e49..180cd698 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/channel.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/channel.pb.swift @@ -1,5 +1,6 @@ // DO NOT EDIT. // swift-format-ignore-file +// swiftlint:disable all // // Generated by the Swift generator plugin for the protocol buffer compiler. // Source: meshtastic/channel.proto @@ -36,13 +37,15 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP /// FIXME: Add description of multi-channel support and how primary vs secondary channels are used. /// FIXME: explain how apps use channels for security. /// explain how remote settings and remote gpio are managed as an example -public struct ChannelSettings { +public struct ChannelSettings: @unchecked Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. /// /// Deprecated in favor of LoraConfig.channel_num + /// + /// NOTE: This field was marked as deprecated in the .proto file. public var channelNum: UInt32 = 0 /// @@ -111,7 +114,7 @@ public struct ChannelSettings { /// /// This message is specifically for modules to store per-channel configuration data. -public struct ModuleSettings { +public struct ModuleSettings: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -132,7 +135,7 @@ public struct ModuleSettings { /// /// A pair of a channel number, mode and the (sharable) settings for that channel -public struct Channel { +public struct Channel: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -170,7 +173,7 @@ public struct Channel { /// cross band routing as needed. /// If a device has only a single radio (the common case) only one channel can be PRIMARY at a time /// (but any number of SECONDARY channels can't be sent received on that common frequency) - public enum Role: SwiftProtobuf.Enum { + public enum Role: SwiftProtobuf.Enum, Swift.CaseIterable { public typealias RawValue = Int /// @@ -209,6 +212,13 @@ public struct Channel { } } + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [Channel.Role] = [ + .disabled, + .primary, + .secondary, + ] + } public init() {} @@ -216,26 +226,6 @@ public struct Channel { fileprivate var _settings: ChannelSettings? = nil } -#if swift(>=4.2) - -extension Channel.Role: CaseIterable { - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [Channel.Role] = [ - .disabled, - .primary, - .secondary, - ] -} - -#endif // swift(>=4.2) - -#if swift(>=5.5) && canImport(_Concurrency) -extension ChannelSettings: @unchecked Sendable {} -extension ModuleSettings: @unchecked Sendable {} -extension Channel: @unchecked Sendable {} -extension Channel.Role: @unchecked Sendable {} -#endif // swift(>=5.5) && canImport(_Concurrency) - // MARK: - Code below here is support for the SwiftProtobuf runtime. fileprivate let _protobuf_package = "meshtastic" diff --git a/MeshtasticProtobufs/Sources/meshtastic/clientonly.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/clientonly.pb.swift index f89a8e3c..d72c0ae1 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/clientonly.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/clientonly.pb.swift @@ -1,5 +1,6 @@ // DO NOT EDIT. // swift-format-ignore-file +// swiftlint:disable all // // Generated by the Swift generator plugin for the protocol buffer compiler. // Source: meshtastic/clientonly.proto @@ -7,7 +8,6 @@ // For information on using the generated types, please see the documentation: // https://github.com/apple/swift-protobuf/ -import Foundation import SwiftProtobuf // If the compiler emits an error on this type, it is because this file @@ -23,7 +23,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP /// /// This abstraction is used to contain any configuration for provisioning a node on any client. /// It is useful for importing and exporting configurations. -public struct DeviceProfile { +public struct DeviceProfile: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -130,10 +130,6 @@ public struct DeviceProfile { fileprivate var _cannedMessages: String? = nil } -#if swift(>=5.5) && canImport(_Concurrency) -extension DeviceProfile: @unchecked Sendable {} -#endif // swift(>=5.5) && canImport(_Concurrency) - // MARK: - Code below here is support for the SwiftProtobuf runtime. fileprivate let _protobuf_package = "meshtastic" diff --git a/MeshtasticProtobufs/Sources/meshtastic/config.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/config.pb.swift index 38df8ea0..566b3ef1 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/config.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/config.pb.swift @@ -1,5 +1,6 @@ // DO NOT EDIT. // swift-format-ignore-file +// swiftlint:disable all // // Generated by the Swift generator plugin for the protocol buffer compiler. // Source: meshtastic/config.proto @@ -20,7 +21,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP typealias Version = _2 } -public struct Config { +public struct Config: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -101,11 +102,19 @@ public struct Config { set {payloadVariant = .sessionkey(newValue)} } + public var deviceUi: DeviceUIConfig { + get { + if case .deviceUi(let v)? = payloadVariant {return v} + return DeviceUIConfig() + } + set {payloadVariant = .deviceUi(newValue)} + } + public var unknownFields = SwiftProtobuf.UnknownStorage() /// /// Payload Variant - public enum OneOf_PayloadVariant: Equatable { + public enum OneOf_PayloadVariant: Equatable, Sendable { case device(Config.DeviceConfig) case position(Config.PositionConfig) case power(Config.PowerConfig) @@ -115,58 +124,13 @@ public struct Config { case bluetooth(Config.BluetoothConfig) case security(Config.SecurityConfig) case sessionkey(Config.SessionkeyConfig) + case deviceUi(DeviceUIConfig) - #if !swift(>=4.1) - public static func ==(lhs: Config.OneOf_PayloadVariant, rhs: Config.OneOf_PayloadVariant) -> Bool { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch (lhs, rhs) { - case (.device, .device): return { - guard case .device(let l) = lhs, case .device(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.position, .position): return { - guard case .position(let l) = lhs, case .position(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.power, .power): return { - guard case .power(let l) = lhs, case .power(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.network, .network): return { - guard case .network(let l) = lhs, case .network(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.display, .display): return { - guard case .display(let l) = lhs, case .display(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.lora, .lora): return { - guard case .lora(let l) = lhs, case .lora(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.bluetooth, .bluetooth): return { - guard case .bluetooth(let l) = lhs, case .bluetooth(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.security, .security): return { - guard case .security(let l) = lhs, case .security(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.sessionkey, .sessionkey): return { - guard case .sessionkey(let l) = lhs, case .sessionkey(let r) = rhs else { preconditionFailure() } - return l == r - }() - default: return false - } - } - #endif } /// /// Configuration - public struct DeviceConfig { + public struct DeviceConfig: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -178,6 +142,8 @@ public struct Config { /// /// Disabling this will disable the SerialConsole by not initilizing the StreamAPI /// Moved to SecurityConfig + /// + /// NOTE: This field was marked as deprecated in the .proto file. public var serialEnabled: Bool = false /// @@ -207,6 +173,8 @@ public struct Config { /// If true, device is considered to be "managed" by a mesh administrator /// Clients should then limit available configuration and administrative options inside the user interface /// Moved to SecurityConfig + /// + /// NOTE: This field was marked as deprecated in the .proto file. public var isManaged: Bool = false /// @@ -225,7 +193,7 @@ public struct Config { /// /// Defines the device's role on the Mesh network - public enum Role: SwiftProtobuf.Enum { + public enum Role: SwiftProtobuf.Enum, Swift.CaseIterable { public typealias RawValue = Int /// @@ -243,6 +211,8 @@ public struct Config { /// The wifi radio and the oled screen will be put to sleep. /// This mode may still potentially have higher power usage due to it's preference in message rebroadcasting on the mesh. case router // = 2 + + /// NOTE: This enum value was marked as deprecated in the .proto file case routerClient // = 3 /// @@ -293,6 +263,14 @@ public struct Config { /// and automatic TAK PLI (position location information) broadcasts. /// Uses position module configuration to determine TAK PLI broadcast interval. case takTracker // = 10 + + /// + /// Description: Will always rebroadcast packets, but will do so after all other modes. + /// Technical Details: Used for router nodes that are intended to provide additional coverage + /// in areas not already covered by other routers, or to bridge around problematic terrain, + /// but should not be given priority over other routers in order to avoid unnecessaraily + /// consuming hops. + case routerLate // = 11 case UNRECOGNIZED(Int) public init() { @@ -312,6 +290,7 @@ public struct Config { case 8: self = .clientHidden case 9: self = .lostAndFound case 10: self = .takTracker + case 11: self = .routerLate default: self = .UNRECOGNIZED(rawValue) } } @@ -329,15 +308,32 @@ public struct Config { case .clientHidden: return 8 case .lostAndFound: return 9 case .takTracker: return 10 + case .routerLate: return 11 case .UNRECOGNIZED(let i): return i } } + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [Config.DeviceConfig.Role] = [ + .client, + .clientMute, + .router, + .routerClient, + .repeater, + .tracker, + .sensor, + .tak, + .clientHidden, + .lostAndFound, + .takTracker, + .routerLate, + ] + } /// /// Defines the device's behavior for how messages are rebroadcast - public enum RebroadcastMode: SwiftProtobuf.Enum { + public enum RebroadcastMode: SwiftProtobuf.Enum, Swift.CaseIterable { public typealias RawValue = Int /// @@ -359,6 +355,15 @@ public struct Config { /// Ignores observed messages from foreign meshes like LOCAL_ONLY, /// but takes it step further by also ignoring messages from nodenums not in the node's known list (NodeDB) case knownOnly // = 3 + + /// + /// Only permitted for SENSOR, TRACKER and TAK_TRACKER roles, this will inhibit all rebroadcasts, not unlike CLIENT_MUTE role. + case none // = 4 + + /// + /// Ignores packets from non-standard portnums such as: TAK, RangeTest, PaxCounter, etc. + /// Only rebroadcasts packets with standard portnums: NodeInfo, Text, Position, Telemetry, and Routing. + case corePortnumsOnly // = 5 case UNRECOGNIZED(Int) public init() { @@ -371,6 +376,8 @@ public struct Config { case 1: self = .allSkipDecoding case 2: self = .localOnly case 3: self = .knownOnly + case 4: self = .none + case 5: self = .corePortnumsOnly default: self = .UNRECOGNIZED(rawValue) } } @@ -381,10 +388,22 @@ public struct Config { case .allSkipDecoding: return 1 case .localOnly: return 2 case .knownOnly: return 3 + case .none: return 4 + case .corePortnumsOnly: return 5 case .UNRECOGNIZED(let i): return i } } + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [Config.DeviceConfig.RebroadcastMode] = [ + .all, + .allSkipDecoding, + .localOnly, + .knownOnly, + .none, + .corePortnumsOnly, + ] + } public init() {} @@ -392,7 +411,7 @@ public struct Config { /// /// Position Config - public struct PositionConfig { + public struct PositionConfig: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -414,6 +433,8 @@ public struct Config { /// /// Is GPS enabled for this node? + /// + /// NOTE: This field was marked as deprecated in the .proto file. public var gpsEnabled: Bool = false /// @@ -424,6 +445,8 @@ public struct Config { /// /// Deprecated in favor of using smart / regular broadcast intervals as implicit attempt time + /// + /// NOTE: This field was marked as deprecated in the .proto file. public var gpsAttemptTime: UInt32 = 0 /// @@ -464,7 +487,7 @@ public struct Config { /// are always included (also time if GPS-synced) /// NOTE: the more fields are included, the larger the message will be - /// leading to longer airtime and a higher risk of packet loss - public enum PositionFlags: SwiftProtobuf.Enum { + public enum PositionFlags: SwiftProtobuf.Enum, Swift.CaseIterable { public typealias RawValue = Int /// @@ -554,9 +577,24 @@ public struct Config { } } + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [Config.PositionConfig.PositionFlags] = [ + .unset, + .altitude, + .altitudeMsl, + .geoidalSeparation, + .dop, + .hvdop, + .satinview, + .seqNo, + .timestamp, + .heading, + .speed, + ] + } - public enum GpsMode: SwiftProtobuf.Enum { + public enum GpsMode: SwiftProtobuf.Enum, Swift.CaseIterable { public typealias RawValue = Int /// @@ -594,6 +632,13 @@ public struct Config { } } + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [Config.PositionConfig.GpsMode] = [ + .disabled, + .enabled, + .notPresent, + ] + } public init() {} @@ -602,7 +647,7 @@ public struct Config { /// /// Power Config\ /// See [Power Config](/docs/settings/config/power) for additional power config details. - public struct PowerConfig { + public struct PowerConfig: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -662,7 +707,7 @@ public struct Config { /// /// Network Config - public struct NetworkConfig { + public struct NetworkConfig: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -707,9 +752,13 @@ public struct Config { /// rsyslog Server and Port public var rsyslogServer: String = String() + /// + /// Flags for enabling/disabling network protocols + public var enabledProtocols: UInt32 = 0 + public var unknownFields = SwiftProtobuf.UnknownStorage() - public enum AddressMode: SwiftProtobuf.Enum { + public enum AddressMode: SwiftProtobuf.Enum, Swift.CaseIterable { public typealias RawValue = Int /// @@ -741,9 +790,57 @@ public struct Config { } } + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [Config.NetworkConfig.AddressMode] = [ + .dhcp, + .static, + ] + } - public struct IpV4Config { + /// + /// Available flags auxiliary network protocols + public enum ProtocolFlags: SwiftProtobuf.Enum, Swift.CaseIterable { + public typealias RawValue = Int + + /// + /// Do not broadcast packets over any network protocol + case noBroadcast // = 0 + + /// + /// Enable broadcasting packets via UDP over the local network + case udpBroadcast // = 1 + case UNRECOGNIZED(Int) + + public init() { + self = .noBroadcast + } + + public init?(rawValue: Int) { + switch rawValue { + case 0: self = .noBroadcast + case 1: self = .udpBroadcast + default: self = .UNRECOGNIZED(rawValue) + } + } + + public var rawValue: Int { + switch self { + case .noBroadcast: return 0 + case .udpBroadcast: return 1 + case .UNRECOGNIZED(let i): return i + } + } + + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [Config.NetworkConfig.ProtocolFlags] = [ + .noBroadcast, + .udpBroadcast, + ] + + } + + public struct IpV4Config: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -776,7 +873,7 @@ public struct Config { /// /// Display Config - public struct DisplayConfig { + public struct DisplayConfig: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -832,7 +929,7 @@ public struct Config { /// /// How the GPS coordinates are displayed on the OLED screen. - public enum GpsCoordinateFormat: SwiftProtobuf.Enum { + public enum GpsCoordinateFormat: SwiftProtobuf.Enum, Swift.CaseIterable { public typealias RawValue = Int /// @@ -895,11 +992,21 @@ public struct Config { } } + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [Config.DisplayConfig.GpsCoordinateFormat] = [ + .dec, + .dms, + .utm, + .mgrs, + .olc, + .osgr, + ] + } /// /// Unit display preference - public enum DisplayUnits: SwiftProtobuf.Enum { + public enum DisplayUnits: SwiftProtobuf.Enum, Swift.CaseIterable { public typealias RawValue = Int /// @@ -931,11 +1038,17 @@ public struct Config { } } + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [Config.DisplayConfig.DisplayUnits] = [ + .metric, + .imperial, + ] + } /// /// Override OLED outo detect with this if it fails. - public enum OledType: SwiftProtobuf.Enum { + public enum OledType: SwiftProtobuf.Enum, Swift.CaseIterable { public typealias RawValue = Int /// @@ -979,9 +1092,17 @@ public struct Config { } } + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [Config.DisplayConfig.OledType] = [ + .oledAuto, + .oledSsd1306, + .oledSh1106, + .oledSh1107, + ] + } - public enum DisplayMode: SwiftProtobuf.Enum { + public enum DisplayMode: SwiftProtobuf.Enum, Swift.CaseIterable { public typealias RawValue = Int /// @@ -1025,9 +1146,17 @@ public struct Config { } } + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [Config.DisplayConfig.DisplayMode] = [ + .default, + .twocolor, + .inverted, + .color, + ] + } - public enum CompassOrientation: SwiftProtobuf.Enum { + public enum CompassOrientation: SwiftProtobuf.Enum, Swift.CaseIterable { public typealias RawValue = Int /// @@ -1095,6 +1224,18 @@ public struct Config { } } + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [Config.DisplayConfig.CompassOrientation] = [ + .degrees0, + .degrees90, + .degrees180, + .degrees270, + .degrees0Inverted, + .degrees90Inverted, + .degrees180Inverted, + .degrees270Inverted, + ] + } public init() {} @@ -1102,7 +1243,7 @@ public struct Config { /// /// Lora Config - public struct LoRaConfig { + public struct LoRaConfig: @unchecked Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -1266,7 +1407,7 @@ public struct Config { public var unknownFields = SwiftProtobuf.UnknownStorage() - public enum RegionCode: SwiftProtobuf.Enum { + public enum RegionCode: SwiftProtobuf.Enum, Swift.CaseIterable { public typealias RawValue = Int /// @@ -1418,12 +1559,38 @@ public struct Config { } } + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [Config.LoRaConfig.RegionCode] = [ + .unset, + .us, + .eu433, + .eu868, + .cn, + .jp, + .anz, + .kr, + .tw, + .ru, + .in, + .nz865, + .th, + .lora24, + .ua433, + .ua868, + .my433, + .my919, + .sg923, + .ph433, + .ph868, + .ph915, + ] + } /// /// Standard predefined channel settings /// Note: these mappings must match ModemPreset Choice in the device code. - public enum ModemPreset: SwiftProtobuf.Enum { + public enum ModemPreset: SwiftProtobuf.Enum, Swift.CaseIterable { public typealias RawValue = Int /// @@ -1437,6 +1604,8 @@ public struct Config { /// /// Very Long Range - Slow /// Deprecated in 2.5: Works only with txco and is unusably slow + /// + /// NOTE: This enum value was marked as deprecated in the .proto file case veryLongSlow // = 2 /// @@ -1500,6 +1669,19 @@ public struct Config { } } + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [Config.LoRaConfig.ModemPreset] = [ + .longFast, + .longSlow, + .veryLongSlow, + .mediumSlow, + .mediumFast, + .shortSlow, + .shortFast, + .longModerate, + .shortTurbo, + ] + } public init() {} @@ -1507,7 +1689,7 @@ public struct Config { fileprivate var _storage = _StorageClass.defaultInstance } - public struct BluetoothConfig { + public struct BluetoothConfig: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -1526,7 +1708,7 @@ public struct Config { public var unknownFields = SwiftProtobuf.UnknownStorage() - public enum PairingMode: SwiftProtobuf.Enum { + public enum PairingMode: SwiftProtobuf.Enum, Swift.CaseIterable { public typealias RawValue = Int /// @@ -1564,12 +1746,19 @@ public struct Config { } } + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [Config.BluetoothConfig.PairingMode] = [ + .randomPin, + .fixedPin, + .noPin, + ] + } public init() {} } - public struct SecurityConfig { + public struct SecurityConfig: @unchecked Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -1613,7 +1802,7 @@ public struct Config { /// /// Blank config request, strictly for getting the session key - public struct SessionkeyConfig { + public struct SessionkeyConfig: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -1626,205 +1815,6 @@ public struct Config { public init() {} } -#if swift(>=4.2) - -extension Config.DeviceConfig.Role: CaseIterable { - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [Config.DeviceConfig.Role] = [ - .client, - .clientMute, - .router, - .routerClient, - .repeater, - .tracker, - .sensor, - .tak, - .clientHidden, - .lostAndFound, - .takTracker, - ] -} - -extension Config.DeviceConfig.RebroadcastMode: CaseIterable { - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [Config.DeviceConfig.RebroadcastMode] = [ - .all, - .allSkipDecoding, - .localOnly, - .knownOnly, - ] -} - -extension Config.PositionConfig.PositionFlags: CaseIterable { - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [Config.PositionConfig.PositionFlags] = [ - .unset, - .altitude, - .altitudeMsl, - .geoidalSeparation, - .dop, - .hvdop, - .satinview, - .seqNo, - .timestamp, - .heading, - .speed, - ] -} - -extension Config.PositionConfig.GpsMode: CaseIterable { - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [Config.PositionConfig.GpsMode] = [ - .disabled, - .enabled, - .notPresent, - ] -} - -extension Config.NetworkConfig.AddressMode: CaseIterable { - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [Config.NetworkConfig.AddressMode] = [ - .dhcp, - .static, - ] -} - -extension Config.DisplayConfig.GpsCoordinateFormat: CaseIterable { - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [Config.DisplayConfig.GpsCoordinateFormat] = [ - .dec, - .dms, - .utm, - .mgrs, - .olc, - .osgr, - ] -} - -extension Config.DisplayConfig.DisplayUnits: CaseIterable { - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [Config.DisplayConfig.DisplayUnits] = [ - .metric, - .imperial, - ] -} - -extension Config.DisplayConfig.OledType: CaseIterable { - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [Config.DisplayConfig.OledType] = [ - .oledAuto, - .oledSsd1306, - .oledSh1106, - .oledSh1107, - ] -} - -extension Config.DisplayConfig.DisplayMode: CaseIterable { - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [Config.DisplayConfig.DisplayMode] = [ - .default, - .twocolor, - .inverted, - .color, - ] -} - -extension Config.DisplayConfig.CompassOrientation: CaseIterable { - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [Config.DisplayConfig.CompassOrientation] = [ - .degrees0, - .degrees90, - .degrees180, - .degrees270, - .degrees0Inverted, - .degrees90Inverted, - .degrees180Inverted, - .degrees270Inverted, - ] -} - -extension Config.LoRaConfig.RegionCode: CaseIterable { - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [Config.LoRaConfig.RegionCode] = [ - .unset, - .us, - .eu433, - .eu868, - .cn, - .jp, - .anz, - .kr, - .tw, - .ru, - .in, - .nz865, - .th, - .lora24, - .ua433, - .ua868, - .my433, - .my919, - .sg923, - .ph433, - .ph868, - .ph915, - ] -} - -extension Config.LoRaConfig.ModemPreset: CaseIterable { - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [Config.LoRaConfig.ModemPreset] = [ - .longFast, - .longSlow, - .veryLongSlow, - .mediumSlow, - .mediumFast, - .shortSlow, - .shortFast, - .longModerate, - .shortTurbo, - ] -} - -extension Config.BluetoothConfig.PairingMode: CaseIterable { - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [Config.BluetoothConfig.PairingMode] = [ - .randomPin, - .fixedPin, - .noPin, - ] -} - -#endif // swift(>=4.2) - -#if swift(>=5.5) && canImport(_Concurrency) -extension Config: @unchecked Sendable {} -extension Config.OneOf_PayloadVariant: @unchecked Sendable {} -extension Config.DeviceConfig: @unchecked Sendable {} -extension Config.DeviceConfig.Role: @unchecked Sendable {} -extension Config.DeviceConfig.RebroadcastMode: @unchecked Sendable {} -extension Config.PositionConfig: @unchecked Sendable {} -extension Config.PositionConfig.PositionFlags: @unchecked Sendable {} -extension Config.PositionConfig.GpsMode: @unchecked Sendable {} -extension Config.PowerConfig: @unchecked Sendable {} -extension Config.NetworkConfig: @unchecked Sendable {} -extension Config.NetworkConfig.AddressMode: @unchecked Sendable {} -extension Config.NetworkConfig.IpV4Config: @unchecked Sendable {} -extension Config.DisplayConfig: @unchecked Sendable {} -extension Config.DisplayConfig.GpsCoordinateFormat: @unchecked Sendable {} -extension Config.DisplayConfig.DisplayUnits: @unchecked Sendable {} -extension Config.DisplayConfig.OledType: @unchecked Sendable {} -extension Config.DisplayConfig.DisplayMode: @unchecked Sendable {} -extension Config.DisplayConfig.CompassOrientation: @unchecked Sendable {} -extension Config.LoRaConfig: @unchecked Sendable {} -extension Config.LoRaConfig.RegionCode: @unchecked Sendable {} -extension Config.LoRaConfig.ModemPreset: @unchecked Sendable {} -extension Config.BluetoothConfig: @unchecked Sendable {} -extension Config.BluetoothConfig.PairingMode: @unchecked Sendable {} -extension Config.SecurityConfig: @unchecked Sendable {} -extension Config.SessionkeyConfig: @unchecked Sendable {} -#endif // swift(>=5.5) && canImport(_Concurrency) - // MARK: - Code below here is support for the SwiftProtobuf runtime. fileprivate let _protobuf_package = "meshtastic" @@ -1841,6 +1831,7 @@ extension Config: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBas 7: .same(proto: "bluetooth"), 8: .same(proto: "security"), 9: .same(proto: "sessionkey"), + 10: .standard(proto: "device_ui"), ] public mutating func decodeMessage(decoder: inout D) throws { @@ -1966,6 +1957,19 @@ extension Config: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBas self.payloadVariant = .sessionkey(v) } }() + case 10: try { + var v: DeviceUIConfig? + var hadOneofValue = false + if let current = self.payloadVariant { + hadOneofValue = true + if case .deviceUi(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.payloadVariant = .deviceUi(v) + } + }() default: break } } @@ -2013,6 +2017,10 @@ extension Config: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBas guard case .sessionkey(let v)? = self.payloadVariant else { preconditionFailure() } try visitor.visitSingularMessageField(value: v, fieldNumber: 9) }() + case .deviceUi?: try { + guard case .deviceUi(let v)? = self.payloadVariant else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 10) + }() case nil: break } try unknownFields.traverse(visitor: &visitor) @@ -2130,6 +2138,7 @@ extension Config.DeviceConfig.Role: SwiftProtobuf._ProtoNameProviding { 8: .same(proto: "CLIENT_HIDDEN"), 9: .same(proto: "LOST_AND_FOUND"), 10: .same(proto: "TAK_TRACKER"), + 11: .same(proto: "ROUTER_LATE"), ] } @@ -2139,6 +2148,8 @@ extension Config.DeviceConfig.RebroadcastMode: SwiftProtobuf._ProtoNameProviding 1: .same(proto: "ALL_SKIP_DECODING"), 2: .same(proto: "LOCAL_ONLY"), 3: .same(proto: "KNOWN_ONLY"), + 4: .same(proto: "NONE"), + 5: .same(proto: "CORE_PORTNUMS_ONLY"), ] } @@ -2311,7 +2322,7 @@ extension Config.PowerConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImple if self.onBatteryShutdownAfterSecs != 0 { try visitor.visitSingularUInt32Field(value: self.onBatteryShutdownAfterSecs, fieldNumber: 2) } - if self.adcMultiplierOverride != 0 { + if self.adcMultiplierOverride.bitPattern != 0 { try visitor.visitSingularFloatField(value: self.adcMultiplierOverride, fieldNumber: 3) } if self.waitBluetoothSecs != 0 { @@ -2361,6 +2372,7 @@ extension Config.NetworkConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImp 7: .standard(proto: "address_mode"), 8: .standard(proto: "ipv4_config"), 9: .standard(proto: "rsyslog_server"), + 10: .standard(proto: "enabled_protocols"), ] public mutating func decodeMessage(decoder: inout D) throws { @@ -2377,6 +2389,7 @@ extension Config.NetworkConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImp case 7: try { try decoder.decodeSingularEnumField(value: &self.addressMode) }() case 8: try { try decoder.decodeSingularMessageField(value: &self._ipv4Config) }() case 9: try { try decoder.decodeSingularStringField(value: &self.rsyslogServer) }() + case 10: try { try decoder.decodeSingularUInt32Field(value: &self.enabledProtocols) }() default: break } } @@ -2411,6 +2424,9 @@ extension Config.NetworkConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImp if !self.rsyslogServer.isEmpty { try visitor.visitSingularStringField(value: self.rsyslogServer, fieldNumber: 9) } + if self.enabledProtocols != 0 { + try visitor.visitSingularUInt32Field(value: self.enabledProtocols, fieldNumber: 10) + } try unknownFields.traverse(visitor: &visitor) } @@ -2423,6 +2439,7 @@ extension Config.NetworkConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImp if lhs.addressMode != rhs.addressMode {return false} if lhs._ipv4Config != rhs._ipv4Config {return false} if lhs.rsyslogServer != rhs.rsyslogServer {return false} + if lhs.enabledProtocols != rhs.enabledProtocols {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } @@ -2435,6 +2452,13 @@ extension Config.NetworkConfig.AddressMode: SwiftProtobuf._ProtoNameProviding { ] } +extension Config.NetworkConfig.ProtocolFlags: SwiftProtobuf._ProtoNameProviding { + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "NO_BROADCAST"), + 1: .same(proto: "UDP_BROADCAST"), + ] +} + extension Config.NetworkConfig.IpV4Config: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = Config.NetworkConfig.protoMessageName + ".IpV4Config" public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ @@ -2759,7 +2783,7 @@ extension Config.LoRaConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem if _storage._codingRate != 0 { try visitor.visitSingularUInt32Field(value: _storage._codingRate, fieldNumber: 5) } - if _storage._frequencyOffset != 0 { + if _storage._frequencyOffset.bitPattern != 0 { try visitor.visitSingularFloatField(value: _storage._frequencyOffset, fieldNumber: 6) } if _storage._region != .unset { @@ -2783,7 +2807,7 @@ extension Config.LoRaConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem if _storage._sx126XRxBoostedGain != false { try visitor.visitSingularBoolField(value: _storage._sx126XRxBoostedGain, fieldNumber: 13) } - if _storage._overrideFrequency != 0 { + if _storage._overrideFrequency.bitPattern != 0 { try visitor.visitSingularFloatField(value: _storage._overrideFrequency, fieldNumber: 14) } if _storage._paFanDisabled != false { @@ -3000,8 +3024,8 @@ extension Config.SessionkeyConfig: SwiftProtobuf.Message, SwiftProtobuf._Message public static let _protobuf_nameMap = SwiftProtobuf._NameMap() public mutating func decodeMessage(decoder: inout D) throws { - while let _ = try decoder.nextFieldNumber() { - } + // Load everything into unknown fields + while try decoder.nextFieldNumber() != nil {} } public func traverse(visitor: inout V) throws { diff --git a/MeshtasticProtobufs/Sources/meshtastic/connection_status.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/connection_status.pb.swift index a2ec180e..6847c0e3 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/connection_status.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/connection_status.pb.swift @@ -1,5 +1,6 @@ // DO NOT EDIT. // swift-format-ignore-file +// swiftlint:disable all // // Generated by the Swift generator plugin for the protocol buffer compiler. // Source: meshtastic/connection_status.proto @@ -7,7 +8,6 @@ // For information on using the generated types, please see the documentation: // https://github.com/apple/swift-protobuf/ -import Foundation import SwiftProtobuf // If the compiler emits an error on this type, it is because this file @@ -20,7 +20,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP typealias Version = _2 } -public struct DeviceConnectionStatus { +public struct DeviceConnectionStatus: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -81,7 +81,7 @@ public struct DeviceConnectionStatus { /// /// WiFi connection status -public struct WifiConnectionStatus { +public struct WifiConnectionStatus: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -114,7 +114,7 @@ public struct WifiConnectionStatus { /// /// Ethernet connection status -public struct EthernetConnectionStatus { +public struct EthernetConnectionStatus: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -139,7 +139,7 @@ public struct EthernetConnectionStatus { /// /// Ethernet or WiFi connection status -public struct NetworkConnectionStatus { +public struct NetworkConnectionStatus: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -167,7 +167,7 @@ public struct NetworkConnectionStatus { /// /// Bluetooth connection status -public struct BluetoothConnectionStatus { +public struct BluetoothConnectionStatus: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -191,7 +191,7 @@ public struct BluetoothConnectionStatus { /// /// Serial connection status -public struct SerialConnectionStatus { +public struct SerialConnectionStatus: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -209,15 +209,6 @@ public struct SerialConnectionStatus { public init() {} } -#if swift(>=5.5) && canImport(_Concurrency) -extension DeviceConnectionStatus: @unchecked Sendable {} -extension WifiConnectionStatus: @unchecked Sendable {} -extension EthernetConnectionStatus: @unchecked Sendable {} -extension NetworkConnectionStatus: @unchecked Sendable {} -extension BluetoothConnectionStatus: @unchecked Sendable {} -extension SerialConnectionStatus: @unchecked Sendable {} -#endif // swift(>=5.5) && canImport(_Concurrency) - // MARK: - Code below here is support for the SwiftProtobuf runtime. fileprivate let _protobuf_package = "meshtastic" diff --git a/MeshtasticProtobufs/Sources/meshtastic/device_ui.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/device_ui.pb.swift new file mode 100644 index 00000000..eaf3951c --- /dev/null +++ b/MeshtasticProtobufs/Sources/meshtastic/device_ui.pb.swift @@ -0,0 +1,740 @@ +// DO NOT EDIT. +// swift-format-ignore-file +// swiftlint:disable all +// +// Generated by the Swift generator plugin for the protocol buffer compiler. +// Source: meshtastic/device_ui.proto +// +// 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 +// was generated by a version of the `protoc` Swift plug-in that is +// incompatible with the version of SwiftProtobuf to which you are linking. +// Please ensure that you are building against the same version of the API +// that was used to generate this file. +fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { + struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {} + typealias Version = _2 +} + +public enum Theme: SwiftProtobuf.Enum, Swift.CaseIterable { + public typealias RawValue = Int + + /// + /// Dark + case dark // = 0 + + /// + /// Light + case light // = 1 + + /// + /// Red + case red // = 2 + case UNRECOGNIZED(Int) + + public init() { + self = .dark + } + + public init?(rawValue: Int) { + switch rawValue { + case 0: self = .dark + case 1: self = .light + case 2: self = .red + default: self = .UNRECOGNIZED(rawValue) + } + } + + public var rawValue: Int { + switch self { + case .dark: return 0 + case .light: return 1 + case .red: return 2 + case .UNRECOGNIZED(let i): return i + } + } + + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [Theme] = [ + .dark, + .light, + .red, + ] + +} + +/// +/// Localization +public enum Language: SwiftProtobuf.Enum, Swift.CaseIterable { + public typealias RawValue = Int + + /// + /// English + case english // = 0 + + /// + /// French + case french // = 1 + + /// + /// German + case german // = 2 + + /// + /// Italian + case italian // = 3 + + /// + /// Portuguese + case portuguese // = 4 + + /// + /// Spanish + case spanish // = 5 + + /// + /// Swedish + case swedish // = 6 + + /// + /// Finnish + case finnish // = 7 + + /// + /// Polish + case polish // = 8 + + /// + /// Turkish + case turkish // = 9 + + /// + /// Serbian + case serbian // = 10 + + /// + /// Russian + case russian // = 11 + + /// + /// Dutch + case dutch // = 12 + + /// + /// Greek + case greek // = 13 + + /// + /// Norwegian + case norwegian // = 14 + + /// + /// Slovenian + case slovenian // = 15 + + /// + /// Simplified Chinese (experimental) + case simplifiedChinese // = 30 + + /// + /// Traditional Chinese (experimental) + case traditionalChinese // = 31 + case UNRECOGNIZED(Int) + + public init() { + self = .english + } + + public init?(rawValue: Int) { + switch rawValue { + case 0: self = .english + case 1: self = .french + case 2: self = .german + case 3: self = .italian + case 4: self = .portuguese + case 5: self = .spanish + case 6: self = .swedish + case 7: self = .finnish + case 8: self = .polish + case 9: self = .turkish + case 10: self = .serbian + case 11: self = .russian + case 12: self = .dutch + case 13: self = .greek + case 14: self = .norwegian + case 15: self = .slovenian + case 30: self = .simplifiedChinese + case 31: self = .traditionalChinese + default: self = .UNRECOGNIZED(rawValue) + } + } + + public var rawValue: Int { + switch self { + case .english: return 0 + case .french: return 1 + case .german: return 2 + case .italian: return 3 + case .portuguese: return 4 + case .spanish: return 5 + case .swedish: return 6 + case .finnish: return 7 + case .polish: return 8 + case .turkish: return 9 + case .serbian: return 10 + case .russian: return 11 + case .dutch: return 12 + case .greek: return 13 + case .norwegian: return 14 + case .slovenian: return 15 + case .simplifiedChinese: return 30 + case .traditionalChinese: return 31 + case .UNRECOGNIZED(let i): return i + } + } + + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [Language] = [ + .english, + .french, + .german, + .italian, + .portuguese, + .spanish, + .swedish, + .finnish, + .polish, + .turkish, + .serbian, + .russian, + .dutch, + .greek, + .norwegian, + .slovenian, + .simplifiedChinese, + .traditionalChinese, + ] + +} + +public struct DeviceUIConfig: @unchecked Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + /// + /// A version integer used to invalidate saved files when we make incompatible changes. + public var version: UInt32 { + get {return _storage._version} + set {_uniqueStorage()._version = newValue} + } + + /// + /// TFT display brightness 1..255 + public var screenBrightness: UInt32 { + get {return _storage._screenBrightness} + set {_uniqueStorage()._screenBrightness = newValue} + } + + /// + /// Screen timeout 0..900 + public var screenTimeout: UInt32 { + get {return _storage._screenTimeout} + set {_uniqueStorage()._screenTimeout = newValue} + } + + /// + /// Screen/Settings lock enabled + public var screenLock: Bool { + get {return _storage._screenLock} + set {_uniqueStorage()._screenLock = newValue} + } + + public var settingsLock: Bool { + get {return _storage._settingsLock} + set {_uniqueStorage()._settingsLock = newValue} + } + + public var pinCode: UInt32 { + get {return _storage._pinCode} + set {_uniqueStorage()._pinCode = newValue} + } + + /// + /// Color theme + public var theme: Theme { + get {return _storage._theme} + set {_uniqueStorage()._theme = newValue} + } + + /// + /// Audible message, banner and ring tone + public var alertEnabled: Bool { + get {return _storage._alertEnabled} + set {_uniqueStorage()._alertEnabled = newValue} + } + + public var bannerEnabled: Bool { + get {return _storage._bannerEnabled} + set {_uniqueStorage()._bannerEnabled = newValue} + } + + public var ringToneID: UInt32 { + get {return _storage._ringToneID} + set {_uniqueStorage()._ringToneID = newValue} + } + + /// + /// Localization + public var language: Language { + get {return _storage._language} + set {_uniqueStorage()._language = newValue} + } + + /// + /// Node list filter + public var nodeFilter: NodeFilter { + get {return _storage._nodeFilter ?? NodeFilter()} + set {_uniqueStorage()._nodeFilter = newValue} + } + /// Returns true if `nodeFilter` has been explicitly set. + public var hasNodeFilter: Bool {return _storage._nodeFilter != nil} + /// Clears the value of `nodeFilter`. Subsequent reads from it will return its default value. + public mutating func clearNodeFilter() {_uniqueStorage()._nodeFilter = nil} + + /// + /// Node list highlightening + public var nodeHighlight: NodeHighlight { + get {return _storage._nodeHighlight ?? NodeHighlight()} + set {_uniqueStorage()._nodeHighlight = newValue} + } + /// Returns true if `nodeHighlight` has been explicitly set. + public var hasNodeHighlight: Bool {return _storage._nodeHighlight != nil} + /// Clears the value of `nodeHighlight`. Subsequent reads from it will return its default value. + public mutating func clearNodeHighlight() {_uniqueStorage()._nodeHighlight = nil} + + /// + /// 8 integers for screen calibration data + public var calibrationData: Data { + get {return _storage._calibrationData} + set {_uniqueStorage()._calibrationData = newValue} + } + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _storage = _StorageClass.defaultInstance +} + +public struct NodeFilter: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + /// + /// Filter unknown nodes + public var unknownSwitch: Bool = false + + /// + /// Filter offline nodes + public var offlineSwitch: Bool = false + + /// + /// Filter nodes w/o public key + public var publicKeySwitch: Bool = false + + /// + /// Filter based on hops away + public var hopsAway: Int32 = 0 + + /// + /// Filter nodes w/o position + public var positionSwitch: Bool = false + + /// + /// Filter nodes by matching name string + public var nodeName: String = String() + + /// + /// Filter based on channel + public var channel: Int32 = 0 + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} +} + +public struct NodeHighlight: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + /// + /// Hightlight nodes w/ active chat + public var chatSwitch: Bool = false + + /// + /// Highlight nodes w/ position + public var positionSwitch: Bool = false + + /// + /// Highlight nodes w/ telemetry data + public var telemetrySwitch: Bool = false + + /// + /// Highlight nodes w/ iaq data + public var iaqSwitch: Bool = false + + /// + /// Highlight nodes by matching name string + public var nodeName: String = String() + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} +} + +// MARK: - Code below here is support for the SwiftProtobuf runtime. + +fileprivate let _protobuf_package = "meshtastic" + +extension Theme: SwiftProtobuf._ProtoNameProviding { + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "DARK"), + 1: .same(proto: "LIGHT"), + 2: .same(proto: "RED"), + ] +} + +extension Language: SwiftProtobuf._ProtoNameProviding { + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "ENGLISH"), + 1: .same(proto: "FRENCH"), + 2: .same(proto: "GERMAN"), + 3: .same(proto: "ITALIAN"), + 4: .same(proto: "PORTUGUESE"), + 5: .same(proto: "SPANISH"), + 6: .same(proto: "SWEDISH"), + 7: .same(proto: "FINNISH"), + 8: .same(proto: "POLISH"), + 9: .same(proto: "TURKISH"), + 10: .same(proto: "SERBIAN"), + 11: .same(proto: "RUSSIAN"), + 12: .same(proto: "DUTCH"), + 13: .same(proto: "GREEK"), + 14: .same(proto: "NORWEGIAN"), + 15: .same(proto: "SLOVENIAN"), + 30: .same(proto: "SIMPLIFIED_CHINESE"), + 31: .same(proto: "TRADITIONAL_CHINESE"), + ] +} + +extension DeviceUIConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".DeviceUIConfig" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "version"), + 2: .standard(proto: "screen_brightness"), + 3: .standard(proto: "screen_timeout"), + 4: .standard(proto: "screen_lock"), + 5: .standard(proto: "settings_lock"), + 6: .standard(proto: "pin_code"), + 7: .same(proto: "theme"), + 8: .standard(proto: "alert_enabled"), + 9: .standard(proto: "banner_enabled"), + 10: .standard(proto: "ring_tone_id"), + 11: .same(proto: "language"), + 12: .standard(proto: "node_filter"), + 13: .standard(proto: "node_highlight"), + 14: .standard(proto: "calibration_data"), + ] + + fileprivate class _StorageClass { + var _version: UInt32 = 0 + var _screenBrightness: UInt32 = 0 + var _screenTimeout: UInt32 = 0 + var _screenLock: Bool = false + var _settingsLock: Bool = false + var _pinCode: UInt32 = 0 + var _theme: Theme = .dark + var _alertEnabled: Bool = false + var _bannerEnabled: Bool = false + var _ringToneID: UInt32 = 0 + var _language: Language = .english + var _nodeFilter: NodeFilter? = nil + var _nodeHighlight: NodeHighlight? = nil + var _calibrationData: Data = Data() + + #if swift(>=5.10) + // This property is used as the initial default value for new instances of the type. + // The type itself is protecting the reference to its storage via CoW semantics. + // This will force a copy to be made of this reference when the first mutation occurs; + // hence, it is safe to mark this as `nonisolated(unsafe)`. + static nonisolated(unsafe) let defaultInstance = _StorageClass() + #else + static let defaultInstance = _StorageClass() + #endif + + private init() {} + + init(copying source: _StorageClass) { + _version = source._version + _screenBrightness = source._screenBrightness + _screenTimeout = source._screenTimeout + _screenLock = source._screenLock + _settingsLock = source._settingsLock + _pinCode = source._pinCode + _theme = source._theme + _alertEnabled = source._alertEnabled + _bannerEnabled = source._bannerEnabled + _ringToneID = source._ringToneID + _language = source._language + _nodeFilter = source._nodeFilter + _nodeHighlight = source._nodeHighlight + _calibrationData = source._calibrationData + } + } + + fileprivate mutating func _uniqueStorage() -> _StorageClass { + if !isKnownUniquelyReferenced(&_storage) { + _storage = _StorageClass(copying: _storage) + } + return _storage + } + + public mutating func decodeMessage(decoder: inout D) throws { + _ = _uniqueStorage() + try withExtendedLifetime(_storage) { (_storage: _StorageClass) in + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularUInt32Field(value: &_storage._version) }() + case 2: try { try decoder.decodeSingularUInt32Field(value: &_storage._screenBrightness) }() + case 3: try { try decoder.decodeSingularUInt32Field(value: &_storage._screenTimeout) }() + case 4: try { try decoder.decodeSingularBoolField(value: &_storage._screenLock) }() + case 5: try { try decoder.decodeSingularBoolField(value: &_storage._settingsLock) }() + case 6: try { try decoder.decodeSingularUInt32Field(value: &_storage._pinCode) }() + case 7: try { try decoder.decodeSingularEnumField(value: &_storage._theme) }() + case 8: try { try decoder.decodeSingularBoolField(value: &_storage._alertEnabled) }() + case 9: try { try decoder.decodeSingularBoolField(value: &_storage._bannerEnabled) }() + case 10: try { try decoder.decodeSingularUInt32Field(value: &_storage._ringToneID) }() + case 11: try { try decoder.decodeSingularEnumField(value: &_storage._language) }() + case 12: try { try decoder.decodeSingularMessageField(value: &_storage._nodeFilter) }() + case 13: try { try decoder.decodeSingularMessageField(value: &_storage._nodeHighlight) }() + case 14: try { try decoder.decodeSingularBytesField(value: &_storage._calibrationData) }() + default: break + } + } + } + } + + public func traverse(visitor: inout V) throws { + try withExtendedLifetime(_storage) { (_storage: _StorageClass) in + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + if _storage._version != 0 { + try visitor.visitSingularUInt32Field(value: _storage._version, fieldNumber: 1) + } + if _storage._screenBrightness != 0 { + try visitor.visitSingularUInt32Field(value: _storage._screenBrightness, fieldNumber: 2) + } + if _storage._screenTimeout != 0 { + try visitor.visitSingularUInt32Field(value: _storage._screenTimeout, fieldNumber: 3) + } + if _storage._screenLock != false { + try visitor.visitSingularBoolField(value: _storage._screenLock, fieldNumber: 4) + } + if _storage._settingsLock != false { + try visitor.visitSingularBoolField(value: _storage._settingsLock, fieldNumber: 5) + } + if _storage._pinCode != 0 { + try visitor.visitSingularUInt32Field(value: _storage._pinCode, fieldNumber: 6) + } + if _storage._theme != .dark { + try visitor.visitSingularEnumField(value: _storage._theme, fieldNumber: 7) + } + if _storage._alertEnabled != false { + try visitor.visitSingularBoolField(value: _storage._alertEnabled, fieldNumber: 8) + } + if _storage._bannerEnabled != false { + try visitor.visitSingularBoolField(value: _storage._bannerEnabled, fieldNumber: 9) + } + if _storage._ringToneID != 0 { + try visitor.visitSingularUInt32Field(value: _storage._ringToneID, fieldNumber: 10) + } + if _storage._language != .english { + try visitor.visitSingularEnumField(value: _storage._language, fieldNumber: 11) + } + try { if let v = _storage._nodeFilter { + try visitor.visitSingularMessageField(value: v, fieldNumber: 12) + } }() + try { if let v = _storage._nodeHighlight { + try visitor.visitSingularMessageField(value: v, fieldNumber: 13) + } }() + if !_storage._calibrationData.isEmpty { + try visitor.visitSingularBytesField(value: _storage._calibrationData, fieldNumber: 14) + } + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: DeviceUIConfig, rhs: DeviceUIConfig) -> Bool { + if lhs._storage !== rhs._storage { + let storagesAreEqual: Bool = withExtendedLifetime((lhs._storage, rhs._storage)) { (_args: (_StorageClass, _StorageClass)) in + let _storage = _args.0 + let rhs_storage = _args.1 + if _storage._version != rhs_storage._version {return false} + if _storage._screenBrightness != rhs_storage._screenBrightness {return false} + if _storage._screenTimeout != rhs_storage._screenTimeout {return false} + if _storage._screenLock != rhs_storage._screenLock {return false} + if _storage._settingsLock != rhs_storage._settingsLock {return false} + if _storage._pinCode != rhs_storage._pinCode {return false} + if _storage._theme != rhs_storage._theme {return false} + if _storage._alertEnabled != rhs_storage._alertEnabled {return false} + if _storage._bannerEnabled != rhs_storage._bannerEnabled {return false} + if _storage._ringToneID != rhs_storage._ringToneID {return false} + if _storage._language != rhs_storage._language {return false} + if _storage._nodeFilter != rhs_storage._nodeFilter {return false} + if _storage._nodeHighlight != rhs_storage._nodeHighlight {return false} + if _storage._calibrationData != rhs_storage._calibrationData {return false} + return true + } + if !storagesAreEqual {return false} + } + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension NodeFilter: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".NodeFilter" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .standard(proto: "unknown_switch"), + 2: .standard(proto: "offline_switch"), + 3: .standard(proto: "public_key_switch"), + 4: .standard(proto: "hops_away"), + 5: .standard(proto: "position_switch"), + 6: .standard(proto: "node_name"), + 7: .same(proto: "channel"), + ] + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularBoolField(value: &self.unknownSwitch) }() + case 2: try { try decoder.decodeSingularBoolField(value: &self.offlineSwitch) }() + case 3: try { try decoder.decodeSingularBoolField(value: &self.publicKeySwitch) }() + case 4: try { try decoder.decodeSingularInt32Field(value: &self.hopsAway) }() + case 5: try { try decoder.decodeSingularBoolField(value: &self.positionSwitch) }() + case 6: try { try decoder.decodeSingularStringField(value: &self.nodeName) }() + case 7: try { try decoder.decodeSingularInt32Field(value: &self.channel) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if self.unknownSwitch != false { + try visitor.visitSingularBoolField(value: self.unknownSwitch, fieldNumber: 1) + } + if self.offlineSwitch != false { + try visitor.visitSingularBoolField(value: self.offlineSwitch, fieldNumber: 2) + } + if self.publicKeySwitch != false { + try visitor.visitSingularBoolField(value: self.publicKeySwitch, fieldNumber: 3) + } + if self.hopsAway != 0 { + try visitor.visitSingularInt32Field(value: self.hopsAway, fieldNumber: 4) + } + if self.positionSwitch != false { + try visitor.visitSingularBoolField(value: self.positionSwitch, fieldNumber: 5) + } + if !self.nodeName.isEmpty { + try visitor.visitSingularStringField(value: self.nodeName, fieldNumber: 6) + } + if self.channel != 0 { + try visitor.visitSingularInt32Field(value: self.channel, fieldNumber: 7) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: NodeFilter, rhs: NodeFilter) -> Bool { + if lhs.unknownSwitch != rhs.unknownSwitch {return false} + if lhs.offlineSwitch != rhs.offlineSwitch {return false} + if lhs.publicKeySwitch != rhs.publicKeySwitch {return false} + if lhs.hopsAway != rhs.hopsAway {return false} + if lhs.positionSwitch != rhs.positionSwitch {return false} + if lhs.nodeName != rhs.nodeName {return false} + if lhs.channel != rhs.channel {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension NodeHighlight: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".NodeHighlight" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .standard(proto: "chat_switch"), + 2: .standard(proto: "position_switch"), + 3: .standard(proto: "telemetry_switch"), + 4: .standard(proto: "iaq_switch"), + 5: .standard(proto: "node_name"), + ] + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularBoolField(value: &self.chatSwitch) }() + case 2: try { try decoder.decodeSingularBoolField(value: &self.positionSwitch) }() + case 3: try { try decoder.decodeSingularBoolField(value: &self.telemetrySwitch) }() + case 4: try { try decoder.decodeSingularBoolField(value: &self.iaqSwitch) }() + case 5: try { try decoder.decodeSingularStringField(value: &self.nodeName) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if self.chatSwitch != false { + try visitor.visitSingularBoolField(value: self.chatSwitch, fieldNumber: 1) + } + if self.positionSwitch != false { + try visitor.visitSingularBoolField(value: self.positionSwitch, fieldNumber: 2) + } + if self.telemetrySwitch != false { + try visitor.visitSingularBoolField(value: self.telemetrySwitch, fieldNumber: 3) + } + if self.iaqSwitch != false { + try visitor.visitSingularBoolField(value: self.iaqSwitch, fieldNumber: 4) + } + if !self.nodeName.isEmpty { + try visitor.visitSingularStringField(value: self.nodeName, fieldNumber: 5) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: NodeHighlight, rhs: NodeHighlight) -> Bool { + if lhs.chatSwitch != rhs.chatSwitch {return false} + if lhs.positionSwitch != rhs.positionSwitch {return false} + if lhs.telemetrySwitch != rhs.telemetrySwitch {return false} + if lhs.iaqSwitch != rhs.iaqSwitch {return false} + if lhs.nodeName != rhs.nodeName {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} diff --git a/MeshtasticProtobufs/Sources/meshtastic/deviceonly.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/deviceonly.pb.swift index 3dd965f2..a8f57eaf 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/deviceonly.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/deviceonly.pb.swift @@ -1,5 +1,6 @@ // DO NOT EDIT. // swift-format-ignore-file +// swiftlint:disable all // // Generated by the Swift generator plugin for the protocol buffer compiler. // Source: meshtastic/deviceonly.proto @@ -20,64 +21,9 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP typealias Version = _2 } -/// -/// Font sizes for the device screen -public enum ScreenFonts: SwiftProtobuf.Enum { - public typealias RawValue = Int - - /// - /// TODO: REPLACE - case fontSmall // = 0 - - /// - /// TODO: REPLACE - case fontMedium // = 1 - - /// - /// TODO: REPLACE - case fontLarge // = 2 - case UNRECOGNIZED(Int) - - public init() { - self = .fontSmall - } - - public init?(rawValue: Int) { - switch rawValue { - case 0: self = .fontSmall - case 1: self = .fontMedium - case 2: self = .fontLarge - default: self = .UNRECOGNIZED(rawValue) - } - } - - public var rawValue: Int { - switch self { - case .fontSmall: return 0 - case .fontMedium: return 1 - case .fontLarge: return 2 - case .UNRECOGNIZED(let i): return i - } - } - -} - -#if swift(>=4.2) - -extension ScreenFonts: CaseIterable { - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [ScreenFonts] = [ - .fontSmall, - .fontMedium, - .fontLarge, - ] -} - -#endif // swift(>=4.2) - /// /// Position with static location information only for NodeDBLite -public struct PositionLite { +public struct PositionLite: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -112,13 +58,15 @@ public struct PositionLite { public init() {} } -public struct UserLite { +public struct UserLite: @unchecked Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. /// /// This is the addr of the radio. + /// + /// NOTE: This field was marked as deprecated in the .proto file. public var macaddr: Data = Data() /// @@ -157,7 +105,7 @@ public struct UserLite { public init() {} } -public struct NodeInfoLite { +public struct NodeInfoLite: @unchecked Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -233,7 +181,7 @@ public struct NodeInfoLite { } /// - /// Number of hops away from us this node is (0 if adjacent) + /// Number of hops away from us this node is (0 if direct neighbor) public var hopsAway: UInt32 { get {return _storage._hopsAway ?? 0} set {_uniqueStorage()._hopsAway = newValue} @@ -251,6 +199,21 @@ public struct NodeInfoLite { set {_uniqueStorage()._isFavorite = newValue} } + /// + /// True if node is in our ignored list + /// Persists between NodeDB internal clean ups + public var isIgnored: Bool { + get {return _storage._isIgnored} + set {_uniqueStorage()._isIgnored = newValue} + } + + /// + /// Last byte of the node number of the node that should be used as the next hop to reach this node. + public var nextHop: UInt32 { + get {return _storage._nextHop} + set {_uniqueStorage()._nextHop = newValue} + } + public var unknownFields = SwiftProtobuf.UnknownStorage() public init() {} @@ -264,7 +227,7 @@ public struct NodeInfoLite { /// FIXME, since we write this each time we enter deep sleep (and have infinite /// flash) it would be better to use some sort of append only data structure for /// the receive queue and use the preferences store for the other stuff -public struct DeviceState { +public struct DeviceState: @unchecked Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -324,6 +287,8 @@ public struct DeviceState { /// Used only during development. /// Indicates developer is testing and changes should never be saved to flash. /// Deprecated in 2.3.1 + /// + /// NOTE: This field was marked as deprecated in the .proto file. public var noSave: Bool { get {return _storage._noSave} set {_uniqueStorage()._noSave = newValue} @@ -372,7 +337,7 @@ public struct DeviceState { /// /// The on-disk saved channels -public struct ChannelFile { +public struct ChannelFile: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -392,90 +357,10 @@ public struct ChannelFile { public init() {} } -/// -/// This can be used for customizing the firmware distribution. If populated, -/// show a secondary bootup screen with custom logo and text for 2.5 seconds. -public struct OEMStore { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// - /// The Logo width in Px - public var oemIconWidth: UInt32 = 0 - - /// - /// The Logo height in Px - public var oemIconHeight: UInt32 = 0 - - /// - /// The Logo in XBM bytechar format - public var oemIconBits: Data = Data() - - /// - /// Use this font for the OEM text. - public var oemFont: ScreenFonts = .fontSmall - - /// - /// Use this font for the OEM text. - public var oemText: String = String() - - /// - /// The default device encryption key, 16 or 32 byte - public var oemAesKey: Data = Data() - - /// - /// A Preset LocalConfig to apply during factory reset - public var oemLocalConfig: LocalConfig { - get {return _oemLocalConfig ?? LocalConfig()} - set {_oemLocalConfig = newValue} - } - /// Returns true if `oemLocalConfig` has been explicitly set. - public var hasOemLocalConfig: Bool {return self._oemLocalConfig != nil} - /// Clears the value of `oemLocalConfig`. Subsequent reads from it will return its default value. - public mutating func clearOemLocalConfig() {self._oemLocalConfig = nil} - - /// - /// A Preset LocalModuleConfig to apply during factory reset - public var oemLocalModuleConfig: LocalModuleConfig { - get {return _oemLocalModuleConfig ?? LocalModuleConfig()} - set {_oemLocalModuleConfig = newValue} - } - /// Returns true if `oemLocalModuleConfig` has been explicitly set. - public var hasOemLocalModuleConfig: Bool {return self._oemLocalModuleConfig != nil} - /// Clears the value of `oemLocalModuleConfig`. Subsequent reads from it will return its default value. - public mutating func clearOemLocalModuleConfig() {self._oemLocalModuleConfig = nil} - - public var unknownFields = SwiftProtobuf.UnknownStorage() - - public init() {} - - fileprivate var _oemLocalConfig: LocalConfig? = nil - fileprivate var _oemLocalModuleConfig: LocalModuleConfig? = nil -} - -#if swift(>=5.5) && canImport(_Concurrency) -extension ScreenFonts: @unchecked Sendable {} -extension PositionLite: @unchecked Sendable {} -extension UserLite: @unchecked Sendable {} -extension NodeInfoLite: @unchecked Sendable {} -extension DeviceState: @unchecked Sendable {} -extension ChannelFile: @unchecked Sendable {} -extension OEMStore: @unchecked Sendable {} -#endif // swift(>=5.5) && canImport(_Concurrency) - // MARK: - Code below here is support for the SwiftProtobuf runtime. fileprivate let _protobuf_package = "meshtastic" -extension ScreenFonts: SwiftProtobuf._ProtoNameProviding { - public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 0: .same(proto: "FONT_SMALL"), - 1: .same(proto: "FONT_MEDIUM"), - 2: .same(proto: "FONT_LARGE"), - ] -} - extension PositionLite: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".PositionLite" public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ @@ -613,6 +498,8 @@ extension NodeInfoLite: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat 8: .standard(proto: "via_mqtt"), 9: .standard(proto: "hops_away"), 10: .standard(proto: "is_favorite"), + 11: .standard(proto: "is_ignored"), + 12: .standard(proto: "next_hop"), ] fileprivate class _StorageClass { @@ -626,6 +513,8 @@ extension NodeInfoLite: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat var _viaMqtt: Bool = false var _hopsAway: UInt32? = nil var _isFavorite: Bool = false + var _isIgnored: Bool = false + var _nextHop: UInt32 = 0 #if swift(>=5.10) // This property is used as the initial default value for new instances of the type. @@ -650,6 +539,8 @@ extension NodeInfoLite: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat _viaMqtt = source._viaMqtt _hopsAway = source._hopsAway _isFavorite = source._isFavorite + _isIgnored = source._isIgnored + _nextHop = source._nextHop } } @@ -678,6 +569,8 @@ extension NodeInfoLite: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat case 8: try { try decoder.decodeSingularBoolField(value: &_storage._viaMqtt) }() case 9: try { try decoder.decodeSingularUInt32Field(value: &_storage._hopsAway) }() case 10: try { try decoder.decodeSingularBoolField(value: &_storage._isFavorite) }() + case 11: try { try decoder.decodeSingularBoolField(value: &_storage._isIgnored) }() + case 12: try { try decoder.decodeSingularUInt32Field(value: &_storage._nextHop) }() default: break } } @@ -699,7 +592,7 @@ extension NodeInfoLite: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat try { if let v = _storage._position { try visitor.visitSingularMessageField(value: v, fieldNumber: 3) } }() - if _storage._snr != 0 { + if _storage._snr.bitPattern != 0 { try visitor.visitSingularFloatField(value: _storage._snr, fieldNumber: 4) } if _storage._lastHeard != 0 { @@ -720,6 +613,12 @@ extension NodeInfoLite: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat if _storage._isFavorite != false { try visitor.visitSingularBoolField(value: _storage._isFavorite, fieldNumber: 10) } + if _storage._isIgnored != false { + try visitor.visitSingularBoolField(value: _storage._isIgnored, fieldNumber: 11) + } + if _storage._nextHop != 0 { + try visitor.visitSingularUInt32Field(value: _storage._nextHop, fieldNumber: 12) + } } try unknownFields.traverse(visitor: &visitor) } @@ -739,6 +638,8 @@ extension NodeInfoLite: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat if _storage._viaMqtt != rhs_storage._viaMqtt {return false} if _storage._hopsAway != rhs_storage._hopsAway {return false} if _storage._isFavorite != rhs_storage._isFavorite {return false} + if _storage._isIgnored != rhs_storage._isIgnored {return false} + if _storage._nextHop != rhs_storage._nextHop {return false} return true } if !storagesAreEqual {return false} @@ -933,81 +834,3 @@ extension ChannelFile: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementati return true } } - -extension OEMStore: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - public static let protoMessageName: String = _protobuf_package + ".OEMStore" - public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .standard(proto: "oem_icon_width"), - 2: .standard(proto: "oem_icon_height"), - 3: .standard(proto: "oem_icon_bits"), - 4: .standard(proto: "oem_font"), - 5: .standard(proto: "oem_text"), - 6: .standard(proto: "oem_aes_key"), - 7: .standard(proto: "oem_local_config"), - 8: .standard(proto: "oem_local_module_config"), - ] - - public mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularUInt32Field(value: &self.oemIconWidth) }() - case 2: try { try decoder.decodeSingularUInt32Field(value: &self.oemIconHeight) }() - case 3: try { try decoder.decodeSingularBytesField(value: &self.oemIconBits) }() - case 4: try { try decoder.decodeSingularEnumField(value: &self.oemFont) }() - case 5: try { try decoder.decodeSingularStringField(value: &self.oemText) }() - case 6: try { try decoder.decodeSingularBytesField(value: &self.oemAesKey) }() - case 7: try { try decoder.decodeSingularMessageField(value: &self._oemLocalConfig) }() - case 8: try { try decoder.decodeSingularMessageField(value: &self._oemLocalModuleConfig) }() - default: break - } - } - } - - public func traverse(visitor: inout V) throws { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every if/case branch local when no optimizations - // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and - // https://github.com/apple/swift-protobuf/issues/1182 - if self.oemIconWidth != 0 { - try visitor.visitSingularUInt32Field(value: self.oemIconWidth, fieldNumber: 1) - } - if self.oemIconHeight != 0 { - try visitor.visitSingularUInt32Field(value: self.oemIconHeight, fieldNumber: 2) - } - if !self.oemIconBits.isEmpty { - try visitor.visitSingularBytesField(value: self.oemIconBits, fieldNumber: 3) - } - if self.oemFont != .fontSmall { - try visitor.visitSingularEnumField(value: self.oemFont, fieldNumber: 4) - } - if !self.oemText.isEmpty { - try visitor.visitSingularStringField(value: self.oemText, fieldNumber: 5) - } - if !self.oemAesKey.isEmpty { - try visitor.visitSingularBytesField(value: self.oemAesKey, fieldNumber: 6) - } - try { if let v = self._oemLocalConfig { - try visitor.visitSingularMessageField(value: v, fieldNumber: 7) - } }() - try { if let v = self._oemLocalModuleConfig { - try visitor.visitSingularMessageField(value: v, fieldNumber: 8) - } }() - try unknownFields.traverse(visitor: &visitor) - } - - public static func ==(lhs: OEMStore, rhs: OEMStore) -> Bool { - if lhs.oemIconWidth != rhs.oemIconWidth {return false} - if lhs.oemIconHeight != rhs.oemIconHeight {return false} - if lhs.oemIconBits != rhs.oemIconBits {return false} - if lhs.oemFont != rhs.oemFont {return false} - if lhs.oemText != rhs.oemText {return false} - if lhs.oemAesKey != rhs.oemAesKey {return false} - if lhs._oemLocalConfig != rhs._oemLocalConfig {return false} - if lhs._oemLocalModuleConfig != rhs._oemLocalModuleConfig {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} diff --git a/MeshtasticProtobufs/Sources/meshtastic/localonly.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/localonly.pb.swift index 0af27466..c3356286 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/localonly.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/localonly.pb.swift @@ -1,5 +1,6 @@ // DO NOT EDIT. // swift-format-ignore-file +// swiftlint:disable all // // Generated by the Swift generator plugin for the protocol buffer compiler. // Source: meshtastic/localonly.proto @@ -7,7 +8,6 @@ // For information on using the generated types, please see the documentation: // https://github.com/apple/swift-protobuf/ -import Foundation import SwiftProtobuf // If the compiler emits an error on this type, it is because this file @@ -20,7 +20,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP typealias Version = _2 } -public struct LocalConfig { +public struct LocalConfig: @unchecked Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -129,7 +129,7 @@ public struct LocalConfig { fileprivate var _storage = _StorageClass.defaultInstance } -public struct LocalModuleConfig { +public struct LocalModuleConfig: @unchecked Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -293,11 +293,6 @@ public struct LocalModuleConfig { fileprivate var _storage = _StorageClass.defaultInstance } -#if swift(>=5.5) && canImport(_Concurrency) -extension LocalConfig: @unchecked Sendable {} -extension LocalModuleConfig: @unchecked Sendable {} -#endif // swift(>=5.5) && canImport(_Concurrency) - // MARK: - Code below here is support for the SwiftProtobuf runtime. fileprivate let _protobuf_package = "meshtastic" diff --git a/MeshtasticProtobufs/Sources/meshtastic/mesh.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/mesh.pb.swift index f604d4a7..cf3cb4ee 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/mesh.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/mesh.pb.swift @@ -1,5 +1,6 @@ // DO NOT EDIT. // swift-format-ignore-file +// swiftlint:disable all // // Generated by the Swift generator plugin for the protocol buffer compiler. // Source: meshtastic/mesh.proto @@ -25,7 +26,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP /// bin/build-all.sh script. /// Because they will be used to find firmware filenames in the android app for OTA updates. /// To match the old style filenames, _ is converted to -, p is converted to . -public enum HardwareModel: SwiftProtobuf.Enum { +public enum HardwareModel: SwiftProtobuf.Enum, Swift.CaseIterable { public typealias RawValue = Int /// @@ -380,6 +381,23 @@ public enum HardwareModel: SwiftProtobuf.Enum { /// Lilygo TLora-C6 with the new ESP32-C6 MCU case tloraC6 // = 83 + /// + /// WisMesh Tap + /// RAK-4631 w/ TFT in injection modled case + case wismeshTap // = 84 + + /// + /// Similar to PORTDUINO but used by Routastic devices, this is not any + /// particular device and does not run Meshtastic's code but supports + /// the same frame format. + /// Runs on linux, see https://github.com/Jorropo/routastic + case routastic // = 85 + + /// + /// Mesh-Tab, esp32 based + /// https://github.com/valzzu/Mesh-Tab + case meshTab // = 86 + /// /// ------------------------------------------------------------------------------------------------------------------------------------------ /// Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits. @@ -477,6 +495,9 @@ public enum HardwareModel: SwiftProtobuf.Enum { case 81: self = .seeedXiaoS3 case 82: self = .ms24Sf1 case 83: self = .tloraC6 + case 84: self = .wismeshTap + case 85: self = .routastic + case 86: self = .meshTab case 255: self = .privateHw default: self = .UNRECOGNIZED(rawValue) } @@ -568,16 +589,14 @@ public enum HardwareModel: SwiftProtobuf.Enum { case .seeedXiaoS3: return 81 case .ms24Sf1: return 82 case .tloraC6: return 83 + case .wismeshTap: return 84 + case .routastic: return 85 + case .meshTab: return 86 case .privateHw: return 255 case .UNRECOGNIZED(let i): return i } } -} - -#if swift(>=4.2) - -extension HardwareModel: CaseIterable { // The compiler won't synthesize support with the UNRECOGNIZED case. public static let allCases: [HardwareModel] = [ .unset, @@ -664,15 +683,17 @@ extension HardwareModel: CaseIterable { .seeedXiaoS3, .ms24Sf1, .tloraC6, + .wismeshTap, + .routastic, + .meshTab, .privateHw, ] -} -#endif // swift(>=4.2) +} /// /// Shared constants between device and phone -public enum Constants: SwiftProtobuf.Enum { +public enum Constants: SwiftProtobuf.Enum, Swift.CaseIterable { public typealias RawValue = Int /// @@ -684,7 +705,7 @@ public enum Constants: SwiftProtobuf.Enum { /// From mesh.options /// note: this payload length is ONLY the bytes that are sent inside of the Data protobuf (excluding protobuf overhead). The 16 byte header is /// outside of this envelope - case dataPayloadLen // = 237 + case dataPayloadLen // = 233 case UNRECOGNIZED(Int) public init() { @@ -694,7 +715,7 @@ public enum Constants: SwiftProtobuf.Enum { public init?(rawValue: Int) { switch rawValue { case 0: self = .zero - case 237: self = .dataPayloadLen + case 233: self = .dataPayloadLen default: self = .UNRECOGNIZED(rawValue) } } @@ -702,31 +723,25 @@ public enum Constants: SwiftProtobuf.Enum { public var rawValue: Int { switch self { case .zero: return 0 - case .dataPayloadLen: return 237 + case .dataPayloadLen: return 233 case .UNRECOGNIZED(let i): return i } } -} - -#if swift(>=4.2) - -extension Constants: CaseIterable { // The compiler won't synthesize support with the UNRECOGNIZED case. public static let allCases: [Constants] = [ .zero, .dataPayloadLen, ] -} -#endif // swift(>=4.2) +} /// /// Error codes for critical errors /// The device might report these fault codes on the screen. /// If you encounter a fault code, please post on the meshtastic.discourse.group /// and we'll try to help. -public enum CriticalErrorCode: SwiftProtobuf.Enum { +public enum CriticalErrorCode: SwiftProtobuf.Enum, Swift.CaseIterable { public typealias RawValue = Int /// @@ -835,11 +850,6 @@ public enum CriticalErrorCode: SwiftProtobuf.Enum { } } -} - -#if swift(>=4.2) - -extension CriticalErrorCode: CaseIterable { // The compiler won't synthesize support with the UNRECOGNIZED case. public static let allCases: [CriticalErrorCode] = [ .none, @@ -857,13 +867,140 @@ extension CriticalErrorCode: CaseIterable { .flashCorruptionRecoverable, .flashCorruptionUnrecoverable, ] + } -#endif // swift(>=4.2) +/// +/// Enum for modules excluded from a device's configuration. +/// Each value represents a ModuleConfigType that can be toggled as excluded +/// by setting its corresponding bit in the `excluded_modules` bitmask field. +public enum ExcludedModules: SwiftProtobuf.Enum, Swift.CaseIterable { + public typealias RawValue = Int + + /// + /// Default value of 0 indicates no modules are excluded. + case excludedNone // = 0 + + /// + /// MQTT module + case mqttConfig // = 1 + + /// + /// Serial module + case serialConfig // = 2 + + /// + /// External Notification module + case extnotifConfig // = 4 + + /// + /// Store and Forward module + case storeforwardConfig // = 8 + + /// + /// Range Test module + case rangetestConfig // = 16 + + /// + /// Telemetry module + case telemetryConfig // = 32 + + /// + /// Canned Message module + case cannedmsgConfig // = 64 + + /// + /// Audio module + case audioConfig // = 128 + + /// + /// Remote Hardware module + case remotehardwareConfig // = 256 + + /// + /// Neighbor Info module + case neighborinfoConfig // = 512 + + /// + /// Ambient Lighting module + case ambientlightingConfig // = 1024 + + /// + /// Detection Sensor module + case detectionsensorConfig // = 2048 + + /// + /// Paxcounter module + case paxcounterConfig // = 4096 + case UNRECOGNIZED(Int) + + public init() { + self = .excludedNone + } + + public init?(rawValue: Int) { + switch rawValue { + case 0: self = .excludedNone + case 1: self = .mqttConfig + case 2: self = .serialConfig + case 4: self = .extnotifConfig + case 8: self = .storeforwardConfig + case 16: self = .rangetestConfig + case 32: self = .telemetryConfig + case 64: self = .cannedmsgConfig + case 128: self = .audioConfig + case 256: self = .remotehardwareConfig + case 512: self = .neighborinfoConfig + case 1024: self = .ambientlightingConfig + case 2048: self = .detectionsensorConfig + case 4096: self = .paxcounterConfig + default: self = .UNRECOGNIZED(rawValue) + } + } + + public var rawValue: Int { + switch self { + case .excludedNone: return 0 + case .mqttConfig: return 1 + case .serialConfig: return 2 + case .extnotifConfig: return 4 + case .storeforwardConfig: return 8 + case .rangetestConfig: return 16 + case .telemetryConfig: return 32 + case .cannedmsgConfig: return 64 + case .audioConfig: return 128 + case .remotehardwareConfig: return 256 + case .neighborinfoConfig: return 512 + case .ambientlightingConfig: return 1024 + case .detectionsensorConfig: return 2048 + case .paxcounterConfig: return 4096 + case .UNRECOGNIZED(let i): return i + } + } + + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [ExcludedModules] = [ + .excludedNone, + .mqttConfig, + .serialConfig, + .extnotifConfig, + .storeforwardConfig, + .rangetestConfig, + .telemetryConfig, + .cannedmsgConfig, + .audioConfig, + .remotehardwareConfig, + .neighborinfoConfig, + .ambientlightingConfig, + .detectionsensorConfig, + .paxcounterConfig, + ] + +} /// -/// a gps position -public struct Position { +/// A GPS Position +public struct Position: @unchecked Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -1080,7 +1217,7 @@ public struct Position { /// /// How the location was acquired: manual, onboard GPS, external (EUD) GPS - public enum LocSource: SwiftProtobuf.Enum { + public enum LocSource: SwiftProtobuf.Enum, Swift.CaseIterable { public typealias RawValue = Int /// @@ -1124,12 +1261,20 @@ public struct Position { } } + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [Position.LocSource] = [ + .locUnset, + .locManual, + .locInternal, + .locExternal, + ] + } /// /// How the altitude was acquired: manual, GPS int/ext, etc /// Default: same as location_source if present - public enum AltSource: SwiftProtobuf.Enum { + public enum AltSource: SwiftProtobuf.Enum, Swift.CaseIterable { public typealias RawValue = Int /// @@ -1179,6 +1324,15 @@ public struct Position { } } + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [Position.AltSource] = [ + .altUnset, + .altManual, + .altInternal, + .altExternal, + .altBarometric, + ] + } public init() {} @@ -1186,31 +1340,6 @@ public struct Position { fileprivate var _storage = _StorageClass.defaultInstance } -#if swift(>=4.2) - -extension Position.LocSource: CaseIterable { - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [Position.LocSource] = [ - .locUnset, - .locManual, - .locInternal, - .locExternal, - ] -} - -extension Position.AltSource: CaseIterable { - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [Position.AltSource] = [ - .altUnset, - .altManual, - .altInternal, - .altExternal, - .altBarometric, - ] -} - -#endif // swift(>=4.2) - /// /// Broadcast when a newly powered mesh node wants to find a node num it can use /// Sent from the phone over bluetooth to set the user id for the owner of this node. @@ -1232,7 +1361,7 @@ extension Position.AltSource: CaseIterable { /// A few nodenums are reserved and will never be requested: /// 0xff - broadcast /// 0 through 3 - for future use -public struct User { +public struct User: @unchecked Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -1257,6 +1386,8 @@ public struct User { /// Deprecated in Meshtastic 2.1.x /// This is the addr of the radio. /// Not populated by the phone, but added by the esp32 when broadcasting + /// + /// NOTE: This field was marked as deprecated in the .proto file. public var macaddr: Data = Data() /// @@ -1288,7 +1419,7 @@ public struct User { /// /// A message used in a traceroute -public struct RouteDiscovery { +public struct RouteDiscovery: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -1316,7 +1447,7 @@ public struct RouteDiscovery { /// /// A Routing control Data packet handled by the routing module -public struct Routing { +public struct Routing: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -1356,7 +1487,7 @@ public struct Routing { public var unknownFields = SwiftProtobuf.UnknownStorage() - public enum OneOf_Variant: Equatable { + public enum OneOf_Variant: Equatable, Sendable { /// /// A route request going from the requester case routeRequest(RouteDiscovery) @@ -1368,34 +1499,12 @@ public struct Routing { /// in addition to ack.fail_id to provide details on the type of failure). case errorReason(Routing.Error) - #if !swift(>=4.1) - public static func ==(lhs: Routing.OneOf_Variant, rhs: Routing.OneOf_Variant) -> Bool { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch (lhs, rhs) { - case (.routeRequest, .routeRequest): return { - guard case .routeRequest(let l) = lhs, case .routeRequest(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.routeReply, .routeReply): return { - guard case .routeReply(let l) = lhs, case .routeReply(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.errorReason, .errorReason): return { - guard case .errorReason(let l) = lhs, case .errorReason(let r) = rhs else { preconditionFailure() } - return l == r - }() - default: return false - } - } - #endif } /// /// A failure in delivering a message (usually used for routing control messages, but might be provided in addition to ack.fail_id to provide /// details on the type of failure). - public enum Error: SwiftProtobuf.Enum { + public enum Error: SwiftProtobuf.Enum, Swift.CaseIterable { public typealias RawValue = Int /// @@ -1513,42 +1622,36 @@ public struct Routing { } } + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [Routing.Error] = [ + .none, + .noRoute, + .gotNak, + .timeout, + .noInterface, + .maxRetransmit, + .noChannel, + .tooLarge, + .noResponse, + .dutyCycleLimit, + .badRequest, + .notAuthorized, + .pkiFailed, + .pkiUnknownPubkey, + .adminBadSessionKey, + .adminPublicKeyUnauthorized, + ] + } public init() {} } -#if swift(>=4.2) - -extension Routing.Error: CaseIterable { - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [Routing.Error] = [ - .none, - .noRoute, - .gotNak, - .timeout, - .noInterface, - .maxRetransmit, - .noChannel, - .tooLarge, - .noResponse, - .dutyCycleLimit, - .badRequest, - .notAuthorized, - .pkiFailed, - .pkiUnknownPubkey, - .adminBadSessionKey, - .adminPublicKeyUnauthorized, - ] -} - -#endif // swift(>=4.2) - /// /// (Formerly called SubPacket) /// The payload portion fo a packet, this is the actual bytes that are sent /// inside a radio packet (because from/to are broken out by the comms library) -public struct DataMessage { +public struct DataMessage: @unchecked Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -1615,7 +1718,7 @@ public struct DataMessage { /// /// Waypoint message, used to share arbitrary locations across the mesh -public struct Waypoint { +public struct Waypoint: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -1677,7 +1780,7 @@ public struct Waypoint { /// /// This message will be proxied over the PhoneAPI for the client to deliver to the MQTT server -public struct MqttClientProxyMessage { +public struct MqttClientProxyMessage: @unchecked Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -1718,7 +1821,7 @@ public struct MqttClientProxyMessage { /// /// The actual service envelope payload or text for mqtt pub / sub - public enum OneOf_PayloadVariant: Equatable { + public enum OneOf_PayloadVariant: Equatable, @unchecked Sendable { /// /// Bytes case data(Data) @@ -1726,24 +1829,6 @@ public struct MqttClientProxyMessage { /// Text case text(String) - #if !swift(>=4.1) - public static func ==(lhs: MqttClientProxyMessage.OneOf_PayloadVariant, rhs: MqttClientProxyMessage.OneOf_PayloadVariant) -> Bool { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch (lhs, rhs) { - case (.data, .data): return { - guard case .data(let l) = lhs, case .data(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.text, .text): return { - guard case .text(let l) = lhs, case .text(let r) = rhs else { preconditionFailure() } - return l == r - }() - default: return false - } - } - #endif } public init() {} @@ -1753,7 +1838,7 @@ public struct MqttClientProxyMessage { /// A packet envelope sent/received over the mesh /// only payload_variant is sent in the payload portion of the LORA packet. /// The other fields are either not sent at all, or sent in the special 16 byte LORA header. -public struct MeshPacket { +public struct MeshPacket: @unchecked Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -1846,7 +1931,7 @@ public struct MeshPacket { } /// - /// If unset treated as zero (no forwarding, send to adjacent nodes only) + /// If unset treated as zero (no forwarding, send to direct neighbor nodes only) /// if 1, allow hopping through one node, etc... /// For our usecase real world topologies probably have a max of about 3. /// This field is normally placed into a few of bits in the header. @@ -1887,6 +1972,8 @@ public struct MeshPacket { /// /// Describe if this message is delayed + /// + /// NOTE: This field was marked as deprecated in the .proto file. public var delayed: MeshPacket.Delayed { get {return _storage._delayed} set {_uniqueStorage()._delayed = newValue} @@ -1921,9 +2008,34 @@ public struct MeshPacket { set {_uniqueStorage()._pkiEncrypted = newValue} } + /// + /// Last byte of the node number of the node that should be used as the next hop in routing. + /// Set by the firmware internally, clients are not supposed to set this. + public var nextHop: UInt32 { + get {return _storage._nextHop} + set {_uniqueStorage()._nextHop = newValue} + } + + /// + /// Last byte of the node number of the node that will relay/relayed this packet. + /// Set by the firmware internally, clients are not supposed to set this. + public var relayNode: UInt32 { + get {return _storage._relayNode} + set {_uniqueStorage()._relayNode = newValue} + } + + /// + /// *Never* sent over the radio links. + /// Timestamp after which this packet may be sent. + /// Set by the firmware internally, clients are not supposed to set this. + public var txAfter: UInt32 { + get {return _storage._txAfter} + set {_uniqueStorage()._txAfter = newValue} + } + public var unknownFields = SwiftProtobuf.UnknownStorage() - public enum OneOf_PayloadVariant: Equatable { + public enum OneOf_PayloadVariant: Equatable, @unchecked Sendable { /// /// TODO: REPLACE case decoded(DataMessage) @@ -1931,24 +2043,6 @@ public struct MeshPacket { /// TODO: REPLACE case encrypted(Data) - #if !swift(>=4.1) - public static func ==(lhs: MeshPacket.OneOf_PayloadVariant, rhs: MeshPacket.OneOf_PayloadVariant) -> Bool { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch (lhs, rhs) { - case (.decoded, .decoded): return { - guard case .decoded(let l) = lhs, case .decoded(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.encrypted, .encrypted): return { - guard case .encrypted(let l) = lhs, case .encrypted(let r) = rhs else { preconditionFailure() } - return l == r - }() - default: return false - } - } - #endif } /// @@ -1970,7 +2064,7 @@ public struct MeshPacket { /// So I bit the bullet and implemented a new (internal - not sent over the air) /// field in MeshPacket called 'priority'. /// And the transmission queue in the router object is now a priority queue. - public enum Priority: SwiftProtobuf.Enum { + public enum Priority: SwiftProtobuf.Enum, Swift.CaseIterable { public typealias RawValue = Int /// @@ -2004,6 +2098,10 @@ public struct MeshPacket { /// Higher priority for specific message types (portnums) to distinguish between other reliable packets. case high // = 100 + /// + /// Higher priority alert message used for critical alerts which take priority over other reliable packets. + case alert // = 110 + /// /// Ack/naks are sent with very high priority to ensure that retransmission /// stops as soon as possible @@ -2027,6 +2125,7 @@ public struct MeshPacket { case 70: self = .reliable case 80: self = .response case 100: self = .high + case 110: self = .alert case 120: self = .ack case 127: self = .max default: self = .UNRECOGNIZED(rawValue) @@ -2042,17 +2141,32 @@ public struct MeshPacket { case .reliable: return 70 case .response: return 80 case .high: return 100 + case .alert: return 110 case .ack: return 120 case .max: return 127 case .UNRECOGNIZED(let i): return i } } + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [MeshPacket.Priority] = [ + .unset, + .min, + .background, + .default, + .reliable, + .response, + .high, + .alert, + .ack, + .max, + ] + } /// /// Identify if this is a delayed packet - public enum Delayed: SwiftProtobuf.Enum { + public enum Delayed: SwiftProtobuf.Enum, Swift.CaseIterable { public typealias RawValue = Int /// @@ -2090,6 +2204,13 @@ public struct MeshPacket { } } + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [MeshPacket.Delayed] = [ + .noDelay, + .broadcast, + .direct, + ] + } public init() {} @@ -2097,34 +2218,6 @@ public struct MeshPacket { fileprivate var _storage = _StorageClass.defaultInstance } -#if swift(>=4.2) - -extension MeshPacket.Priority: CaseIterable { - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [MeshPacket.Priority] = [ - .unset, - .min, - .background, - .default, - .reliable, - .response, - .high, - .ack, - .max, - ] -} - -extension MeshPacket.Delayed: CaseIterable { - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [MeshPacket.Delayed] = [ - .noDelay, - .broadcast, - .direct, - ] -} - -#endif // swift(>=4.2) - /// /// The bluetooth to device link: /// Old BTLE protocol docs from TODO, merge in above and make real docs... @@ -2142,7 +2235,7 @@ extension MeshPacket.Delayed: CaseIterable { /// level etc) SET_CONFIG (switches device to a new set of radio params and /// preshared key, drops all existing nodes, force our node to rejoin this new group) /// Full information about a node on the mesh -public struct NodeInfo { +public struct NodeInfo: @unchecked Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -2218,7 +2311,7 @@ public struct NodeInfo { } /// - /// Number of hops away from us this node is (0 if adjacent) + /// Number of hops away from us this node is (0 if direct neighbor) public var hopsAway: UInt32 { get {return _storage._hopsAway ?? 0} set {_uniqueStorage()._hopsAway = newValue} @@ -2236,6 +2329,14 @@ public struct NodeInfo { set {_uniqueStorage()._isFavorite = newValue} } + /// + /// True if node is in our ignored list + /// Persists between NodeDB internal clean ups + public var isIgnored: Bool { + get {return _storage._isIgnored} + set {_uniqueStorage()._isIgnored = newValue} + } + public var unknownFields = SwiftProtobuf.UnknownStorage() public init() {} @@ -2247,7 +2348,7 @@ public struct NodeInfo { /// Unique local debugging info for this node /// Note: we don't include position or the user info, because that will come in the /// Sent to the phone in response to WantNodes. -public struct MyNodeInfo { +public struct MyNodeInfo: @unchecked Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -2267,6 +2368,14 @@ public struct MyNodeInfo { /// Phone/PC apps should compare this to their build number and if too low tell the user they must update their app public var minAppVersion: UInt32 = 0 + /// + /// Unique hardware identifier for this device + public var deviceID: Data = Data() + + /// + /// The PlatformIO environment used to build this firmware + public var pioEnv: String = String() + public var unknownFields = SwiftProtobuf.UnknownStorage() public init() {} @@ -2278,7 +2387,7 @@ public struct MyNodeInfo { /// on the message it is assumed to be a continuation of the previously sent message. /// This allows the device code to use fixed maxlen 64 byte strings for messages, /// and then extend as needed by emitting multiple records. -public struct LogRecord { +public struct LogRecord: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -2303,7 +2412,7 @@ public struct LogRecord { /// /// Log levels, chosen to match python logging conventions. - public enum Level: SwiftProtobuf.Enum { + public enum Level: SwiftProtobuf.Enum, Swift.CaseIterable { public typealias RawValue = Int /// @@ -2365,29 +2474,23 @@ public struct LogRecord { } } + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [LogRecord.Level] = [ + .unset, + .critical, + .error, + .warning, + .info, + .debug, + .trace, + ] + } public init() {} } -#if swift(>=4.2) - -extension LogRecord.Level: CaseIterable { - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [LogRecord.Level] = [ - .unset, - .critical, - .error, - .warning, - .info, - .debug, - .trace, - ] -} - -#endif // swift(>=4.2) - -public struct QueueStatus { +public struct QueueStatus: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -2414,7 +2517,7 @@ public struct QueueStatus { /// It will support READ and NOTIFY. When a new packet arrives the device will BLE notify? /// It will sit in that descriptor until consumed by the phone, /// at which point the next item in the FIFO will be populated. -public struct FromRadio { +public struct FromRadio: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -2586,11 +2689,21 @@ public struct FromRadio { set {payloadVariant = .clientNotification(newValue)} } + /// + /// Persistent data for device-ui + public var deviceuiConfig: DeviceUIConfig { + get { + if case .deviceuiConfig(let v)? = payloadVariant {return v} + return DeviceUIConfig() + } + set {payloadVariant = .deviceuiConfig(newValue)} + } + public var unknownFields = SwiftProtobuf.UnknownStorage() /// /// Log levels, chosen to match python logging conventions. - public enum OneOf_PayloadVariant: Equatable { + public enum OneOf_PayloadVariant: Equatable, Sendable { /// /// Log levels, chosen to match python logging conventions. case packet(MeshPacket) @@ -2644,77 +2757,10 @@ public struct FromRadio { /// /// Notification message to the client case clientNotification(ClientNotification) + /// + /// Persistent data for device-ui + case deviceuiConfig(DeviceUIConfig) - #if !swift(>=4.1) - public static func ==(lhs: FromRadio.OneOf_PayloadVariant, rhs: FromRadio.OneOf_PayloadVariant) -> Bool { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch (lhs, rhs) { - case (.packet, .packet): return { - guard case .packet(let l) = lhs, case .packet(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.myInfo, .myInfo): return { - guard case .myInfo(let l) = lhs, case .myInfo(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.nodeInfo, .nodeInfo): return { - guard case .nodeInfo(let l) = lhs, case .nodeInfo(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.config, .config): return { - guard case .config(let l) = lhs, case .config(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.logRecord, .logRecord): return { - guard case .logRecord(let l) = lhs, case .logRecord(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.configCompleteID, .configCompleteID): return { - guard case .configCompleteID(let l) = lhs, case .configCompleteID(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.rebooted, .rebooted): return { - guard case .rebooted(let l) = lhs, case .rebooted(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.moduleConfig, .moduleConfig): return { - guard case .moduleConfig(let l) = lhs, case .moduleConfig(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.channel, .channel): return { - guard case .channel(let l) = lhs, case .channel(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.queueStatus, .queueStatus): return { - guard case .queueStatus(let l) = lhs, case .queueStatus(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.xmodemPacket, .xmodemPacket): return { - guard case .xmodemPacket(let l) = lhs, case .xmodemPacket(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.metadata, .metadata): return { - guard case .metadata(let l) = lhs, case .metadata(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.mqttClientProxyMessage, .mqttClientProxyMessage): return { - guard case .mqttClientProxyMessage(let l) = lhs, case .mqttClientProxyMessage(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.fileInfo, .fileInfo): return { - guard case .fileInfo(let l) = lhs, case .fileInfo(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.clientNotification, .clientNotification): return { - guard case .clientNotification(let l) = lhs, case .clientNotification(let r) = rhs else { preconditionFailure() } - return l == r - }() - default: return false - } - } - #endif } public init() {} @@ -2725,7 +2771,7 @@ public struct FromRadio { /// To be used for important messages that should to be displayed to the user /// in the form of push notifications or validation messages when saving /// invalid configuration. -public struct ClientNotification { +public struct ClientNotification: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -2762,7 +2808,7 @@ public struct ClientNotification { /// /// Individual File info for the device -public struct FileInfo { +public struct FileInfo: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -2783,7 +2829,7 @@ public struct FileInfo { /// /// Packets/commands to the radio will be written (reliably) to the toRadio characteristic. /// Once the write completes the phone can assume it is handled. -public struct ToRadio { +public struct ToRadio: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -2863,7 +2909,7 @@ public struct ToRadio { /// /// Log levels, chosen to match python logging conventions. - public enum OneOf_PayloadVariant: Equatable { + public enum OneOf_PayloadVariant: Equatable, Sendable { /// /// Send this packet on the mesh case packet(MeshPacket) @@ -2890,40 +2936,6 @@ public struct ToRadio { /// Heartbeat message (used to keep the device connection awake on serial) case heartbeat(Heartbeat) - #if !swift(>=4.1) - public static func ==(lhs: ToRadio.OneOf_PayloadVariant, rhs: ToRadio.OneOf_PayloadVariant) -> Bool { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch (lhs, rhs) { - case (.packet, .packet): return { - guard case .packet(let l) = lhs, case .packet(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.wantConfigID, .wantConfigID): return { - guard case .wantConfigID(let l) = lhs, case .wantConfigID(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.disconnect, .disconnect): return { - guard case .disconnect(let l) = lhs, case .disconnect(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.xmodemPacket, .xmodemPacket): return { - guard case .xmodemPacket(let l) = lhs, case .xmodemPacket(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.mqttClientProxyMessage, .mqttClientProxyMessage): return { - guard case .mqttClientProxyMessage(let l) = lhs, case .mqttClientProxyMessage(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.heartbeat, .heartbeat): return { - guard case .heartbeat(let l) = lhs, case .heartbeat(let r) = rhs else { preconditionFailure() } - return l == r - }() - default: return false - } - } - #endif } public init() {} @@ -2931,7 +2943,7 @@ public struct ToRadio { /// /// Compressed message payload -public struct Compressed { +public struct Compressed: @unchecked Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -2951,7 +2963,7 @@ public struct Compressed { /// /// Full info on edges for a single node -public struct NeighborInfo { +public struct NeighborInfo: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -2979,7 +2991,7 @@ public struct NeighborInfo { /// /// A single edge in the mesh -public struct Neighbor { +public struct Neighbor: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -3009,7 +3021,7 @@ public struct Neighbor { /// /// Device metadata response -public struct DeviceMetadata { +public struct DeviceMetadata: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -3058,6 +3070,11 @@ public struct DeviceMetadata { /// Has PKC capabilities public var hasPkc_p: Bool = false + /// + /// Bit field of boolean for excluded modules + /// (bitwise OR of ExcludedModules) + public var excludedModules: UInt32 = 0 + public var unknownFields = SwiftProtobuf.UnknownStorage() public init() {} @@ -3066,7 +3083,7 @@ public struct DeviceMetadata { /// /// A heartbeat message is sent to the node from the client to keep the connection alive. /// This is currently only needed to keep serial connections alive, but can be used by any PhoneAPI. -public struct Heartbeat { +public struct Heartbeat: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -3078,7 +3095,7 @@ public struct Heartbeat { /// /// RemoteHardwarePins associated with a node -public struct NodeRemoteHardwarePin { +public struct NodeRemoteHardwarePin: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -3105,7 +3122,7 @@ public struct NodeRemoteHardwarePin { fileprivate var _pin: RemoteHardwarePin? = nil } -public struct ChunkedPayload { +public struct ChunkedPayload: @unchecked Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -3133,7 +3150,7 @@ public struct ChunkedPayload { /// /// Wrapper message for broken repeated oneof support -public struct resend_chunks { +public struct resend_chunks: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -3147,7 +3164,7 @@ public struct resend_chunks { /// /// Responses to a ChunkedPayload request -public struct ChunkedPayloadResponse { +public struct ChunkedPayloadResponse: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -3190,7 +3207,7 @@ public struct ChunkedPayloadResponse { public var unknownFields = SwiftProtobuf.UnknownStorage() - public enum OneOf_PayloadVariant: Equatable { + public enum OneOf_PayloadVariant: Equatable, Sendable { /// /// Request to transfer chunked payload case requestTransfer(Bool) @@ -3201,76 +3218,11 @@ public struct ChunkedPayloadResponse { /// Request missing indexes in the chunked payload case resendChunks(resend_chunks) - #if !swift(>=4.1) - public static func ==(lhs: ChunkedPayloadResponse.OneOf_PayloadVariant, rhs: ChunkedPayloadResponse.OneOf_PayloadVariant) -> Bool { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch (lhs, rhs) { - case (.requestTransfer, .requestTransfer): return { - guard case .requestTransfer(let l) = lhs, case .requestTransfer(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.acceptTransfer, .acceptTransfer): return { - guard case .acceptTransfer(let l) = lhs, case .acceptTransfer(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.resendChunks, .resendChunks): return { - guard case .resendChunks(let l) = lhs, case .resendChunks(let r) = rhs else { preconditionFailure() } - return l == r - }() - default: return false - } - } - #endif } public init() {} } -#if swift(>=5.5) && canImport(_Concurrency) -extension HardwareModel: @unchecked Sendable {} -extension Constants: @unchecked Sendable {} -extension CriticalErrorCode: @unchecked Sendable {} -extension Position: @unchecked Sendable {} -extension Position.LocSource: @unchecked Sendable {} -extension Position.AltSource: @unchecked Sendable {} -extension User: @unchecked Sendable {} -extension RouteDiscovery: @unchecked Sendable {} -extension Routing: @unchecked Sendable {} -extension Routing.OneOf_Variant: @unchecked Sendable {} -extension Routing.Error: @unchecked Sendable {} -extension DataMessage: @unchecked Sendable {} -extension Waypoint: @unchecked Sendable {} -extension MqttClientProxyMessage: @unchecked Sendable {} -extension MqttClientProxyMessage.OneOf_PayloadVariant: @unchecked Sendable {} -extension MeshPacket: @unchecked Sendable {} -extension MeshPacket.OneOf_PayloadVariant: @unchecked Sendable {} -extension MeshPacket.Priority: @unchecked Sendable {} -extension MeshPacket.Delayed: @unchecked Sendable {} -extension NodeInfo: @unchecked Sendable {} -extension MyNodeInfo: @unchecked Sendable {} -extension LogRecord: @unchecked Sendable {} -extension LogRecord.Level: @unchecked Sendable {} -extension QueueStatus: @unchecked Sendable {} -extension FromRadio: @unchecked Sendable {} -extension FromRadio.OneOf_PayloadVariant: @unchecked Sendable {} -extension ClientNotification: @unchecked Sendable {} -extension FileInfo: @unchecked Sendable {} -extension ToRadio: @unchecked Sendable {} -extension ToRadio.OneOf_PayloadVariant: @unchecked Sendable {} -extension Compressed: @unchecked Sendable {} -extension NeighborInfo: @unchecked Sendable {} -extension Neighbor: @unchecked Sendable {} -extension DeviceMetadata: @unchecked Sendable {} -extension Heartbeat: @unchecked Sendable {} -extension NodeRemoteHardwarePin: @unchecked Sendable {} -extension ChunkedPayload: @unchecked Sendable {} -extension resend_chunks: @unchecked Sendable {} -extension ChunkedPayloadResponse: @unchecked Sendable {} -extension ChunkedPayloadResponse.OneOf_PayloadVariant: @unchecked Sendable {} -#endif // swift(>=5.5) && canImport(_Concurrency) - // MARK: - Code below here is support for the SwiftProtobuf runtime. fileprivate let _protobuf_package = "meshtastic" @@ -3361,6 +3313,9 @@ extension HardwareModel: SwiftProtobuf._ProtoNameProviding { 81: .same(proto: "SEEED_XIAO_S3"), 82: .same(proto: "MS24SF1"), 83: .same(proto: "TLORA_C6"), + 84: .same(proto: "WISMESH_TAP"), + 85: .same(proto: "ROUTASTIC"), + 86: .same(proto: "MESH_TAB"), 255: .same(proto: "PRIVATE_HW"), ] } @@ -3368,7 +3323,7 @@ extension HardwareModel: SwiftProtobuf._ProtoNameProviding { extension Constants: SwiftProtobuf._ProtoNameProviding { public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 0: .same(proto: "ZERO"), - 237: .same(proto: "DATA_PAYLOAD_LEN"), + 233: .same(proto: "DATA_PAYLOAD_LEN"), ] } @@ -3391,6 +3346,25 @@ extension CriticalErrorCode: SwiftProtobuf._ProtoNameProviding { ] } +extension ExcludedModules: SwiftProtobuf._ProtoNameProviding { + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "EXCLUDED_NONE"), + 1: .same(proto: "MQTT_CONFIG"), + 2: .same(proto: "SERIAL_CONFIG"), + 4: .same(proto: "EXTNOTIF_CONFIG"), + 8: .same(proto: "STOREFORWARD_CONFIG"), + 16: .same(proto: "RANGETEST_CONFIG"), + 32: .same(proto: "TELEMETRY_CONFIG"), + 64: .same(proto: "CANNEDMSG_CONFIG"), + 128: .same(proto: "AUDIO_CONFIG"), + 256: .same(proto: "REMOTEHARDWARE_CONFIG"), + 512: .same(proto: "NEIGHBORINFO_CONFIG"), + 1024: .same(proto: "AMBIENTLIGHTING_CONFIG"), + 2048: .same(proto: "DETECTIONSENSOR_CONFIG"), + 4096: .same(proto: "PAXCOUNTER_CONFIG"), + ] +} + extension Position: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".Position" public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ @@ -4144,6 +4118,9 @@ extension MeshPacket: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio 15: .standard(proto: "hop_start"), 16: .standard(proto: "public_key"), 17: .standard(proto: "pki_encrypted"), + 18: .standard(proto: "next_hop"), + 19: .standard(proto: "relay_node"), + 20: .standard(proto: "tx_after"), ] fileprivate class _StorageClass { @@ -4163,6 +4140,9 @@ extension MeshPacket: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio var _hopStart: UInt32 = 0 var _publicKey: Data = Data() var _pkiEncrypted: Bool = false + var _nextHop: UInt32 = 0 + var _relayNode: UInt32 = 0 + var _txAfter: UInt32 = 0 #if swift(>=5.10) // This property is used as the initial default value for new instances of the type. @@ -4193,6 +4173,9 @@ extension MeshPacket: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio _hopStart = source._hopStart _publicKey = source._publicKey _pkiEncrypted = source._pkiEncrypted + _nextHop = source._nextHop + _relayNode = source._relayNode + _txAfter = source._txAfter } } @@ -4247,6 +4230,9 @@ extension MeshPacket: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio case 15: try { try decoder.decodeSingularUInt32Field(value: &_storage._hopStart) }() case 16: try { try decoder.decodeSingularBytesField(value: &_storage._publicKey) }() case 17: try { try decoder.decodeSingularBoolField(value: &_storage._pkiEncrypted) }() + case 18: try { try decoder.decodeSingularUInt32Field(value: &_storage._nextHop) }() + case 19: try { try decoder.decodeSingularUInt32Field(value: &_storage._relayNode) }() + case 20: try { try decoder.decodeSingularUInt32Field(value: &_storage._txAfter) }() default: break } } @@ -4285,7 +4271,7 @@ extension MeshPacket: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio if _storage._rxTime != 0 { try visitor.visitSingularFixed32Field(value: _storage._rxTime, fieldNumber: 7) } - if _storage._rxSnr != 0 { + if _storage._rxSnr.bitPattern != 0 { try visitor.visitSingularFloatField(value: _storage._rxSnr, fieldNumber: 8) } if _storage._hopLimit != 0 { @@ -4315,6 +4301,15 @@ extension MeshPacket: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio if _storage._pkiEncrypted != false { try visitor.visitSingularBoolField(value: _storage._pkiEncrypted, fieldNumber: 17) } + if _storage._nextHop != 0 { + try visitor.visitSingularUInt32Field(value: _storage._nextHop, fieldNumber: 18) + } + if _storage._relayNode != 0 { + try visitor.visitSingularUInt32Field(value: _storage._relayNode, fieldNumber: 19) + } + if _storage._txAfter != 0 { + try visitor.visitSingularUInt32Field(value: _storage._txAfter, fieldNumber: 20) + } } try unknownFields.traverse(visitor: &visitor) } @@ -4340,6 +4335,9 @@ extension MeshPacket: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio if _storage._hopStart != rhs_storage._hopStart {return false} if _storage._publicKey != rhs_storage._publicKey {return false} if _storage._pkiEncrypted != rhs_storage._pkiEncrypted {return false} + if _storage._nextHop != rhs_storage._nextHop {return false} + if _storage._relayNode != rhs_storage._relayNode {return false} + if _storage._txAfter != rhs_storage._txAfter {return false} return true } if !storagesAreEqual {return false} @@ -4358,6 +4356,7 @@ extension MeshPacket.Priority: SwiftProtobuf._ProtoNameProviding { 70: .same(proto: "RELIABLE"), 80: .same(proto: "RESPONSE"), 100: .same(proto: "HIGH"), + 110: .same(proto: "ALERT"), 120: .same(proto: "ACK"), 127: .same(proto: "MAX"), ] @@ -4384,6 +4383,7 @@ extension NodeInfo: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB 8: .standard(proto: "via_mqtt"), 9: .standard(proto: "hops_away"), 10: .standard(proto: "is_favorite"), + 11: .standard(proto: "is_ignored"), ] fileprivate class _StorageClass { @@ -4397,6 +4397,7 @@ extension NodeInfo: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB var _viaMqtt: Bool = false var _hopsAway: UInt32? = nil var _isFavorite: Bool = false + var _isIgnored: Bool = false #if swift(>=5.10) // This property is used as the initial default value for new instances of the type. @@ -4421,6 +4422,7 @@ extension NodeInfo: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB _viaMqtt = source._viaMqtt _hopsAway = source._hopsAway _isFavorite = source._isFavorite + _isIgnored = source._isIgnored } } @@ -4449,6 +4451,7 @@ extension NodeInfo: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB case 8: try { try decoder.decodeSingularBoolField(value: &_storage._viaMqtt) }() case 9: try { try decoder.decodeSingularUInt32Field(value: &_storage._hopsAway) }() case 10: try { try decoder.decodeSingularBoolField(value: &_storage._isFavorite) }() + case 11: try { try decoder.decodeSingularBoolField(value: &_storage._isIgnored) }() default: break } } @@ -4470,7 +4473,7 @@ extension NodeInfo: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB try { if let v = _storage._position { try visitor.visitSingularMessageField(value: v, fieldNumber: 3) } }() - if _storage._snr != 0 { + if _storage._snr.bitPattern != 0 { try visitor.visitSingularFloatField(value: _storage._snr, fieldNumber: 4) } if _storage._lastHeard != 0 { @@ -4491,6 +4494,9 @@ extension NodeInfo: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB if _storage._isFavorite != false { try visitor.visitSingularBoolField(value: _storage._isFavorite, fieldNumber: 10) } + if _storage._isIgnored != false { + try visitor.visitSingularBoolField(value: _storage._isIgnored, fieldNumber: 11) + } } try unknownFields.traverse(visitor: &visitor) } @@ -4510,6 +4516,7 @@ extension NodeInfo: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB if _storage._viaMqtt != rhs_storage._viaMqtt {return false} if _storage._hopsAway != rhs_storage._hopsAway {return false} if _storage._isFavorite != rhs_storage._isFavorite {return false} + if _storage._isIgnored != rhs_storage._isIgnored {return false} return true } if !storagesAreEqual {return false} @@ -4525,6 +4532,8 @@ extension MyNodeInfo: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio 1: .standard(proto: "my_node_num"), 8: .standard(proto: "reboot_count"), 11: .standard(proto: "min_app_version"), + 12: .standard(proto: "device_id"), + 13: .standard(proto: "pio_env"), ] public mutating func decodeMessage(decoder: inout D) throws { @@ -4536,6 +4545,8 @@ extension MyNodeInfo: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio case 1: try { try decoder.decodeSingularUInt32Field(value: &self.myNodeNum) }() case 8: try { try decoder.decodeSingularUInt32Field(value: &self.rebootCount) }() case 11: try { try decoder.decodeSingularUInt32Field(value: &self.minAppVersion) }() + case 12: try { try decoder.decodeSingularBytesField(value: &self.deviceID) }() + case 13: try { try decoder.decodeSingularStringField(value: &self.pioEnv) }() default: break } } @@ -4551,6 +4562,12 @@ extension MyNodeInfo: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio if self.minAppVersion != 0 { try visitor.visitSingularUInt32Field(value: self.minAppVersion, fieldNumber: 11) } + if !self.deviceID.isEmpty { + try visitor.visitSingularBytesField(value: self.deviceID, fieldNumber: 12) + } + if !self.pioEnv.isEmpty { + try visitor.visitSingularStringField(value: self.pioEnv, fieldNumber: 13) + } try unknownFields.traverse(visitor: &visitor) } @@ -4558,6 +4575,8 @@ extension MyNodeInfo: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio if lhs.myNodeNum != rhs.myNodeNum {return false} if lhs.rebootCount != rhs.rebootCount {return false} if lhs.minAppVersion != rhs.minAppVersion {return false} + if lhs.deviceID != rhs.deviceID {return false} + if lhs.pioEnv != rhs.pioEnv {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } @@ -4694,6 +4713,7 @@ extension FromRadio: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementation 14: .same(proto: "mqttClientProxyMessage"), 15: .same(proto: "fileInfo"), 16: .same(proto: "clientNotification"), + 17: .same(proto: "deviceuiConfig"), ] public mutating func decodeMessage(decoder: inout D) throws { @@ -4888,6 +4908,19 @@ extension FromRadio: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementation self.payloadVariant = .clientNotification(v) } }() + case 17: try { + var v: DeviceUIConfig? + var hadOneofValue = false + if let current = self.payloadVariant { + hadOneofValue = true + if case .deviceuiConfig(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.payloadVariant = .deviceuiConfig(v) + } + }() default: break } } @@ -4962,6 +4995,10 @@ extension FromRadio: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementation guard case .clientNotification(let v)? = self.payloadVariant else { preconditionFailure() } try visitor.visitSingularMessageField(value: v, fieldNumber: 16) }() + case .deviceuiConfig?: try { + guard case .deviceuiConfig(let v)? = self.payloadVariant else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 17) + }() case nil: break } try unknownFields.traverse(visitor: &visitor) @@ -5315,7 +5352,7 @@ extension Neighbor: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB if self.nodeID != 0 { try visitor.visitSingularUInt32Field(value: self.nodeID, fieldNumber: 1) } - if self.snr != 0 { + if self.snr.bitPattern != 0 { try visitor.visitSingularFloatField(value: self.snr, fieldNumber: 2) } if self.lastRxTime != 0 { @@ -5351,6 +5388,7 @@ extension DeviceMetadata: SwiftProtobuf.Message, SwiftProtobuf._MessageImplement 9: .standard(proto: "hw_model"), 10: .same(proto: "hasRemoteHardware"), 11: .same(proto: "hasPKC"), + 12: .standard(proto: "excluded_modules"), ] public mutating func decodeMessage(decoder: inout D) throws { @@ -5370,6 +5408,7 @@ extension DeviceMetadata: SwiftProtobuf.Message, SwiftProtobuf._MessageImplement case 9: try { try decoder.decodeSingularEnumField(value: &self.hwModel) }() case 10: try { try decoder.decodeSingularBoolField(value: &self.hasRemoteHardware_p) }() case 11: try { try decoder.decodeSingularBoolField(value: &self.hasPkc_p) }() + case 12: try { try decoder.decodeSingularUInt32Field(value: &self.excludedModules) }() default: break } } @@ -5409,6 +5448,9 @@ extension DeviceMetadata: SwiftProtobuf.Message, SwiftProtobuf._MessageImplement if self.hasPkc_p != false { try visitor.visitSingularBoolField(value: self.hasPkc_p, fieldNumber: 11) } + if self.excludedModules != 0 { + try visitor.visitSingularUInt32Field(value: self.excludedModules, fieldNumber: 12) + } try unknownFields.traverse(visitor: &visitor) } @@ -5424,6 +5466,7 @@ extension DeviceMetadata: SwiftProtobuf.Message, SwiftProtobuf._MessageImplement if lhs.hwModel != rhs.hwModel {return false} if lhs.hasRemoteHardware_p != rhs.hasRemoteHardware_p {return false} if lhs.hasPkc_p != rhs.hasPkc_p {return false} + if lhs.excludedModules != rhs.excludedModules {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } @@ -5434,8 +5477,8 @@ extension Heartbeat: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementation public static let _protobuf_nameMap = SwiftProtobuf._NameMap() public mutating func decodeMessage(decoder: inout D) throws { - while let _ = try decoder.nextFieldNumber() { - } + // Load everything into unknown fields + while try decoder.nextFieldNumber() != nil {} } public func traverse(visitor: inout V) throws { diff --git a/MeshtasticProtobufs/Sources/meshtastic/module_config.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/module_config.pb.swift index 30a8e0a4..2cb3291b 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/module_config.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/module_config.pb.swift @@ -1,5 +1,6 @@ // DO NOT EDIT. // swift-format-ignore-file +// swiftlint:disable all // // Generated by the Swift generator plugin for the protocol buffer compiler. // Source: meshtastic/module_config.proto @@ -7,7 +8,6 @@ // For information on using the generated types, please see the documentation: // https://github.com/apple/swift-protobuf/ -import Foundation import SwiftProtobuf // If the compiler emits an error on this type, it is because this file @@ -20,7 +20,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP typealias Version = _2 } -public enum RemoteHardwarePinType: SwiftProtobuf.Enum { +public enum RemoteHardwarePinType: SwiftProtobuf.Enum, Swift.CaseIterable { public typealias RawValue = Int /// @@ -58,24 +58,18 @@ public enum RemoteHardwarePinType: SwiftProtobuf.Enum { } } -} - -#if swift(>=4.2) - -extension RemoteHardwarePinType: CaseIterable { // The compiler won't synthesize support with the UNRECOGNIZED case. public static let allCases: [RemoteHardwarePinType] = [ .unknown, .digitalRead, .digitalWrite, ] -} -#endif // swift(>=4.2) +} /// /// Module Config -public struct ModuleConfig { +public struct ModuleConfig: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -218,7 +212,7 @@ public struct ModuleConfig { /// /// TODO: REPLACE - public enum OneOf_PayloadVariant: Equatable { + public enum OneOf_PayloadVariant: Equatable, Sendable { /// /// TODO: REPLACE case mqtt(ModuleConfig.MQTTConfig) @@ -259,73 +253,11 @@ public struct ModuleConfig { /// TODO: REPLACE case paxcounter(ModuleConfig.PaxcounterConfig) - #if !swift(>=4.1) - public static func ==(lhs: ModuleConfig.OneOf_PayloadVariant, rhs: ModuleConfig.OneOf_PayloadVariant) -> Bool { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch (lhs, rhs) { - case (.mqtt, .mqtt): return { - guard case .mqtt(let l) = lhs, case .mqtt(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.serial, .serial): return { - guard case .serial(let l) = lhs, case .serial(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.externalNotification, .externalNotification): return { - guard case .externalNotification(let l) = lhs, case .externalNotification(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.storeForward, .storeForward): return { - guard case .storeForward(let l) = lhs, case .storeForward(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.rangeTest, .rangeTest): return { - guard case .rangeTest(let l) = lhs, case .rangeTest(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.telemetry, .telemetry): return { - guard case .telemetry(let l) = lhs, case .telemetry(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.cannedMessage, .cannedMessage): return { - guard case .cannedMessage(let l) = lhs, case .cannedMessage(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.audio, .audio): return { - guard case .audio(let l) = lhs, case .audio(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.remoteHardware, .remoteHardware): return { - guard case .remoteHardware(let l) = lhs, case .remoteHardware(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.neighborInfo, .neighborInfo): return { - guard case .neighborInfo(let l) = lhs, case .neighborInfo(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.ambientLighting, .ambientLighting): return { - guard case .ambientLighting(let l) = lhs, case .ambientLighting(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.detectionSensor, .detectionSensor): return { - guard case .detectionSensor(let l) = lhs, case .detectionSensor(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.paxcounter, .paxcounter): return { - guard case .paxcounter(let l) = lhs, case .paxcounter(let r) = rhs else { preconditionFailure() } - return l == r - }() - default: return false - } - } - #endif } /// /// MQTT Client Config - public struct MQTTConfig { + public struct MQTTConfig: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -400,7 +332,7 @@ public struct ModuleConfig { /// /// Settings for reporting unencrypted information about our node to a map via MQTT - public struct MapReportSettings { + public struct MapReportSettings: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -420,7 +352,7 @@ public struct ModuleConfig { /// /// RemoteHardwareModule Config - public struct RemoteHardwareConfig { + public struct RemoteHardwareConfig: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -444,7 +376,7 @@ public struct ModuleConfig { /// /// NeighborInfoModule Config - public struct NeighborInfoConfig { + public struct NeighborInfoConfig: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -455,9 +387,14 @@ public struct ModuleConfig { /// /// Interval in seconds of how often we should try to send our - /// Neighbor Info to the mesh + /// Neighbor Info (minimum is 14400, i.e., 4 hours) public var updateInterval: UInt32 = 0 + /// + /// Whether in addition to sending it to MQTT and the PhoneAPI, our NeighborInfo should be transmitted over LoRa. + /// Note that this is not available on a channel with default key and name. + public var transmitOverLora: Bool = false + public var unknownFields = SwiftProtobuf.UnknownStorage() public init() {} @@ -465,7 +402,7 @@ public struct ModuleConfig { /// /// Detection Sensor Module Config - public struct DetectionSensorConfig { + public struct DetectionSensorConfig: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -512,7 +449,7 @@ public struct ModuleConfig { public var unknownFields = SwiftProtobuf.UnknownStorage() - public enum TriggerType: SwiftProtobuf.Enum { + public enum TriggerType: SwiftProtobuf.Enum, Swift.CaseIterable { public typealias RawValue = Int /// Event is triggered if pin is low @@ -564,6 +501,16 @@ public struct ModuleConfig { } } + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [ModuleConfig.DetectionSensorConfig.TriggerType] = [ + .logicLow, + .logicHigh, + .fallingEdge, + .risingEdge, + .eitherEdgeActiveLow, + .eitherEdgeActiveHigh, + ] + } public init() {} @@ -571,7 +518,7 @@ public struct ModuleConfig { /// /// Audio Config for codec2 voice - public struct AudioConfig { + public struct AudioConfig: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -608,7 +555,7 @@ public struct ModuleConfig { /// /// Baudrate for codec2 voice - public enum Audio_Baud: SwiftProtobuf.Enum { + public enum Audio_Baud: SwiftProtobuf.Enum, Swift.CaseIterable { public typealias RawValue = Int case codec2Default // = 0 case codec23200 // = 1 @@ -655,6 +602,19 @@ public struct ModuleConfig { } } + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [ModuleConfig.AudioConfig.Audio_Baud] = [ + .codec2Default, + .codec23200, + .codec22400, + .codec21600, + .codec21400, + .codec21300, + .codec21200, + .codec2700, + .codec2700B, + ] + } public init() {} @@ -662,7 +622,7 @@ public struct ModuleConfig { /// /// Config for the Paxcounter Module - public struct PaxcounterConfig { + public struct PaxcounterConfig: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -688,7 +648,7 @@ public struct ModuleConfig { /// /// Serial Config - public struct SerialConfig { + public struct SerialConfig: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -731,7 +691,7 @@ public struct ModuleConfig { /// /// TODO: REPLACE - public enum Serial_Baud: SwiftProtobuf.Enum { + public enum Serial_Baud: SwiftProtobuf.Enum, Swift.CaseIterable { public typealias RawValue = Int case baudDefault // = 0 case baud110 // = 1 @@ -799,11 +759,31 @@ public struct ModuleConfig { } } + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [ModuleConfig.SerialConfig.Serial_Baud] = [ + .baudDefault, + .baud110, + .baud300, + .baud600, + .baud1200, + .baud2400, + .baud4800, + .baud9600, + .baud19200, + .baud38400, + .baud57600, + .baud115200, + .baud230400, + .baud460800, + .baud576000, + .baud921600, + ] + } /// /// TODO: REPLACE - public enum Serial_Mode: SwiftProtobuf.Enum { + public enum Serial_Mode: SwiftProtobuf.Enum, Swift.CaseIterable { public typealias RawValue = Int case `default` // = 0 case simple // = 1 @@ -848,6 +828,17 @@ public struct ModuleConfig { } } + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [ModuleConfig.SerialConfig.Serial_Mode] = [ + .default, + .simple, + .proto, + .textmsg, + .nmea, + .caltopo, + .ws85, + ] + } public init() {} @@ -855,7 +846,7 @@ public struct ModuleConfig { /// /// External Notifications Config - public struct ExternalNotificationConfig { + public struct ExternalNotificationConfig: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -938,7 +929,7 @@ public struct ModuleConfig { /// /// Store and Forward Module Config - public struct StoreForwardConfig { + public struct StoreForwardConfig: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -974,7 +965,7 @@ public struct ModuleConfig { /// /// Preferences for the RangeTestModule - public struct RangeTestConfig { + public struct RangeTestConfig: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -999,7 +990,7 @@ public struct ModuleConfig { /// /// Configuration for both device and environment metrics - public struct TelemetryConfig { + public struct TelemetryConfig: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -1068,7 +1059,7 @@ public struct ModuleConfig { /// /// TODO: REPLACE - public struct CannedMessageConfig { + public struct CannedMessageConfig: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -1123,7 +1114,7 @@ public struct ModuleConfig { /// /// TODO: REPLACE - public enum InputEventChar: SwiftProtobuf.Enum { + public enum InputEventChar: SwiftProtobuf.Enum, Swift.CaseIterable { public typealias RawValue = Int /// @@ -1191,6 +1182,18 @@ public struct ModuleConfig { } } + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [ModuleConfig.CannedMessageConfig.InputEventChar] = [ + .none, + .up, + .down, + .left, + .right, + .select, + .back, + .cancel, + ] + } public init() {} @@ -1199,7 +1202,7 @@ public struct ModuleConfig { /// ///Ambient Lighting Module - Settings for control of onboard LEDs to allow users to adjust the brightness levels and respective color levels. ///Initially created for the RAK14001 RGB LED module. - public struct AmbientLightingConfig { + public struct AmbientLightingConfig: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -1232,89 +1235,9 @@ public struct ModuleConfig { public init() {} } -#if swift(>=4.2) - -extension ModuleConfig.DetectionSensorConfig.TriggerType: CaseIterable { - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [ModuleConfig.DetectionSensorConfig.TriggerType] = [ - .logicLow, - .logicHigh, - .fallingEdge, - .risingEdge, - .eitherEdgeActiveLow, - .eitherEdgeActiveHigh, - ] -} - -extension ModuleConfig.AudioConfig.Audio_Baud: CaseIterable { - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [ModuleConfig.AudioConfig.Audio_Baud] = [ - .codec2Default, - .codec23200, - .codec22400, - .codec21600, - .codec21400, - .codec21300, - .codec21200, - .codec2700, - .codec2700B, - ] -} - -extension ModuleConfig.SerialConfig.Serial_Baud: CaseIterable { - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [ModuleConfig.SerialConfig.Serial_Baud] = [ - .baudDefault, - .baud110, - .baud300, - .baud600, - .baud1200, - .baud2400, - .baud4800, - .baud9600, - .baud19200, - .baud38400, - .baud57600, - .baud115200, - .baud230400, - .baud460800, - .baud576000, - .baud921600, - ] -} - -extension ModuleConfig.SerialConfig.Serial_Mode: CaseIterable { - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [ModuleConfig.SerialConfig.Serial_Mode] = [ - .default, - .simple, - .proto, - .textmsg, - .nmea, - .caltopo, - .ws85, - ] -} - -extension ModuleConfig.CannedMessageConfig.InputEventChar: CaseIterable { - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [ModuleConfig.CannedMessageConfig.InputEventChar] = [ - .none, - .up, - .down, - .left, - .right, - .select, - .back, - .cancel, - ] -} - -#endif // swift(>=4.2) - /// /// A GPIO pin definition for remote hardware module -public struct RemoteHardwarePin { +public struct RemoteHardwarePin: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -1336,32 +1259,6 @@ public struct RemoteHardwarePin { public init() {} } -#if swift(>=5.5) && canImport(_Concurrency) -extension RemoteHardwarePinType: @unchecked Sendable {} -extension ModuleConfig: @unchecked Sendable {} -extension ModuleConfig.OneOf_PayloadVariant: @unchecked Sendable {} -extension ModuleConfig.MQTTConfig: @unchecked Sendable {} -extension ModuleConfig.MapReportSettings: @unchecked Sendable {} -extension ModuleConfig.RemoteHardwareConfig: @unchecked Sendable {} -extension ModuleConfig.NeighborInfoConfig: @unchecked Sendable {} -extension ModuleConfig.DetectionSensorConfig: @unchecked Sendable {} -extension ModuleConfig.DetectionSensorConfig.TriggerType: @unchecked Sendable {} -extension ModuleConfig.AudioConfig: @unchecked Sendable {} -extension ModuleConfig.AudioConfig.Audio_Baud: @unchecked Sendable {} -extension ModuleConfig.PaxcounterConfig: @unchecked Sendable {} -extension ModuleConfig.SerialConfig: @unchecked Sendable {} -extension ModuleConfig.SerialConfig.Serial_Baud: @unchecked Sendable {} -extension ModuleConfig.SerialConfig.Serial_Mode: @unchecked Sendable {} -extension ModuleConfig.ExternalNotificationConfig: @unchecked Sendable {} -extension ModuleConfig.StoreForwardConfig: @unchecked Sendable {} -extension ModuleConfig.RangeTestConfig: @unchecked Sendable {} -extension ModuleConfig.TelemetryConfig: @unchecked Sendable {} -extension ModuleConfig.CannedMessageConfig: @unchecked Sendable {} -extension ModuleConfig.CannedMessageConfig.InputEventChar: @unchecked Sendable {} -extension ModuleConfig.AmbientLightingConfig: @unchecked Sendable {} -extension RemoteHardwarePin: @unchecked Sendable {} -#endif // swift(>=5.5) && canImport(_Concurrency) - // MARK: - Code below here is support for the SwiftProtobuf runtime. fileprivate let _protobuf_package = "meshtastic" @@ -1825,6 +1722,7 @@ extension ModuleConfig.NeighborInfoConfig: SwiftProtobuf.Message, SwiftProtobuf. public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 1: .same(proto: "enabled"), 2: .standard(proto: "update_interval"), + 3: .standard(proto: "transmit_over_lora"), ] public mutating func decodeMessage(decoder: inout D) throws { @@ -1835,6 +1733,7 @@ extension ModuleConfig.NeighborInfoConfig: SwiftProtobuf.Message, SwiftProtobuf. switch fieldNumber { case 1: try { try decoder.decodeSingularBoolField(value: &self.enabled) }() case 2: try { try decoder.decodeSingularUInt32Field(value: &self.updateInterval) }() + case 3: try { try decoder.decodeSingularBoolField(value: &self.transmitOverLora) }() default: break } } @@ -1847,12 +1746,16 @@ extension ModuleConfig.NeighborInfoConfig: SwiftProtobuf.Message, SwiftProtobuf. if self.updateInterval != 0 { try visitor.visitSingularUInt32Field(value: self.updateInterval, fieldNumber: 2) } + if self.transmitOverLora != false { + try visitor.visitSingularBoolField(value: self.transmitOverLora, fieldNumber: 3) + } try unknownFields.traverse(visitor: &visitor) } public static func ==(lhs: ModuleConfig.NeighborInfoConfig, rhs: ModuleConfig.NeighborInfoConfig) -> Bool { if lhs.enabled != rhs.enabled {return false} if lhs.updateInterval != rhs.updateInterval {return false} + if lhs.transmitOverLora != rhs.transmitOverLora {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } diff --git a/MeshtasticProtobufs/Sources/meshtastic/mqtt.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/mqtt.pb.swift index efe6cdd5..006fd9c8 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/mqtt.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/mqtt.pb.swift @@ -1,5 +1,6 @@ // DO NOT EDIT. // swift-format-ignore-file +// swiftlint:disable all // // Generated by the Swift generator plugin for the protocol buffer compiler. // Source: meshtastic/mqtt.proto @@ -7,7 +8,6 @@ // For information on using the generated types, please see the documentation: // https://github.com/apple/swift-protobuf/ -import Foundation import SwiftProtobuf // If the compiler emits an error on this type, it is because this file @@ -22,7 +22,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP /// /// This message wraps a MeshPacket with extra metadata about the sender and how it arrived. -public struct ServiceEnvelope { +public struct ServiceEnvelope: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -57,7 +57,7 @@ public struct ServiceEnvelope { /// /// Information about a node intended to be reported unencrypted to a map using MQTT. -public struct MapReport { +public struct MapReport: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -121,11 +121,6 @@ public struct MapReport { public init() {} } -#if swift(>=5.5) && canImport(_Concurrency) -extension ServiceEnvelope: @unchecked Sendable {} -extension MapReport: @unchecked Sendable {} -#endif // swift(>=5.5) && canImport(_Concurrency) - // MARK: - Code below here is support for the SwiftProtobuf runtime. fileprivate let _protobuf_package = "meshtastic" diff --git a/MeshtasticProtobufs/Sources/meshtastic/paxcount.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/paxcount.pb.swift index cf8aa463..e24ed371 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/paxcount.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/paxcount.pb.swift @@ -1,5 +1,6 @@ // DO NOT EDIT. // swift-format-ignore-file +// swiftlint:disable all // // Generated by the Swift generator plugin for the protocol buffer compiler. // Source: meshtastic/paxcount.proto @@ -7,7 +8,6 @@ // For information on using the generated types, please see the documentation: // https://github.com/apple/swift-protobuf/ -import Foundation import SwiftProtobuf // If the compiler emits an error on this type, it is because this file @@ -22,7 +22,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP /// /// TODO: REPLACE -public struct Paxcount { +public struct Paxcount: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -44,10 +44,6 @@ public struct Paxcount { public init() {} } -#if swift(>=5.5) && canImport(_Concurrency) -extension Paxcount: @unchecked Sendable {} -#endif // swift(>=5.5) && canImport(_Concurrency) - // MARK: - Code below here is support for the SwiftProtobuf runtime. fileprivate let _protobuf_package = "meshtastic" diff --git a/MeshtasticProtobufs/Sources/meshtastic/portnums.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/portnums.pb.swift index dd7e036f..79dfd7f1 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/portnums.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/portnums.pb.swift @@ -1,5 +1,6 @@ // DO NOT EDIT. // swift-format-ignore-file +// swiftlint:disable all // // Generated by the Swift generator plugin for the protocol buffer compiler. // Source: meshtastic/portnums.proto @@ -7,7 +8,6 @@ // For information on using the generated types, please see the documentation: // https://github.com/apple/swift-protobuf/ -import Foundation import SwiftProtobuf // If the compiler emits an error on this type, it is because this file @@ -33,7 +33,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP /// Note: This was formerly a Type enum named 'typ' with the same id # /// We have change to this 'portnum' based scheme for specifying app handlers for particular payloads. /// This change is backwards compatible by treating the legacy OPAQUE/CLEAR_TEXT values identically. -public enum PortNum: SwiftProtobuf.Enum { +public enum PortNum: SwiftProtobuf.Enum, Swift.CaseIterable { public typealias RawValue = Int /// @@ -107,6 +107,10 @@ public enum PortNum: SwiftProtobuf.Enum { /// NOTE: This portnum traffic is not sent to the public MQTT starting at firmware version 2.2.9 case detectionSensorApp // = 10 + /// + /// Same as Text Message but used for critical alerts. + case alertApp // = 11 + /// /// Provides a 'ping' service that replies to any packet it receives. /// Also serves as a small example module. @@ -222,6 +226,7 @@ public enum PortNum: SwiftProtobuf.Enum { case 8: self = .waypointApp case 9: self = .audioApp case 10: self = .detectionSensorApp + case 11: self = .alertApp case 32: self = .replyApp case 33: self = .ipTunnelApp case 34: self = .paxcounterApp @@ -256,6 +261,7 @@ public enum PortNum: SwiftProtobuf.Enum { case .waypointApp: return 8 case .audioApp: return 9 case .detectionSensorApp: return 10 + case .alertApp: return 11 case .replyApp: return 32 case .ipTunnelApp: return 33 case .paxcounterApp: return 34 @@ -277,11 +283,6 @@ public enum PortNum: SwiftProtobuf.Enum { } } -} - -#if swift(>=4.2) - -extension PortNum: CaseIterable { // The compiler won't synthesize support with the UNRECOGNIZED case. public static let allCases: [PortNum] = [ .unknownApp, @@ -295,6 +296,7 @@ extension PortNum: CaseIterable { .waypointApp, .audioApp, .detectionSensorApp, + .alertApp, .replyApp, .ipTunnelApp, .paxcounterApp, @@ -313,14 +315,9 @@ extension PortNum: CaseIterable { .atakForwarder, .max, ] + } -#endif // swift(>=4.2) - -#if swift(>=5.5) && canImport(_Concurrency) -extension PortNum: @unchecked Sendable {} -#endif // swift(>=5.5) && canImport(_Concurrency) - // MARK: - Code below here is support for the SwiftProtobuf runtime. extension PortNum: SwiftProtobuf._ProtoNameProviding { @@ -336,6 +333,7 @@ extension PortNum: SwiftProtobuf._ProtoNameProviding { 8: .same(proto: "WAYPOINT_APP"), 9: .same(proto: "AUDIO_APP"), 10: .same(proto: "DETECTION_SENSOR_APP"), + 11: .same(proto: "ALERT_APP"), 32: .same(proto: "REPLY_APP"), 33: .same(proto: "IP_TUNNEL_APP"), 34: .same(proto: "PAXCOUNTER_APP"), diff --git a/MeshtasticProtobufs/Sources/meshtastic/powermon.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/powermon.pb.swift index 5f51e948..58c21701 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/powermon.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/powermon.pb.swift @@ -1,5 +1,6 @@ // DO NOT EDIT. // swift-format-ignore-file +// swiftlint:disable all // // Generated by the Swift generator plugin for the protocol buffer compiler. // Source: meshtastic/powermon.proto @@ -7,7 +8,6 @@ // For information on using the generated types, please see the documentation: // https://github.com/apple/swift-protobuf/ -import Foundation import SwiftProtobuf // If the compiler emits an error on this type, it is because this file @@ -22,7 +22,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP /// Note: There are no 'PowerMon' messages normally in use (PowerMons are sent only as structured logs - slogs). ///But we wrap our State enum in this message to effectively nest a namespace (without our linter yelling at us) -public struct PowerMon { +public struct PowerMon: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -31,7 +31,7 @@ public struct PowerMon { /// Any significant power changing event in meshtastic should be tagged with a powermon state transition. ///If you are making new meshtastic features feel free to add new entries at the end of this definition. - public enum State: SwiftProtobuf.Enum { + public enum State: SwiftProtobuf.Enum, Swift.CaseIterable { public typealias RawValue = Int case none // = 0 case cpuDeepSleep // = 1 @@ -104,37 +104,31 @@ public struct PowerMon { } } + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [PowerMon.State] = [ + .none, + .cpuDeepSleep, + .cpuLightSleep, + .vext1On, + .loraRxon, + .loraTxon, + .loraRxactive, + .btOn, + .ledOn, + .screenOn, + .screenDrawing, + .wifiOn, + .gpsActive, + ] + } public init() {} } -#if swift(>=4.2) - -extension PowerMon.State: CaseIterable { - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [PowerMon.State] = [ - .none, - .cpuDeepSleep, - .cpuLightSleep, - .vext1On, - .loraRxon, - .loraTxon, - .loraRxactive, - .btOn, - .ledOn, - .screenOn, - .screenDrawing, - .wifiOn, - .gpsActive, - ] -} - -#endif // swift(>=4.2) - /// /// PowerStress testing support via the C++ PowerStress module -public struct PowerStressMessage { +public struct PowerStressMessage: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -151,7 +145,7 @@ public struct PowerStressMessage { /// What operation would we like the UUT to perform. ///note: senders should probably set want_response in their request packets, so that they can know when the state ///machine has started processing their request - public enum Opcode: SwiftProtobuf.Enum { + public enum Opcode: SwiftProtobuf.Enum, Swift.CaseIterable { public typealias RawValue = Int /// @@ -272,48 +266,35 @@ public struct PowerStressMessage { } } + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [PowerStressMessage.Opcode] = [ + .unset, + .printInfo, + .forceQuiet, + .endQuiet, + .screenOn, + .screenOff, + .cpuIdle, + .cpuDeepsleep, + .cpuFullon, + .ledOn, + .ledOff, + .loraOff, + .loraTx, + .loraRx, + .btOff, + .btOn, + .wifiOff, + .wifiOn, + .gpsOff, + .gpsOn, + ] + } public init() {} } -#if swift(>=4.2) - -extension PowerStressMessage.Opcode: CaseIterable { - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [PowerStressMessage.Opcode] = [ - .unset, - .printInfo, - .forceQuiet, - .endQuiet, - .screenOn, - .screenOff, - .cpuIdle, - .cpuDeepsleep, - .cpuFullon, - .ledOn, - .ledOff, - .loraOff, - .loraTx, - .loraRx, - .btOff, - .btOn, - .wifiOff, - .wifiOn, - .gpsOff, - .gpsOn, - ] -} - -#endif // swift(>=4.2) - -#if swift(>=5.5) && canImport(_Concurrency) -extension PowerMon: @unchecked Sendable {} -extension PowerMon.State: @unchecked Sendable {} -extension PowerStressMessage: @unchecked Sendable {} -extension PowerStressMessage.Opcode: @unchecked Sendable {} -#endif // swift(>=5.5) && canImport(_Concurrency) - // MARK: - Code below here is support for the SwiftProtobuf runtime. fileprivate let _protobuf_package = "meshtastic" @@ -323,8 +304,8 @@ extension PowerMon: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB public static let _protobuf_nameMap = SwiftProtobuf._NameMap() public mutating func decodeMessage(decoder: inout D) throws { - while let _ = try decoder.nextFieldNumber() { - } + // Load everything into unknown fields + while try decoder.nextFieldNumber() != nil {} } public func traverse(visitor: inout V) throws { @@ -379,7 +360,7 @@ extension PowerStressMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImple if self.cmd != .unset { try visitor.visitSingularEnumField(value: self.cmd, fieldNumber: 1) } - if self.numSeconds != 0 { + if self.numSeconds.bitPattern != 0 { try visitor.visitSingularFloatField(value: self.numSeconds, fieldNumber: 2) } try unknownFields.traverse(visitor: &visitor) diff --git a/MeshtasticProtobufs/Sources/meshtastic/remote_hardware.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/remote_hardware.pb.swift index ac6eeb26..d23dc07b 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/remote_hardware.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/remote_hardware.pb.swift @@ -1,5 +1,6 @@ // DO NOT EDIT. // swift-format-ignore-file +// swiftlint:disable all // // Generated by the Swift generator plugin for the protocol buffer compiler. // Source: meshtastic/remote_hardware.proto @@ -7,7 +8,6 @@ // For information on using the generated types, please see the documentation: // https://github.com/apple/swift-protobuf/ -import Foundation import SwiftProtobuf // If the compiler emits an error on this type, it is because this file @@ -30,7 +30,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP /// because no security yet (beyond the channel mechanism). /// It should be off by default and then protected based on some TBD mechanism /// (a special channel once multichannel support is included?) -public struct HardwareMessage { +public struct HardwareMessage: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -52,7 +52,7 @@ public struct HardwareMessage { /// /// TODO: REPLACE - public enum TypeEnum: SwiftProtobuf.Enum { + public enum TypeEnum: SwiftProtobuf.Enum, Swift.CaseIterable { public typealias RawValue = Int /// @@ -110,32 +110,21 @@ public struct HardwareMessage { } } + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [HardwareMessage.TypeEnum] = [ + .unset, + .writeGpios, + .watchGpios, + .gpiosChanged, + .readGpios, + .readGpiosReply, + ] + } public init() {} } -#if swift(>=4.2) - -extension HardwareMessage.TypeEnum: CaseIterable { - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [HardwareMessage.TypeEnum] = [ - .unset, - .writeGpios, - .watchGpios, - .gpiosChanged, - .readGpios, - .readGpiosReply, - ] -} - -#endif // swift(>=4.2) - -#if swift(>=5.5) && canImport(_Concurrency) -extension HardwareMessage: @unchecked Sendable {} -extension HardwareMessage.TypeEnum: @unchecked Sendable {} -#endif // swift(>=5.5) && canImport(_Concurrency) - // MARK: - Code below here is support for the SwiftProtobuf runtime. fileprivate let _protobuf_package = "meshtastic" diff --git a/MeshtasticProtobufs/Sources/meshtastic/rtttl.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/rtttl.pb.swift index 6fdf3208..38d0c880 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/rtttl.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/rtttl.pb.swift @@ -1,5 +1,6 @@ // DO NOT EDIT. // swift-format-ignore-file +// swiftlint:disable all // // Generated by the Swift generator plugin for the protocol buffer compiler. // Source: meshtastic/rtttl.proto @@ -7,7 +8,6 @@ // For information on using the generated types, please see the documentation: // https://github.com/apple/swift-protobuf/ -import Foundation import SwiftProtobuf // If the compiler emits an error on this type, it is because this file @@ -22,7 +22,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP /// /// Canned message module configuration. -public struct RTTTLConfig { +public struct RTTTLConfig: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -36,10 +36,6 @@ public struct RTTTLConfig { public init() {} } -#if swift(>=5.5) && canImport(_Concurrency) -extension RTTTLConfig: @unchecked Sendable {} -#endif // swift(>=5.5) && canImport(_Concurrency) - // MARK: - Code below here is support for the SwiftProtobuf runtime. fileprivate let _protobuf_package = "meshtastic" diff --git a/MeshtasticProtobufs/Sources/meshtastic/storeforward.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/storeforward.pb.swift index 54efa77b..deb96569 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/storeforward.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/storeforward.pb.swift @@ -1,5 +1,6 @@ // DO NOT EDIT. // swift-format-ignore-file +// swiftlint:disable all // // Generated by the Swift generator plugin for the protocol buffer compiler. // Source: meshtastic/storeforward.proto @@ -22,7 +23,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP /// /// TODO: REPLACE -public struct StoreAndForward { +public struct StoreAndForward: @unchecked Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -79,7 +80,7 @@ public struct StoreAndForward { /// /// TODO: REPLACE - public enum OneOf_Variant: Equatable { + public enum OneOf_Variant: Equatable, @unchecked Sendable { /// /// TODO: REPLACE case stats(StoreAndForward.Statistics) @@ -93,38 +94,12 @@ public struct StoreAndForward { /// Text from history message. case text(Data) - #if !swift(>=4.1) - public static func ==(lhs: StoreAndForward.OneOf_Variant, rhs: StoreAndForward.OneOf_Variant) -> Bool { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch (lhs, rhs) { - case (.stats, .stats): return { - guard case .stats(let l) = lhs, case .stats(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.history, .history): return { - guard case .history(let l) = lhs, case .history(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.heartbeat, .heartbeat): return { - guard case .heartbeat(let l) = lhs, case .heartbeat(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.text, .text): return { - guard case .text(let l) = lhs, case .text(let r) = rhs else { preconditionFailure() } - return l == r - }() - default: return false - } - } - #endif } /// /// 001 - 063 = From Router /// 064 - 127 = From Client - public enum RequestResponse: SwiftProtobuf.Enum { + public enum RequestResponse: SwiftProtobuf.Enum, Swift.CaseIterable { public typealias RawValue = Int /// @@ -242,11 +217,31 @@ public struct StoreAndForward { } } + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [StoreAndForward.RequestResponse] = [ + .unset, + .routerError, + .routerHeartbeat, + .routerPing, + .routerPong, + .routerBusy, + .routerHistory, + .routerStats, + .routerTextDirect, + .routerTextBroadcast, + .clientError, + .clientHistory, + .clientStats, + .clientPing, + .clientPong, + .clientAbort, + ] + } /// /// TODO: REPLACE - public struct Statistics { + public struct Statistics: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -294,7 +289,7 @@ public struct StoreAndForward { /// /// TODO: REPLACE - public struct History { + public struct History: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -319,7 +314,7 @@ public struct StoreAndForward { /// /// TODO: REPLACE - public struct Heartbeat { + public struct Heartbeat: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -340,41 +335,6 @@ public struct StoreAndForward { public init() {} } -#if swift(>=4.2) - -extension StoreAndForward.RequestResponse: CaseIterable { - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [StoreAndForward.RequestResponse] = [ - .unset, - .routerError, - .routerHeartbeat, - .routerPing, - .routerPong, - .routerBusy, - .routerHistory, - .routerStats, - .routerTextDirect, - .routerTextBroadcast, - .clientError, - .clientHistory, - .clientStats, - .clientPing, - .clientPong, - .clientAbort, - ] -} - -#endif // swift(>=4.2) - -#if swift(>=5.5) && canImport(_Concurrency) -extension StoreAndForward: @unchecked Sendable {} -extension StoreAndForward.OneOf_Variant: @unchecked Sendable {} -extension StoreAndForward.RequestResponse: @unchecked Sendable {} -extension StoreAndForward.Statistics: @unchecked Sendable {} -extension StoreAndForward.History: @unchecked Sendable {} -extension StoreAndForward.Heartbeat: @unchecked Sendable {} -#endif // swift(>=5.5) && canImport(_Concurrency) - // MARK: - Code below here is support for the SwiftProtobuf runtime. fileprivate let _protobuf_package = "meshtastic" diff --git a/MeshtasticProtobufs/Sources/meshtastic/telemetry.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/telemetry.pb.swift index ec5faaa4..737ebf95 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/telemetry.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/telemetry.pb.swift @@ -1,5 +1,6 @@ // DO NOT EDIT. // swift-format-ignore-file +// swiftlint:disable all // // Generated by the Swift generator plugin for the protocol buffer compiler. // Source: meshtastic/telemetry.proto @@ -7,7 +8,6 @@ // For information on using the generated types, please see the documentation: // https://github.com/apple/swift-protobuf/ -import Foundation import SwiftProtobuf // If the compiler emits an error on this type, it is because this file @@ -22,7 +22,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP /// /// Supported I2C Sensors for telemetry in Meshtastic -public enum TelemetrySensorType: SwiftProtobuf.Enum { +public enum TelemetrySensorType: SwiftProtobuf.Enum, Swift.CaseIterable { public typealias RawValue = Int /// @@ -150,8 +150,20 @@ public enum TelemetrySensorType: SwiftProtobuf.Enum { case max30102 // = 30 /// - /// MLX90614 non-contact IR temperature sensor. + /// MLX90614 non-contact IR temperature sensor case mlx90614 // = 31 + + /// + /// SCD40/SCD41 CO2, humidity, temperature sensor + case scd4X // = 32 + + /// + /// ClimateGuard RadSens, radiation, Geiger-Muller Tube + case radsens // = 33 + + /// + /// High accuracy current and voltage + case ina226 // = 34 case UNRECOGNIZED(Int) public init() { @@ -192,6 +204,9 @@ public enum TelemetrySensorType: SwiftProtobuf.Enum { case 29: self = .customSensor case 30: self = .max30102 case 31: self = .mlx90614 + case 32: self = .scd4X + case 33: self = .radsens + case 34: self = .ina226 default: self = .UNRECOGNIZED(rawValue) } } @@ -230,15 +245,13 @@ public enum TelemetrySensorType: SwiftProtobuf.Enum { case .customSensor: return 29 case .max30102: return 30 case .mlx90614: return 31 + case .scd4X: return 32 + case .radsens: return 33 + case .ina226: return 34 case .UNRECOGNIZED(let i): return i } } -} - -#if swift(>=4.2) - -extension TelemetrySensorType: CaseIterable { // The compiler won't synthesize support with the UNRECOGNIZED case. public static let allCases: [TelemetrySensorType] = [ .sensorUnset, @@ -273,14 +286,16 @@ extension TelemetrySensorType: CaseIterable { .customSensor, .max30102, .mlx90614, + .scd4X, + .radsens, + .ina226, ] -} -#endif // swift(>=4.2) +} /// /// Key native device metrics such as battery level -public struct DeviceMetrics { +public struct DeviceMetrics: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -353,7 +368,7 @@ public struct DeviceMetrics { /// /// Weather station or other environmental metrics -public struct EnvironmentMetrics { +public struct EnvironmentMetrics: @unchecked Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -547,6 +562,17 @@ public struct EnvironmentMetrics { /// Clears the value of `windLull`. Subsequent reads from it will return its default value. public mutating func clearWindLull() {_uniqueStorage()._windLull = nil} + /// + /// Radiation in µR/h + public var radiation: Float { + get {return _storage._radiation ?? 0} + set {_uniqueStorage()._radiation = newValue} + } + /// Returns true if `radiation` has been explicitly set. + public var hasRadiation: Bool {return _storage._radiation != nil} + /// Clears the value of `radiation`. Subsequent reads from it will return its default value. + public mutating func clearRadiation() {_uniqueStorage()._radiation = nil} + public var unknownFields = SwiftProtobuf.UnknownStorage() public init() {} @@ -556,7 +582,7 @@ public struct EnvironmentMetrics { /// /// Power Metrics (voltage / current / etc) -public struct PowerMetrics { +public struct PowerMetrics: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -641,7 +667,7 @@ public struct PowerMetrics { /// /// Air quality metrics -public struct AirQualityMetrics { +public struct AirQualityMetrics: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -778,6 +804,17 @@ public struct AirQualityMetrics { /// Clears the value of `particles100Um`. Subsequent reads from it will return its default value. public mutating func clearParticles100Um() {self._particles100Um = nil} + /// + /// 10.0um Particle Count + public var co2: UInt32 { + get {return _co2 ?? 0} + set {_co2 = newValue} + } + /// Returns true if `co2` has been explicitly set. + public var hasCo2: Bool {return self._co2 != nil} + /// Clears the value of `co2`. Subsequent reads from it will return its default value. + public mutating func clearCo2() {self._co2 = nil} + public var unknownFields = SwiftProtobuf.UnknownStorage() public init() {} @@ -794,11 +831,12 @@ public struct AirQualityMetrics { fileprivate var _particles25Um: UInt32? = nil fileprivate var _particles50Um: UInt32? = nil fileprivate var _particles100Um: UInt32? = nil + fileprivate var _co2: UInt32? = nil } /// /// Local device mesh statistics -public struct LocalStats { +public struct LocalStats: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -856,7 +894,7 @@ public struct LocalStats { /// /// Health telemetry metrics -public struct HealthMetrics { +public struct HealthMetrics: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -905,7 +943,7 @@ public struct HealthMetrics { /// /// Types of Measurements the telemetry module is equipped to handle -public struct Telemetry { +public struct Telemetry: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -978,7 +1016,7 @@ public struct Telemetry { public var unknownFields = SwiftProtobuf.UnknownStorage() - public enum OneOf_Variant: Equatable { + public enum OneOf_Variant: Equatable, Sendable { /// /// Key native device metrics such as battery level case deviceMetrics(DeviceMetrics) @@ -998,40 +1036,6 @@ public struct Telemetry { /// Health telemetry metrics case healthMetrics(HealthMetrics) - #if !swift(>=4.1) - public static func ==(lhs: Telemetry.OneOf_Variant, rhs: Telemetry.OneOf_Variant) -> Bool { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch (lhs, rhs) { - case (.deviceMetrics, .deviceMetrics): return { - guard case .deviceMetrics(let l) = lhs, case .deviceMetrics(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.environmentMetrics, .environmentMetrics): return { - guard case .environmentMetrics(let l) = lhs, case .environmentMetrics(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.airQualityMetrics, .airQualityMetrics): return { - guard case .airQualityMetrics(let l) = lhs, case .airQualityMetrics(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.powerMetrics, .powerMetrics): return { - guard case .powerMetrics(let l) = lhs, case .powerMetrics(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.localStats, .localStats): return { - guard case .localStats(let l) = lhs, case .localStats(let r) = rhs else { preconditionFailure() } - return l == r - }() - case (.healthMetrics, .healthMetrics): return { - guard case .healthMetrics(let l) = lhs, case .healthMetrics(let r) = rhs else { preconditionFailure() } - return l == r - }() - default: return false - } - } - #endif } public init() {} @@ -1039,7 +1043,7 @@ public struct Telemetry { /// /// NAU7802 Telemetry configuration, for saving to flash -public struct Nau7802Config { +public struct Nau7802Config: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -1057,19 +1061,6 @@ public struct Nau7802Config { public init() {} } -#if swift(>=5.5) && canImport(_Concurrency) -extension TelemetrySensorType: @unchecked Sendable {} -extension DeviceMetrics: @unchecked Sendable {} -extension EnvironmentMetrics: @unchecked Sendable {} -extension PowerMetrics: @unchecked Sendable {} -extension AirQualityMetrics: @unchecked Sendable {} -extension LocalStats: @unchecked Sendable {} -extension HealthMetrics: @unchecked Sendable {} -extension Telemetry: @unchecked Sendable {} -extension Telemetry.OneOf_Variant: @unchecked Sendable {} -extension Nau7802Config: @unchecked Sendable {} -#endif // swift(>=5.5) && canImport(_Concurrency) - // MARK: - Code below here is support for the SwiftProtobuf runtime. fileprivate let _protobuf_package = "meshtastic" @@ -1108,6 +1099,9 @@ extension TelemetrySensorType: SwiftProtobuf._ProtoNameProviding { 29: .same(proto: "CUSTOM_SENSOR"), 30: .same(proto: "MAX30102"), 31: .same(proto: "MLX90614"), + 32: .same(proto: "SCD4X"), + 33: .same(proto: "RADSENS"), + 34: .same(proto: "INA226"), ] } @@ -1191,6 +1185,7 @@ extension EnvironmentMetrics: SwiftProtobuf.Message, SwiftProtobuf._MessageImple 15: .same(proto: "weight"), 16: .standard(proto: "wind_gust"), 17: .standard(proto: "wind_lull"), + 18: .same(proto: "radiation"), ] fileprivate class _StorageClass { @@ -1211,6 +1206,7 @@ extension EnvironmentMetrics: SwiftProtobuf.Message, SwiftProtobuf._MessageImple var _weight: Float? = nil var _windGust: Float? = nil var _windLull: Float? = nil + var _radiation: Float? = nil #if swift(>=5.10) // This property is used as the initial default value for new instances of the type. @@ -1242,6 +1238,7 @@ extension EnvironmentMetrics: SwiftProtobuf.Message, SwiftProtobuf._MessageImple _weight = source._weight _windGust = source._windGust _windLull = source._windLull + _radiation = source._radiation } } @@ -1277,6 +1274,7 @@ extension EnvironmentMetrics: SwiftProtobuf.Message, SwiftProtobuf._MessageImple case 15: try { try decoder.decodeSingularFloatField(value: &_storage._weight) }() case 16: try { try decoder.decodeSingularFloatField(value: &_storage._windGust) }() case 17: try { try decoder.decodeSingularFloatField(value: &_storage._windLull) }() + case 18: try { try decoder.decodeSingularFloatField(value: &_storage._radiation) }() default: break } } @@ -1340,6 +1338,9 @@ extension EnvironmentMetrics: SwiftProtobuf.Message, SwiftProtobuf._MessageImple try { if let v = _storage._windLull { try visitor.visitSingularFloatField(value: v, fieldNumber: 17) } }() + try { if let v = _storage._radiation { + try visitor.visitSingularFloatField(value: v, fieldNumber: 18) + } }() } try unknownFields.traverse(visitor: &visitor) } @@ -1366,6 +1367,7 @@ extension EnvironmentMetrics: SwiftProtobuf.Message, SwiftProtobuf._MessageImple if _storage._weight != rhs_storage._weight {return false} if _storage._windGust != rhs_storage._windGust {return false} if _storage._windLull != rhs_storage._windLull {return false} + if _storage._radiation != rhs_storage._radiation {return false} return true } if !storagesAreEqual {return false} @@ -1456,6 +1458,7 @@ extension AirQualityMetrics: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem 10: .standard(proto: "particles_25um"), 11: .standard(proto: "particles_50um"), 12: .standard(proto: "particles_100um"), + 13: .same(proto: "co2"), ] public mutating func decodeMessage(decoder: inout D) throws { @@ -1476,6 +1479,7 @@ extension AirQualityMetrics: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem case 10: try { try decoder.decodeSingularUInt32Field(value: &self._particles25Um) }() case 11: try { try decoder.decodeSingularUInt32Field(value: &self._particles50Um) }() case 12: try { try decoder.decodeSingularUInt32Field(value: &self._particles100Um) }() + case 13: try { try decoder.decodeSingularUInt32Field(value: &self._co2) }() default: break } } @@ -1522,6 +1526,9 @@ extension AirQualityMetrics: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem try { if let v = self._particles100Um { try visitor.visitSingularUInt32Field(value: v, fieldNumber: 12) } }() + try { if let v = self._co2 { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 13) + } }() try unknownFields.traverse(visitor: &visitor) } @@ -1538,6 +1545,7 @@ extension AirQualityMetrics: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem if lhs._particles25Um != rhs._particles25Um {return false} if lhs._particles50Um != rhs._particles50Um {return false} if lhs._particles100Um != rhs._particles100Um {return false} + if lhs._co2 != rhs._co2 {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } @@ -1585,10 +1593,10 @@ extension LocalStats: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio if self.uptimeSeconds != 0 { try visitor.visitSingularUInt32Field(value: self.uptimeSeconds, fieldNumber: 1) } - if self.channelUtilization != 0 { + if self.channelUtilization.bitPattern != 0 { try visitor.visitSingularFloatField(value: self.channelUtilization, fieldNumber: 2) } - if self.airUtilTx != 0 { + if self.airUtilTx.bitPattern != 0 { try visitor.visitSingularFloatField(value: self.airUtilTx, fieldNumber: 3) } if self.numPacketsTx != 0 { @@ -1855,7 +1863,7 @@ extension Nau7802Config: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementa if self.zeroOffset != 0 { try visitor.visitSingularInt32Field(value: self.zeroOffset, fieldNumber: 1) } - if self.calibrationFactor != 0 { + if self.calibrationFactor.bitPattern != 0 { try visitor.visitSingularFloatField(value: self.calibrationFactor, fieldNumber: 2) } try unknownFields.traverse(visitor: &visitor) diff --git a/MeshtasticProtobufs/Sources/meshtastic/xmodem.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/xmodem.pb.swift index 1f41fe0b..46907a58 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/xmodem.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/xmodem.pb.swift @@ -1,5 +1,6 @@ // DO NOT EDIT. // swift-format-ignore-file +// swiftlint:disable all // // Generated by the Swift generator plugin for the protocol buffer compiler. // Source: meshtastic/xmodem.proto @@ -20,7 +21,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP typealias Version = _2 } -public struct XModem { +public struct XModem: @unchecked Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -35,7 +36,7 @@ public struct XModem { public var unknownFields = SwiftProtobuf.UnknownStorage() - public enum Control: SwiftProtobuf.Enum { + public enum Control: SwiftProtobuf.Enum, Swift.CaseIterable { public typealias RawValue = Int case nul // = 0 case soh // = 1 @@ -79,34 +80,23 @@ public struct XModem { } } + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [XModem.Control] = [ + .nul, + .soh, + .stx, + .eot, + .ack, + .nak, + .can, + .ctrlz, + ] + } public init() {} } -#if swift(>=4.2) - -extension XModem.Control: CaseIterable { - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [XModem.Control] = [ - .nul, - .soh, - .stx, - .eot, - .ack, - .nak, - .can, - .ctrlz, - ] -} - -#endif // swift(>=4.2) - -#if swift(>=5.5) && canImport(_Concurrency) -extension XModem: @unchecked Sendable {} -extension XModem.Control: @unchecked Sendable {} -#endif // swift(>=5.5) && canImport(_Concurrency) - // MARK: - Code below here is support for the SwiftProtobuf runtime. fileprivate let _protobuf_package = "meshtastic" diff --git a/README.md b/README.md index de9af2a2..bbaf49b0 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ SwiftUI client applications for iOS, iPadOS and macOS. ## Getting Started -This project is currently using **Xcode 15.4**. +This project always uses the latest release version of XCode. 1. Clone the repo. 2. Set up git hooks to automatically lint the project when you commit changes. @@ -24,11 +24,7 @@ open Meshtastic.xcworkspace ### Supported Operating Systems -The last two operating system versions are supported. Currently that is 16 and 17. - -* iOS 16.6+ -* iPadOS 16.6+ -* macOS 13+ +The last two major operating system versions are supported on iOS, iPadOS and macOS. ### Code Standards diff --git a/Widgets/MeshActivityAttributes.swift b/Widgets/MeshActivityAttributes.swift index 876b75de..37376531 100644 --- a/Widgets/MeshActivityAttributes.swift +++ b/Widgets/MeshActivityAttributes.swift @@ -4,7 +4,7 @@ // // Created by Garth Vander Houwen on 3/1/23. // - +#if !targetEnvironment(macCatalyst) #if canImport(ActivityKit) import ActivityKit @@ -36,3 +36,4 @@ struct MeshActivityAttributes: ActivityAttributes { var name: String } #endif +#endif diff --git a/ci_scripts/ci_pre_xcodebuild.sh b/ci_scripts/ci_pre_xcodebuild.sh new file mode 100755 index 00000000..23aec511 --- /dev/null +++ b/ci_scripts/ci_pre_xcodebuild.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +echo "Stage: PRE-Xcode Build is activated .... " + +# Move to the place where the scripts are located. +# This is important because the position of the subsequently mentioned files depend of this origin. +cd $CI_PRIMARY_REPOSITORY_PATH/ci_scripts || exit 1 + +# Write a JSON File containing all the environment variables and secrets. +printf "{\"PUBLIC_MQTT_USERNAME\":\"%s\",\"PUBLIC_MQTT_PASSWORD\":\"%s\"}" "$PUBLIC_MQTT_USERNAME" "$PUBLIC_MQTT_PASSWORD" >> .\ $CI_PRIMARY_REPOSITORY_PATH/SupportingFiles/secrets.json + +echo "Wrote Secrets.json file." + +echo "Stage: PRE-Xcode Build is DONE .... " + +exit 0 diff --git a/protobufs b/protobufs index c9ae7fd4..76f806e1 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit c9ae7fd478bffe5f954b30de6cb140821fe9ff52 +Subproject commit 76f806e1bb1e2a7b157a14fadd095775f63db5e4