mirror of
https://github.com/meshtastic/Meshtastic-Apple.git
synced 2026-04-20 22:13:56 +00:00
Merge pull request #445 from meshtastic/2.2.18_Working_Changes
2.2.18 working changes
This commit is contained in:
commit
238cadff06
54 changed files with 1345 additions and 761 deletions
|
|
@ -28,7 +28,7 @@
|
|||
DD2553572855B02500E55709 /* LoRaConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2553562855B02500E55709 /* LoRaConfig.swift */; };
|
||||
DD2553592855B52700E55709 /* PositionConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2553582855B52700E55709 /* PositionConfig.swift */; };
|
||||
DD2AD8A8296D2DF9001FF0E7 /* MapViewSwiftUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2AD8A7296D2DF9001FF0E7 /* MapViewSwiftUI.swift */; };
|
||||
DD2DC2C029BCD8AB003B383C /* HardwareModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2DC2BF29BCD8AB003B383C /* HardwareModels.swift */; };
|
||||
DD33DB622B3D27C7003E1EA0 /* FirmwareApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD33DB612B3D27C7003E1EA0 /* FirmwareApi.swift */; };
|
||||
DD3501892852FC3B000FC853 /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3501882852FC3B000FC853 /* Settings.swift */; };
|
||||
DD3619152B1EF9F900C41C8C /* LocationsHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3619142B1EF9F900C41C8C /* LocationsHandler.swift */; };
|
||||
DD3CC6B528E33FD100FA9159 /* ShareChannels.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3CC6B428E33FD100FA9159 /* ShareChannels.swift */; };
|
||||
|
|
@ -180,6 +180,7 @@
|
|||
DDF6B2482A9AEBF500BA6931 /* StoreForward.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF6B2472A9AEBF500BA6931 /* StoreForward.swift */; };
|
||||
DDF924CA26FBB953009FE055 /* ConnectedDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF924C926FBB953009FE055 /* ConnectedDevice.swift */; };
|
||||
DDFEB3BB29900C1200EE7472 /* CurrentConditionsCompact.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDFEB3BA29900C1200EE7472 /* CurrentConditionsCompact.swift */; };
|
||||
DDFFA7472B3A7F3C004730DB /* Bundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDFFA7462B3A7F3C004730DB /* Bundle.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
|
|
@ -245,7 +246,8 @@
|
|||
DD295CE92B323ED9002CC4AC /* MeshtasticDataModelV22.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV22.xcdatamodel; sourceTree = "<group>"; };
|
||||
DD2AD8A7296D2DF9001FF0E7 /* MapViewSwiftUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapViewSwiftUI.swift; sourceTree = "<group>"; };
|
||||
DD2CC2E52ABFE04E00EDFDA7 /* MeshtasticDataModelV19.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV19.xcdatamodel; sourceTree = "<group>"; };
|
||||
DD2DC2BF29BCD8AB003B383C /* HardwareModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HardwareModels.swift; sourceTree = "<group>"; };
|
||||
DD33DB602B3D1ECC003E1EA0 /* MeshtasticDataModelV 23.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 23.xcdatamodel"; sourceTree = "<group>"; };
|
||||
DD33DB612B3D27C7003E1EA0 /* FirmwareApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirmwareApi.swift; sourceTree = "<group>"; };
|
||||
DD3501882852FC3B000FC853 /* Settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Settings.swift; sourceTree = "<group>"; };
|
||||
DD3619132B1EE20700C41C8C /* MeshtasticDataModelV21.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV21.xcdatamodel; sourceTree = "<group>"; };
|
||||
DD3619142B1EF9F900C41C8C /* LocationsHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationsHandler.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -421,6 +423,7 @@
|
|||
DDF6B24B2A9C2FC800BA6931 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
DDF924C926FBB953009FE055 /* ConnectedDevice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectedDevice.swift; sourceTree = "<group>"; };
|
||||
DDFEB3BA29900C1200EE7472 /* CurrentConditionsCompact.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentConditionsCompact.swift; sourceTree = "<group>"; };
|
||||
DDFFA7462B3A7F3C004730DB /* Bundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bundle.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
|
|
@ -548,6 +551,7 @@
|
|||
DDE9659B2B1C3B6A00531070 /* RouteRecorder.swift */,
|
||||
DDA0B6B1294CDC55001356EC /* Channels.swift */,
|
||||
DDD6EEAE29BC024700383354 /* Firmware.swift */,
|
||||
DD33DB612B3D27C7003E1EA0 /* FirmwareApi.swift */,
|
||||
DD8169FA271F1F3A00F4AB02 /* MeshLog.swift */,
|
||||
DD86D40B287F401000BAEB7A /* SaveChannelQRCode.swift */,
|
||||
DD3501882852FC3B000FC853 /* Settings.swift */,
|
||||
|
|
@ -661,7 +665,6 @@
|
|||
DD1925B828CDA93900720036 /* SerialConfigEnums.swift */,
|
||||
DD994B68295F88B60013760A /* IntervalEnums.swift */,
|
||||
DD5E5239298EFA5300D21B61 /* TelemetryWeather.swift */,
|
||||
DD2DC2BF29BCD8AB003B383C /* HardwareModels.swift */,
|
||||
);
|
||||
path = Enums;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -905,6 +908,7 @@
|
|||
DDB75A0E2A05920E006ED576 /* FileManager.swift */,
|
||||
DDB75A102A059258006ED576 /* Url.swift */,
|
||||
DD1933772B084F4200771CD5 /* Measurement.swift */,
|
||||
DDFFA7462B3A7F3C004730DB /* Bundle.swift */,
|
||||
);
|
||||
path = Extensions;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -1123,6 +1127,7 @@
|
|||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
DDDB444829F8A9C900EE2349 /* String.swift in Sources */,
|
||||
DDFFA7472B3A7F3C004730DB /* Bundle.swift in Sources */,
|
||||
DD5E520C298EE33B00D21B61 /* portnums.pb.swift in Sources */,
|
||||
DD457188293C7E63000C49FB /* BLESignalStrengthIndicator.swift in Sources */,
|
||||
DDFEB3BB29900C1200EE7472 /* CurrentConditionsCompact.swift in Sources */,
|
||||
|
|
@ -1186,6 +1191,7 @@
|
|||
DDDB444A29F8AA3A00EE2349 /* CLLocationCoordinate2D.swift in Sources */,
|
||||
DD41582628582E9B009B0E59 /* DeviceConfig.swift in Sources */,
|
||||
DD007BAE2AA4E91200F5FA12 /* MyInfoEntityExtension.swift in Sources */,
|
||||
DD33DB622B3D27C7003E1EA0 /* FirmwareApi.swift in Sources */,
|
||||
DD3CC6B528E33FD100FA9159 /* ShareChannels.swift in Sources */,
|
||||
DD1BF2F92776FE2E008C8D2F /* UserMessageList.swift in Sources */,
|
||||
DD5E5207298EE33B00D21B61 /* connection_status.pb.swift in Sources */,
|
||||
|
|
@ -1209,7 +1215,6 @@
|
|||
DD47E3D626F17ED900029299 /* CircleText.swift in Sources */,
|
||||
DDC2E18F26CE25FE0042C5E4 /* ContentView.swift in Sources */,
|
||||
DD2553572855B02500E55709 /* LoRaConfig.swift in Sources */,
|
||||
DD2DC2C029BCD8AB003B383C /* HardwareModels.swift in Sources */,
|
||||
DDB6ABD928B0A4BA00384BA1 /* BluetoothModes.swift in Sources */,
|
||||
DDD9E4E4284B208E003777C5 /* UserEntityExtension.swift in Sources */,
|
||||
DD2553592855B52700E55709 /* PositionConfig.swift in Sources */,
|
||||
|
|
@ -1486,7 +1491,7 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.2.17;
|
||||
MARKETING_VERSION = 2.2.18;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
|
|
@ -1520,7 +1525,7 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.2.17;
|
||||
MARKETING_VERSION = 2.2.18;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
|
|
@ -1642,7 +1647,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.2.17;
|
||||
MARKETING_VERSION = 2.2.18;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
|
|
@ -1675,7 +1680,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.2.17;
|
||||
MARKETING_VERSION = 2.2.18;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
|
|
@ -1786,6 +1791,7 @@
|
|||
DD3CC6BA28E366DF00FA9159 /* Meshtastic.xcdatamodeld */ = {
|
||||
isa = XCVersionGroup;
|
||||
children = (
|
||||
DD33DB602B3D1ECC003E1EA0 /* MeshtasticDataModelV 23.xcdatamodel */,
|
||||
DD295CE92B323ED9002CC4AC /* MeshtasticDataModelV22.xcdatamodel */,
|
||||
DD3619132B1EE20700C41C8C /* MeshtasticDataModelV21.xcdatamodel */,
|
||||
DDAB580B2B0D913500147258 /* MeshtasticDataModelV20.xcdatamodel */,
|
||||
|
|
@ -1809,7 +1815,7 @@
|
|||
DD5D0A9A2931AD6B00F7EA61 /* MeshtasticDataModelV2.xcdatamodel */,
|
||||
DD3CC6BB28E366DF00FA9159 /* MeshtasticDataModel.xcdatamodel */,
|
||||
);
|
||||
currentVersion = DD295CE92B323ED9002CC4AC /* MeshtasticDataModelV22.xcdatamodel */;
|
||||
currentVersion = DD33DB602B3D1ECC003E1EA0 /* MeshtasticDataModelV 23.xcdatamodel */;
|
||||
name = Meshtastic.xcdatamodeld;
|
||||
path = Meshtastic/Meshtastic.xcdatamodeld;
|
||||
sourceTree = "<group>";
|
||||
|
|
|
|||
23
Meshtastic/Assets.xcassets/SOLAR_NODE.imageset/Contents.json
vendored
Normal file
23
Meshtastic/Assets.xcassets/SOLAR_NODE.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "solarnode.jpeg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "solarnode 1.jpeg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "solar_node.jpeg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
Meshtastic/Assets.xcassets/SOLAR_NODE.imageset/solar_node.jpeg
vendored
Normal file
BIN
Meshtastic/Assets.xcassets/SOLAR_NODE.imageset/solar_node.jpeg
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 206 KiB |
BIN
Meshtastic/Assets.xcassets/SOLAR_NODE.imageset/solarnode 1.jpeg
vendored
Normal file
BIN
Meshtastic/Assets.xcassets/SOLAR_NODE.imageset/solarnode 1.jpeg
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 52 KiB |
BIN
Meshtastic/Assets.xcassets/SOLAR_NODE.imageset/solarnode.jpeg
vendored
Normal file
BIN
Meshtastic/Assets.xcassets/SOLAR_NODE.imageset/solarnode.jpeg
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 52 KiB |
|
|
@ -1,334 +0,0 @@
|
|||
//
|
||||
// HardwareModels.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Copyright(c) Garth Vander Houwen 3/11/23.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
enum HardwareModels: String, CaseIterable, Identifiable {
|
||||
|
||||
case UNSET
|
||||
case TLORAV2
|
||||
case TLORAV1
|
||||
case TLORAV211P6
|
||||
case TBEAM
|
||||
case HELTECV20
|
||||
case TBEAMV0P7
|
||||
case TECHO
|
||||
case TLORAV11P3
|
||||
case RAK4631
|
||||
case HELTECV21
|
||||
case HELTECV1
|
||||
case DIYV1
|
||||
case LILYGOTBEAMS3CORE
|
||||
case RAK11200
|
||||
case NANOG1
|
||||
case TLORAV211P8
|
||||
case TLORAT3S3
|
||||
case NANOG1EXPLORER
|
||||
case STATIONG1
|
||||
case M5STACK
|
||||
case HELTECV3
|
||||
case HELTECWSLV3
|
||||
case NANOG2ULTRA
|
||||
case RAK11310
|
||||
case RPIPICO
|
||||
case HELTECWIRELESSTRACKER
|
||||
case HELTECWIRELESSPAPER
|
||||
case TDECK
|
||||
case TWATCHS3
|
||||
|
||||
var id: String { self.rawValue }
|
||||
var description: String {
|
||||
switch self {
|
||||
|
||||
case .UNSET:
|
||||
return "unset".localized
|
||||
case .TLORAV2:
|
||||
return "TLoRa V2"
|
||||
case .TLORAV1:
|
||||
return "TLoRa V1"
|
||||
case .TLORAV211P6:
|
||||
return "TLoRa V2.1.1.6"
|
||||
case .TBEAM:
|
||||
return "TBeam"
|
||||
case .HELTECV20:
|
||||
return "HELTEC V2.0"
|
||||
case .TBEAMV0P7:
|
||||
return "TBeam 0.7"
|
||||
case .TECHO:
|
||||
return "TEcho"
|
||||
case .TLORAV11P3:
|
||||
return "TLORA V1.1.3"
|
||||
case .RAK4631:
|
||||
return "RAK 4631 NRF"
|
||||
case .HELTECV21:
|
||||
return "HELTEC V2.1"
|
||||
case .HELTECV1:
|
||||
return "HELTEC V1"
|
||||
case .DIYV1:
|
||||
return "Hydra 1W DIY"
|
||||
case .LILYGOTBEAMS3CORE:
|
||||
return "TBEAM S3"
|
||||
case .RAK11200:
|
||||
return "RAK 11200 ESP32"
|
||||
case .NANOG1:
|
||||
return "Nano G1"
|
||||
case .TLORAV211P8:
|
||||
return "TLoRa V2.1.1.8"
|
||||
case .TLORAT3S3:
|
||||
return "TLoRa T3 S3"
|
||||
case .NANOG1EXPLORER:
|
||||
return "Nano G1 Explorer"
|
||||
case .STATIONG1:
|
||||
return "Station G1"
|
||||
case .M5STACK:
|
||||
return "M5 Stack"
|
||||
case .HELTECV3:
|
||||
return "Heltec V3"
|
||||
case .HELTECWSLV3:
|
||||
return "Heltec wireless stick lite V3"
|
||||
case .NANOG2ULTRA:
|
||||
return "Nano G2 Ultra"
|
||||
case .RAK11310:
|
||||
return "RAK 11310 Pi Pico"
|
||||
case .RPIPICO:
|
||||
return "Pi Pico"
|
||||
case .HELTECWIRELESSTRACKER:
|
||||
return "Heltec Wireless Tracker"
|
||||
case .HELTECWIRELESSPAPER:
|
||||
return "Heltec Wireless Paper"
|
||||
case .TDECK:
|
||||
return "T-Deck"
|
||||
case .TWATCHS3:
|
||||
return "T-Watch S3"
|
||||
}
|
||||
|
||||
}
|
||||
var firmwareStrings: [String] {
|
||||
switch self {
|
||||
|
||||
case .UNSET:
|
||||
return []
|
||||
case .TLORAV2:
|
||||
return ["firmware-tlora-v2-"]
|
||||
case .TLORAV1:
|
||||
return ["firmware-tlora-v1-"]
|
||||
case .TLORAV211P6:
|
||||
return ["firmware-tlora-v2-1-1.6-"]
|
||||
case .TBEAM:
|
||||
return ["firmware-tbeam-"]
|
||||
case .HELTECV20:
|
||||
return ["firmware-heltec-v2.0-"]
|
||||
case .TBEAMV0P7:
|
||||
return ["firmware-tbeam0.7-"]
|
||||
case .TECHO:
|
||||
return ["firmware-t-echo-"]
|
||||
case .TLORAV11P3:
|
||||
return ["firmware-tlora_v1_3-"]
|
||||
case .RAK4631:
|
||||
return ["firmware-rak4631-", "firmware-rak4631_eink-"]
|
||||
case .HELTECV21:
|
||||
return ["firmware-heltec-v2.1-"]
|
||||
case .HELTECV1:
|
||||
return ["firmware-heltec-v1-"]
|
||||
case .DIYV1:
|
||||
return ["firmware-meshtastic-diy-v1"]
|
||||
case .LILYGOTBEAMS3CORE:
|
||||
return ["firmware-tbeam-s3-core-"]
|
||||
case .RAK11200:
|
||||
return ["firmware-rak11200-"]
|
||||
case .NANOG1:
|
||||
return ["firmware-nano-g1-"]
|
||||
case .TLORAV211P8:
|
||||
return ["firmware-tlora-v2-1-1.8-"]
|
||||
case .TLORAT3S3:
|
||||
return ["firmware-tlora-t3s3-v1-"]
|
||||
case .NANOG1EXPLORER:
|
||||
return ["firmware-nano-g1-explorer-"]
|
||||
case .STATIONG1:
|
||||
return ["firmware-station-g1-"]
|
||||
case .M5STACK:
|
||||
return ["firmware-m5stack-core-", "firmware-m5stack-coreink-"]
|
||||
case .HELTECV3:
|
||||
return ["firmware-heltec-v3-"]
|
||||
case .HELTECWSLV3:
|
||||
return ["firmware-heltec-wsl-v3-"]
|
||||
case .NANOG2ULTRA:
|
||||
return ["firmware-nano-g2-ultra-"]
|
||||
case .RAK11310:
|
||||
return ["firmware-rak11310-"]
|
||||
case .RPIPICO:
|
||||
return ["firmware-pico-"]
|
||||
case .HELTECWIRELESSTRACKER:
|
||||
return ["firmware-heltec-wireless-tracker-"]
|
||||
case .HELTECWIRELESSPAPER:
|
||||
return ["firmware-heltec-wireless-paper-"]
|
||||
case .TDECK:
|
||||
return ["firmware-t-echo-"]
|
||||
case .TWATCHS3:
|
||||
return ["firmware-t-watch-s3-"]
|
||||
}
|
||||
|
||||
}
|
||||
func platform() -> HardwarePlatforms {
|
||||
|
||||
switch self {
|
||||
|
||||
case .UNSET:
|
||||
return HardwarePlatforms.none
|
||||
case .TLORAV2:
|
||||
return HardwarePlatforms.esp32
|
||||
case .TLORAV1:
|
||||
return HardwarePlatforms.esp32
|
||||
case .TLORAV211P6:
|
||||
return HardwarePlatforms.esp32
|
||||
case .TBEAM:
|
||||
return HardwarePlatforms.esp32
|
||||
case .HELTECV20:
|
||||
return HardwarePlatforms.esp32
|
||||
case .TBEAMV0P7:
|
||||
return HardwarePlatforms.esp32
|
||||
case .TECHO:
|
||||
return HardwarePlatforms.nrf52
|
||||
case .TLORAV11P3:
|
||||
return HardwarePlatforms.esp32
|
||||
case .RAK4631:
|
||||
return HardwarePlatforms.nrf52
|
||||
case .HELTECV21:
|
||||
return HardwarePlatforms.esp32
|
||||
case .HELTECV1:
|
||||
return HardwarePlatforms.esp32
|
||||
case .DIYV1:
|
||||
return HardwarePlatforms.esp32
|
||||
case .LILYGOTBEAMS3CORE:
|
||||
return HardwarePlatforms.esp32
|
||||
case .RAK11200:
|
||||
return HardwarePlatforms.esp32
|
||||
case .NANOG1:
|
||||
return HardwarePlatforms.esp32
|
||||
case .TLORAV211P8:
|
||||
return HardwarePlatforms.esp32
|
||||
case .TLORAT3S3:
|
||||
return HardwarePlatforms.esp32
|
||||
case .NANOG1EXPLORER:
|
||||
return HardwarePlatforms.esp32
|
||||
case .STATIONG1:
|
||||
return HardwarePlatforms.esp32
|
||||
case .M5STACK:
|
||||
return HardwarePlatforms.esp32
|
||||
case .HELTECV3:
|
||||
return HardwarePlatforms.esp32
|
||||
case .HELTECWSLV3:
|
||||
return HardwarePlatforms.esp32
|
||||
case .NANOG2ULTRA:
|
||||
return HardwarePlatforms.nrf52
|
||||
case .RAK11310:
|
||||
return HardwarePlatforms.piPico
|
||||
case .RPIPICO:
|
||||
return HardwarePlatforms.piPico
|
||||
case .HELTECWIRELESSTRACKER:
|
||||
return HardwarePlatforms.esp32
|
||||
case .HELTECWIRELESSPAPER:
|
||||
return HardwarePlatforms.esp32
|
||||
case .TDECK:
|
||||
return HardwarePlatforms.esp32
|
||||
case .TWATCHS3:
|
||||
return HardwarePlatforms.esp32
|
||||
}
|
||||
}
|
||||
func protoEnumValue() -> HardwareModel {
|
||||
|
||||
switch self {
|
||||
|
||||
case .UNSET:
|
||||
return HardwareModel.unset
|
||||
case .TLORAV2:
|
||||
return HardwareModel.tloraV2
|
||||
case .TLORAV1:
|
||||
return HardwareModel.tloraV1
|
||||
case .TLORAV211P6:
|
||||
return HardwareModel.tloraV211P6
|
||||
case .TBEAM:
|
||||
return HardwareModel.tbeam
|
||||
case .HELTECV20:
|
||||
return HardwareModel.heltecV20
|
||||
case .TBEAMV0P7:
|
||||
return HardwareModel.tbeamV0P7
|
||||
case .TECHO:
|
||||
return HardwareModel.tEcho
|
||||
case .TLORAV11P3:
|
||||
return HardwareModel.tloraV11P3
|
||||
case .RAK4631:
|
||||
return HardwareModel.rak4631
|
||||
case .HELTECV21:
|
||||
return HardwareModel.heltecV21
|
||||
case .HELTECV1:
|
||||
return HardwareModel.heltecV1
|
||||
case .DIYV1:
|
||||
return HardwareModel.diyV1
|
||||
case .LILYGOTBEAMS3CORE:
|
||||
return HardwareModel.lilygoTbeamS3Core
|
||||
case .RAK11200:
|
||||
return HardwareModel.rak11200
|
||||
case .NANOG1:
|
||||
return HardwareModel.nanoG1
|
||||
case .TLORAV211P8:
|
||||
return HardwareModel.tloraV211P8
|
||||
case .TLORAT3S3:
|
||||
return HardwareModel.tloraT3S3
|
||||
case .NANOG1EXPLORER:
|
||||
return HardwareModel.nanoG1Explorer
|
||||
case .STATIONG1:
|
||||
return HardwareModel.stationG1
|
||||
case .M5STACK:
|
||||
return HardwareModel.m5Stack
|
||||
case .HELTECV3:
|
||||
return HardwareModel.heltecV3
|
||||
case .HELTECWSLV3:
|
||||
return HardwareModel.heltecWslV3
|
||||
case .NANOG2ULTRA:
|
||||
return HardwareModel.nanoG2Ultra
|
||||
case .RAK11310:
|
||||
return HardwareModel.rak11310
|
||||
case .RPIPICO:
|
||||
return HardwareModel.rpiPico
|
||||
case .HELTECWIRELESSTRACKER:
|
||||
return HardwareModel.heltecWirelessTracker
|
||||
case .HELTECWIRELESSPAPER:
|
||||
return HardwareModel.heltecWirelessPaper
|
||||
case .TDECK:
|
||||
return HardwareModel.tDeck
|
||||
case .TWATCHS3:
|
||||
return HardwareModel.tWatchS3
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum HardwarePlatforms: String, CaseIterable, Identifiable {
|
||||
case none
|
||||
case esp32
|
||||
case nrf52
|
||||
case stm32
|
||||
case piPico
|
||||
case linux
|
||||
var id: String { self.rawValue }
|
||||
var description: String {
|
||||
switch self {
|
||||
case .none:
|
||||
return "None"
|
||||
case .esp32:
|
||||
return "Expressif ESP 32"
|
||||
case .nrf52:
|
||||
return "Nordic NRF52"
|
||||
case .stm32:
|
||||
return "ARM STM 32"
|
||||
case .piPico:
|
||||
return "Raspberrry Pi Pico"
|
||||
case .linux:
|
||||
return "Linux"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -111,6 +111,7 @@ enum SerialModeTypes: Int, CaseIterable, Identifiable {
|
|||
case proto = 2
|
||||
case txtmsg = 3
|
||||
case nmea = 4
|
||||
case caltopo = 5
|
||||
|
||||
var id: Int { self.rawValue }
|
||||
var description: String {
|
||||
|
|
@ -125,6 +126,8 @@ enum SerialModeTypes: Int, CaseIterable, Identifiable {
|
|||
return "serial.mode.txtmsg".localized
|
||||
case .nmea:
|
||||
return "serial.mode.nmea".localized
|
||||
case .caltopo:
|
||||
return "serial.mode.caltopo".localized
|
||||
}
|
||||
}
|
||||
func protoEnumValue() -> ModuleConfig.SerialConfig.Serial_Mode {
|
||||
|
|
@ -141,6 +144,8 @@ enum SerialModeTypes: Int, CaseIterable, Identifiable {
|
|||
return ModuleConfig.SerialConfig.Serial_Mode.textmsg
|
||||
case .nmea:
|
||||
return ModuleConfig.SerialConfig.Serial_Mode.nmea
|
||||
case .caltopo:
|
||||
return ModuleConfig.SerialConfig.Serial_Mode.caltopo
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
22
Meshtastic/Extensions/Bundle.swift
Normal file
22
Meshtastic/Extensions/Bundle.swift
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
//
|
||||
// Bundle.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Created by Garth Vander Houwen on 12/25/23.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension Bundle {
|
||||
public var appName: String { getInfo("CFBundleName") }
|
||||
public var displayName: String { getInfo("CFBundleDisplayName") }
|
||||
public var language: String { getInfo("CFBundleDevelopmentRegion") }
|
||||
public var identifier: String { getInfo("CFBundleIdentifier") }
|
||||
public var copyright: String { getInfo("NSHumanReadableCopyright").replacingOccurrences(of: "\\\\n", with: "\n") }
|
||||
|
||||
public var appBuild: String { getInfo("CFBundleVersion") }
|
||||
public var appVersionLong: String { getInfo("CFBundleShortVersionString") }
|
||||
//public var appVersionShort: String { getInfo("CFBundleShortVersion") }
|
||||
|
||||
fileprivate func getInfo(_ str: String) -> String { infoDictionary?[str] as? String ?? "⚠️" }
|
||||
}
|
||||
|
|
@ -532,7 +532,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
tryClearExistingChannels()
|
||||
}
|
||||
// NodeInfo
|
||||
if decodedInfo.nodeInfo.num > 0 && !invalidVersion {
|
||||
if decodedInfo.nodeInfo.num > 0 {// && !invalidVersion {
|
||||
nowKnown = true
|
||||
let nodeInfo = nodeInfoPacket(nodeInfo: decodedInfo.nodeInfo, channel: decodedInfo.packet.channel, context: context!)
|
||||
|
||||
|
|
@ -690,6 +690,8 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
if let neighborInfo = try? NeighborInfo(serializedData: decodedInfo.packet.decoded.payload) {
|
||||
MeshLogger.log("🕸️ MESH PACKET received for Neighbor Info App App UNHANDLED \(neighborInfo)")
|
||||
}
|
||||
case .paxcounterApp:
|
||||
MeshLogger.log("🕸️ MESH PACKET received for PAX Counter App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")")
|
||||
case .UNRECOGNIZED:
|
||||
MeshLogger.log("🕸️ MESH PACKET received for Other App UNHANDLED \(try! decodedInfo.packet.jsonString())")
|
||||
case .max:
|
||||
|
|
@ -757,18 +759,14 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
// Use context to pass the radio name with the timer
|
||||
// Use a RunLoop to prevent the timer from running on the main UI thread
|
||||
if UserDefaults.provideLocation {
|
||||
let interval = UserDefaults.provideLocationInterval > 0 ? UserDefaults.provideLocationInterval : 30
|
||||
if positionTimer != nil {
|
||||
}
|
||||
let interval = UserDefaults.provideLocationInterval >= 10 ? UserDefaults.provideLocationInterval : 30
|
||||
positionTimer = Timer.scheduledTimer(timeInterval: TimeInterval(interval), target: self, selector: #selector(positionTimerFired), userInfo: context, repeats: true)
|
||||
if positionTimer != nil {
|
||||
RunLoop.current.add(positionTimer!, forMode: .common)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
case FROMNUM_UUID:
|
||||
print("🗞️ BLE (Notify) characteristic, value will be read next")
|
||||
|
|
@ -962,7 +960,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
return success
|
||||
}
|
||||
|
||||
public func sendPosition(destNum: Int64, wantResponse: Bool) -> Bool {
|
||||
public func sendPosition(channel: Int32, destNum: Int64, wantResponse: Bool) -> Bool {
|
||||
var success = false
|
||||
let fromNodeNum = connectedPeripheral.num
|
||||
var positionPacket = Position()
|
||||
|
|
@ -1016,6 +1014,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
|
||||
var meshPacket = MeshPacket()
|
||||
meshPacket.to = UInt32(destNum)
|
||||
meshPacket.channel = UInt32(channel)
|
||||
meshPacket.from = UInt32(fromNodeNum)
|
||||
var dataMessage = DataMessage()
|
||||
dataMessage.payload = try! positionPacket.serializedData()
|
||||
|
|
@ -1040,7 +1039,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
if connectedPeripheral != nil {
|
||||
// Send a position out to the mesh if "share location with the mesh" is enabled in settings
|
||||
if UserDefaults.provideLocation {
|
||||
let _ = sendPosition(destNum: connectedPeripheral.num, wantResponse: false)
|
||||
let _ = sendPosition(channel: 0, destNum: connectedPeripheral.num, wantResponse: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,20 +8,23 @@
|
|||
import SwiftUI
|
||||
import CoreLocation
|
||||
|
||||
|
||||
// Shared state that manages the `CLLocationManager` and `CLBackgroundActivitySession`.
|
||||
@available(iOS 17.0, macOS 14.0, *)
|
||||
@MainActor class LocationsHandler: ObservableObject {
|
||||
|
||||
|
||||
static let shared = LocationsHandler() // Create a single, shared instance of the object.
|
||||
private let manager: CLLocationManager
|
||||
private var background: CLBackgroundActivitySession?
|
||||
var locationsArray: [CLLocation]
|
||||
var enableSmartPosition: Bool
|
||||
|
||||
//@Published var lastLocation = CLLocation()
|
||||
@Published var locationsArray: [CLLocation]
|
||||
@Published var isStationary = false
|
||||
@Published var count = 0
|
||||
@Published var isRecording = false
|
||||
@Published var isRecordingPaused = false
|
||||
@Published var recordingStarted: Date?
|
||||
@Published var distanceTraveled = 0.0
|
||||
@Published var elevationGain = 0.0
|
||||
|
||||
@Published
|
||||
var updatesStarted: Bool = UserDefaults.standard.bool(forKey: "liveUpdatesStarted") {
|
||||
|
|
@ -55,7 +58,7 @@ import CoreLocation
|
|||
if !self.updatesStarted { break } // End location updates by breaking out of the loop.
|
||||
if let loc = update.location {
|
||||
self.isStationary = update.isStationary
|
||||
self.count += 1
|
||||
|
||||
var locationAdded: Bool
|
||||
if enableSmartPosition {
|
||||
locationAdded = addLocation(loc)
|
||||
|
|
@ -64,8 +67,8 @@ import CoreLocation
|
|||
locationsArray.append(loc)
|
||||
locationAdded = true
|
||||
}
|
||||
if !locationAdded {
|
||||
//print("Bad Location \(self.count): \(loc)")
|
||||
if locationAdded {
|
||||
self.count += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -95,6 +98,16 @@ import CoreLocation
|
|||
print("Bad Location \(self.count): Horizontal Accuracy: \(location.horizontalAccuracy) \(location)")
|
||||
return false
|
||||
}
|
||||
if isRecording {
|
||||
if let lastLocation = locationsArray.last {
|
||||
let distance = location.distance(from: lastLocation)
|
||||
let gain = location.altitude - lastLocation.altitude
|
||||
distanceTraveled += distance
|
||||
if gain > 0 {
|
||||
elevationGain += gain
|
||||
}
|
||||
}
|
||||
}
|
||||
locationsArray.append(location)
|
||||
return true
|
||||
}
|
||||
|
|
@ -103,7 +116,7 @@ import CoreLocation
|
|||
|
||||
static var satsInView: Int {
|
||||
var sats = 0
|
||||
if let newLocation = shared.locationsArray.last{
|
||||
if let newLocation = shared.locationsArray.last {
|
||||
sats = 1
|
||||
if newLocation.verticalAccuracy > 0 {
|
||||
sats = 4
|
||||
|
|
|
|||
|
|
@ -194,7 +194,6 @@ func deviceMetadataPacket (metadata: DeviceMetadata, fromNum: Int64, context: NS
|
|||
}
|
||||
if fetchedNode.count > 0 {
|
||||
let newMetadata = DeviceMetadataEntity(context: context)
|
||||
newMetadata.firmwareVersion = metadata.firmwareVersion
|
||||
newMetadata.deviceStateVersion = Int32(metadata.deviceStateVersion)
|
||||
newMetadata.canShutdown = metadata.canShutdown
|
||||
newMetadata.hasWifi = metadata.hasWifi_p
|
||||
|
|
@ -290,7 +289,7 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje
|
|||
position.longitudeI = nodeInfo.position.longitudeI
|
||||
position.altitude = nodeInfo.position.altitude
|
||||
position.satsInView = Int32(nodeInfo.position.satsInView)
|
||||
position.speed = Int32(nodeInfo.position.groundSpeed)
|
||||
position.speed = Int32(nodeInfo.position.groundSpeed * UInt32(3.6))
|
||||
position.heading = Int32(nodeInfo.position.groundTrack)
|
||||
position.time = Date(timeIntervalSince1970: TimeInterval(Int64(nodeInfo.position.time)))
|
||||
var newPostions = [PositionEntity]()
|
||||
|
|
|
|||
|
|
@ -3,6 +3,6 @@
|
|||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>_XCCurrentVersionName</key>
|
||||
<string>MeshtasticDataModelV22.xcdatamodel</string>
|
||||
<string>MeshtasticDataModelV 23.xcdatamodel</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,409 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="22522" systemVersion="23D5033f" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
||||
<entity name="AmbientLightingConfigEntity" representedClassName="AmbientLightingConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="blue" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="current" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="green" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="ledState" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="red" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="ambientLightingConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="ambientLightingConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="BluetoothConfigEntity" representedClassName="BluetoothConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="fixedPin" optional="YES" attributeType="Integer 32" defaultValueString="123456" usesScalarValueType="YES"/>
|
||||
<attribute name="mode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="bluetoothConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="bluetoothConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="CannedMessageConfigEntity" representedClassName="CannedMessageConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerEventCcw" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerEventCw" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerEventPress" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerPinA" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerPinB" optional="YES" attributeType="Integer 32" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerPinPress" optional="YES" attributeType="Integer 32" usesScalarValueType="YES"/>
|
||||
<attribute name="messages" optional="YES" attributeType="String" minValueString="0" maxValueString="198"/>
|
||||
<attribute name="rotary1Enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="sendBell" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="updown1Enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<relationship name="cannedMessagesConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="cannedMessageConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="ChannelEntity" representedClassName="ChannelEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="downlinkEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="id" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="index" attributeType="Integer 32" minValueString="0" maxValueString="13" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="mute" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="name" optional="YES" attributeType="String"/>
|
||||
<attribute name="psk" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="role" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="uplinkEnabled" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<relationship name="myInfoChannel" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MyInfoEntity" inverseName="channels" inverseEntity="MyInfoEntity"/>
|
||||
<fetchedProperty name="allPrivateMessages" optional="YES">
|
||||
<fetchRequest name="fetchedPropertyFetchRequest" entity="MessageEntity" predicateString="channel == $FETCH_SOURCE.index && toUser == nil AND isEmoji == false"/>
|
||||
</fetchedProperty>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="index"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="DetectionSensorConfigEntity" representedClassName="DetectionSensorConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="detectionTriggeredHigh" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="minimumBroadcastSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="monitorPin" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="name" optional="YES" attributeType="String"/>
|
||||
<attribute name="sendBell" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="stateBroadcastSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="usePullup" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<relationship name="detectionSensorConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="detectionSensorConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="DeviceConfigEntity" representedClassName="DeviceConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="buttonGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="buzzerGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="debugLogEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="disableTripleClick" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="doubleTapAsButtonPress" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="isManaged" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="nodeInfoBroadcastSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="rebroadcastMode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="role" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="serialEnabled" optional="YES" attributeType="Boolean" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="deviceConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="deviceConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="DeviceMetadataEntity" representedClassName="DeviceMetadataEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="canShutdown" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="deviceStateVersion" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="firmwareVersion" optional="YES" attributeType="String"/>
|
||||
<attribute name="hasBluetooth" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="hasEthernet" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="hasWifi" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="hwModel" optional="YES" attributeType="String"/>
|
||||
<attribute name="positionFlags" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="role" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="metadataNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="metadata" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="DisplayConfigEntity" representedClassName="DisplayConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="compassNorthTop" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="displayMode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="flipScreen" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="gpsFormat" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="headingBold" optional="YES" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<attribute name="oledType" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="screenCarouselInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="screenOnSeconds" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="units" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="wakeOnTapOrMotion" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<relationship name="displayConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="displayConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="ExternalNotificationConfigEntity" representedClassName="ExternalNotificationConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="active" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="alertBell" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="alertBellBuzzer" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="alertBellVibra" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="alertMessage" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="alertMessageBuzzer" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="alertMessageVibra" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="nagTimeout" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="output" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="outputBuzzer" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="outputMilliseconds" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="outputVibra" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="useI2SAsBuzzer" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="usePWM" optional="YES" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<relationship name="externalNotificationConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="externalNotificationConfig" inverseEntity="NodeInfoEntity"/>
|
||||
<fetchedProperty name="fetchedProperty" optional="YES">
|
||||
<fetchRequest name="fetchedPropertyFetchRequest" entity="ExternalNotificationConfigEntity"/>
|
||||
</fetchedProperty>
|
||||
</entity>
|
||||
<entity name="LocationEntity" representedClassName="LocationEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="altitude" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="heading" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="id" optional="YES" attributeType="Integer 32" usesScalarValueType="YES"/>
|
||||
<attribute name="latitudeI" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="longitudeI" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="speed" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="routeLocation" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="RouteEntity" inverseName="locations" inverseEntity="RouteEntity"/>
|
||||
</entity>
|
||||
<entity name="LoRaConfigEntity" representedClassName="LoRaConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="bandwidth" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="channelNum" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="codingRate" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="frequencyOffset" optional="YES" attributeType="Float" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="hopLimit" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="modemPreset" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="overrideDutyCycle" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="overrideFrequency" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="regionCode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="spreadFactor" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="sx126xRxBoostedGain" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="txEnabled" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<attribute name="txPower" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="usePreset" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<relationship name="loRaConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="loRaConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="MessageEntity" representedClassName="MessageEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="ackError" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="ackSNR" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="ackTimestamp" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="admin" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="adminDescription" optional="YES" attributeType="String"/>
|
||||
<attribute name="channel" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="isEmoji" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="messageId" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="messagePayload" optional="YES" attributeType="String" defaultValueString=""/>
|
||||
<attribute name="messagePayloadMarkdown" optional="YES" attributeType="String"/>
|
||||
<attribute name="messageTimestamp" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="portNum" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="read" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="realACK" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="receivedACK" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="receivedTimestamp" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="replyID" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="rssi" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<relationship name="fromUser" optional="YES" maxCount="1" deletionRule="Nullify" ordered="YES" destinationEntity="UserEntity" inverseName="sentMessages" inverseEntity="UserEntity"/>
|
||||
<relationship name="toUser" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="UserEntity" inverseName="receivedMessages" inverseEntity="UserEntity"/>
|
||||
<fetchedProperty name="tapbacks" optional="YES">
|
||||
<fetchRequest name="fetchedPropertyFetchRequest" entity="MessageEntity" predicateString="replyID == $FETCH_SOURCE.messageId AND isEmoji == true"/>
|
||||
</fetchedProperty>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="messageId"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="MQTTConfigEntity" representedClassName="MQTTConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="address" optional="YES" attributeType="String" maxValueString="30"/>
|
||||
<attribute name="enabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="encryptionEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="jsonEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="password" optional="YES" attributeType="String" maxValueString="30"/>
|
||||
<attribute name="proxyToClientEnabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="root" optional="YES" attributeType="String" defaultValueString="msh"/>
|
||||
<attribute name="tlsEnabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="username" optional="YES" attributeType="String" maxValueString="30"/>
|
||||
<relationship name="mqttConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="mqttConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="MyInfoEntity" representedClassName="MyInfoEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="adminIndex" optional="YES" attributeType="Integer 32" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="bleName" optional="YES" attributeType="String"/>
|
||||
<attribute name="minAppVersion" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="myNodeNum" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="peripheralId" optional="YES" attributeType="String"/>
|
||||
<attribute name="rebootCount" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="channels" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="ChannelEntity" inverseName="myInfoChannel" inverseEntity="ChannelEntity"/>
|
||||
<relationship name="myInfoNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="myInfo" inverseEntity="NodeInfoEntity"/>
|
||||
<fetchedProperty name="allMessages" optional="YES">
|
||||
<fetchRequest name="fetchedPropertyFetchRequest" entity="MessageEntity" predicateString="toUser == nil"/>
|
||||
</fetchedProperty>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="myNodeNum"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="NetworkConfigEntity" representedClassName="NetworkConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="dns" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="ethEnabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="gateway" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="ip" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="ntpServer" optional="YES" attributeType="String"/>
|
||||
<attribute name="subnet" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="wifiEnabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="wifiMode" optional="YES" attributeType="Integer 32" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="wifiPsk" optional="YES" attributeType="String" minValueString="0" maxValueString="60"/>
|
||||
<attribute name="wifiSsid" optional="YES" attributeType="String" minValueString="0" maxValueString="30"/>
|
||||
<relationship name="networkConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="networkConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="NodeInfoEntity" representedClassName="NodeInfoEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="bleName" optional="YES" attributeType="String"/>
|
||||
<attribute name="channel" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="detection" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="environment" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="id" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="lastHeard" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="num" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="peripheralId" optional="YES" attributeType="String"/>
|
||||
<attribute name="rssi" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<relationship name="ambientLightingConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="AmbientLightingConfigEntity" inverseName="ambientLightingConfigNode" inverseEntity="AmbientLightingConfigEntity"/>
|
||||
<relationship name="bluetoothConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="BluetoothConfigEntity" inverseName="bluetoothConfigNode" inverseEntity="BluetoothConfigEntity"/>
|
||||
<relationship name="cannedMessageConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="CannedMessageConfigEntity" inverseName="cannedMessagesConfigNode" inverseEntity="CannedMessageConfigEntity"/>
|
||||
<relationship name="detectionSensorConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="DetectionSensorConfigEntity" inverseName="detectionSensorConfigNode" inverseEntity="DetectionSensorConfigEntity"/>
|
||||
<relationship name="deviceConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="DeviceConfigEntity" inverseName="deviceConfigNode" inverseEntity="DeviceConfigEntity"/>
|
||||
<relationship name="displayConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="DisplayConfigEntity" inverseName="displayConfigNode" inverseEntity="DisplayConfigEntity"/>
|
||||
<relationship name="externalNotificationConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="ExternalNotificationConfigEntity" inverseName="externalNotificationConfigNode" inverseEntity="ExternalNotificationConfigEntity"/>
|
||||
<relationship name="loRaConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="LoRaConfigEntity" inverseName="loRaConfigNode" inverseEntity="LoRaConfigEntity"/>
|
||||
<relationship name="metadata" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="DeviceMetadataEntity" inverseName="metadataNode" inverseEntity="DeviceMetadataEntity"/>
|
||||
<relationship name="mqttConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MQTTConfigEntity" inverseName="mqttConfigNode" inverseEntity="MQTTConfigEntity"/>
|
||||
<relationship name="myInfo" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MyInfoEntity" inverseName="myInfoNode" inverseEntity="MyInfoEntity"/>
|
||||
<relationship name="networkConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NetworkConfigEntity" inverseName="networkConfigNode" inverseEntity="NetworkConfigEntity"/>
|
||||
<relationship name="positionConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="PositionConfigEntity" inverseName="positionConfigNode" inverseEntity="PositionConfigEntity"/>
|
||||
<relationship name="positions" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="PositionEntity" inverseName="nodePosition" inverseEntity="PositionEntity"/>
|
||||
<relationship name="rangeTestConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="RangeTestConfigEntity" inverseName="rangeTestConfigNode" inverseEntity="RangeTestConfigEntity"/>
|
||||
<relationship name="rtttlConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="RTTTLConfigEntity" inverseName="rtttlConfigNode" inverseEntity="RTTTLConfigEntity"/>
|
||||
<relationship name="serialConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="SerialConfigEntity" inverseName="serialConfigNode" inverseEntity="SerialConfigEntity"/>
|
||||
<relationship name="storeForwardConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="StoreForwardConfigEntity" inverseName="storeForwardConfigNode" inverseEntity="StoreForwardConfigEntity"/>
|
||||
<relationship name="telemetries" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="TelemetryEntity" inverseName="nodeTelemetry" inverseEntity="TelemetryEntity"/>
|
||||
<relationship name="telemetryConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="TelemetryConfigEntity" inverseName="telemetryConfigNode" inverseEntity="TelemetryConfigEntity"/>
|
||||
<relationship name="traceRoutes" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="TraceRouteEntity" inverseName="node" inverseEntity="TraceRouteEntity"/>
|
||||
<relationship name="user" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="UserEntity" inverseName="userNode" inverseEntity="UserEntity"/>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="num"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="PositionConfigEntity" representedClassName="PositionConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="broadcastSmartMinimumDistance" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="broadcastSmartMinimumIntervalSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="deviceGpsEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="fixedPosition" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="gpsAttemptTime" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="gpsEnGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="gpsUpdateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="positionBroadcastSeconds" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="positionFlags" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="rxGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="smartPositionEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="txGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="positionConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="positionConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="PositionEntity" representedClassName="PositionEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="altitude" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="heading" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="latest" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="latitudeI" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="longitudeI" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="rssi" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="satsInView" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="seqNo" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="speed" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="time" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<relationship name="nodePosition" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="positions" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="RangeTestConfigEntity" representedClassName="RangeTestConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="save" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="sender" optional="YES" attributeType="Integer 32" usesScalarValueType="YES"/>
|
||||
<relationship name="rangeTestConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="rangeTestConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="RouteEntity" representedClassName="RouteEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="color" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="date" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="id" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="name" optional="YES" attributeType="String"/>
|
||||
<attribute name="notes" optional="YES" attributeType="String"/>
|
||||
<relationship name="locations" optional="YES" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="LocationEntity" inverseName="routeLocation" inverseEntity="LocationEntity"/>
|
||||
</entity>
|
||||
<entity name="RTTTLConfigEntity" representedClassName="RTTTLConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="ringtone" optional="YES" attributeType="String" maxValueString="228" defaultValueString=""/>
|
||||
<relationship name="rtttlConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="rtttlConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="SerialConfigEntity" representedClassName="SerialConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="baudRate" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="echo" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="mode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="overrideConsoleSerialPort" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="rxd" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="timeout" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="txd" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="serialConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="serialConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="StoreForwardConfigEntity" representedClassName="StoreForwardConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="enabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="heartbeat" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<attribute name="historyReturnMax" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="historyReturnWindow" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="records" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="storeForwardConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="storeForwardConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="TelemetryConfigEntity" representedClassName="TelemetryConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="deviceUpdateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="environmentDisplayFahrenheit" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="environmentMeasurementEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="environmentScreenEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="environmentUpdateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="telemetryConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="telemetryConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="TelemetryEntity" representedClassName="TelemetryEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="airUtilTx" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="barometricPressure" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="batteryLevel" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="channelUtilization" optional="YES" attributeType="Float" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="current" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="gasResistance" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="metricsType" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="relativeHumidity" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="rssi" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="temperature" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="time" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="voltage" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<relationship name="nodeTelemetry" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="telemetries" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="TraceRouteEntity" representedClassName="TraceRouteEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="altitude" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="hasPositions" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="id" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="latitudeI" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="longitudeI" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="num" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="response" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="route" optional="YES" attributeType="Transformable" customClassName="[UInt32]"/>
|
||||
<attribute name="routeText" optional="YES" attributeType="String"/>
|
||||
<attribute name="time" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<relationship name="hops" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="TraceRouteHopEntity" inverseName="traceRoute" inverseEntity="TraceRouteHopEntity"/>
|
||||
<relationship name="node" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="traceRoutes" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="TraceRouteHopEntity" representedClassName="TraceRouteHopEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="altitude" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="latitudeI" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="longitudeI" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="name" optional="YES" attributeType="String"/>
|
||||
<attribute name="num" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="time" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<relationship name="traceRoute" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="TraceRouteEntity" inverseName="hops" inverseEntity="TraceRouteEntity"/>
|
||||
</entity>
|
||||
<entity name="UserEntity" representedClassName="UserEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="hwModel" attributeType="String"/>
|
||||
<attribute name="isLicensed" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="lastMessage" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="longName" attributeType="String"/>
|
||||
<attribute name="mute" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="num" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="role" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="shortName" attributeType="String"/>
|
||||
<attribute name="userId" attributeType="String"/>
|
||||
<attribute name="vip" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<relationship name="receivedMessages" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="MessageEntity" inverseName="toUser" inverseEntity="MessageEntity"/>
|
||||
<relationship name="sentMessages" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="MessageEntity" inverseName="fromUser" inverseEntity="MessageEntity"/>
|
||||
<relationship name="userNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="user" inverseEntity="NodeInfoEntity"/>
|
||||
<fetchedProperty name="adminMessages" optional="YES">
|
||||
<fetchRequest name="fetchedPropertyFetchRequest" entity="MessageEntity" predicateString="(fromUser.num == $FETCH_SOURCE.num) AND isEmoji == false AND admin = true"/>
|
||||
</fetchedProperty>
|
||||
<fetchedProperty name="allMessages" optional="YES">
|
||||
<fetchRequest name="fetchedPropertyFetchRequest" entity="MessageEntity" predicateString="((toUser.num == $FETCH_SOURCE.num) OR (fromUser.num == $FETCH_SOURCE.num)) AND toUser != nil AND fromUser != nil AND isEmoji == false AND admin = false AND portNum != 10 "/>
|
||||
</fetchedProperty>
|
||||
<fetchedProperty name="detectionSensorMessages" optional="YES">
|
||||
<fetchRequest name="fetchedPropertyFetchRequest" entity="MessageEntity" predicateString="(fromUser.num == $FETCH_SOURCE.num) AND portNum = 10"/>
|
||||
</fetchedProperty>
|
||||
</entity>
|
||||
<entity name="WaypointEntity" representedClassName="WaypointEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="created" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="expire" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="icon" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="id" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="lastUpdated" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="latitudeI" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="locked" attributeType="Integer 64" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="longDescription" optional="YES" attributeType="String" maxValueString="100"/>
|
||||
<attribute name="longitudeI" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="name" attributeType="String" minValueString="1" maxValueString="30"/>
|
||||
</entity>
|
||||
</model>
|
||||
|
|
@ -250,7 +250,7 @@ func upsertPositionPacket (packet: MeshPacket, context: NSManagedObjectContext)
|
|||
position.longitudeI = positionMessage.longitudeI
|
||||
position.altitude = positionMessage.altitude
|
||||
position.satsInView = Int32(positionMessage.satsInView)
|
||||
position.speed = Int32(positionMessage.groundSpeed)
|
||||
position.speed = Int32(positionMessage.groundSpeed * UInt32(3.6))
|
||||
position.heading = Int32(positionMessage.groundTrack)
|
||||
if positionMessage.timestamp != 0 {
|
||||
position.time = Date(timeIntervalSince1970: TimeInterval(Int64(positionMessage.timestamp)))
|
||||
|
|
|
|||
|
|
@ -224,6 +224,17 @@ struct AdminMessage {
|
|||
set {payloadVariant = .getNodeRemoteHardwarePinsResponse(newValue)}
|
||||
}
|
||||
|
||||
///
|
||||
/// Enter (UF2) DFU mode
|
||||
/// Only implemented on NRF52 currently
|
||||
var enterDfuModeRequest: Bool {
|
||||
get {
|
||||
if case .enterDfuModeRequest(let v)? = payloadVariant {return v}
|
||||
return false
|
||||
}
|
||||
set {payloadVariant = .enterDfuModeRequest(newValue)}
|
||||
}
|
||||
|
||||
///
|
||||
/// Set the owner for this node
|
||||
var setOwner: User {
|
||||
|
|
@ -445,6 +456,10 @@ struct AdminMessage {
|
|||
/// Respond with the mesh's nodes with their available gpio pins for RemoteHardware module use
|
||||
case getNodeRemoteHardwarePinsResponse(NodeRemoteHardwarePinsResponse)
|
||||
///
|
||||
/// Enter (UF2) DFU mode
|
||||
/// Only implemented on NRF52 currently
|
||||
case enterDfuModeRequest(Bool)
|
||||
///
|
||||
/// Set the owner for this node
|
||||
case setOwner(User)
|
||||
///
|
||||
|
|
@ -579,6 +594,10 @@ struct AdminMessage {
|
|||
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 (.setOwner, .setOwner): return {
|
||||
guard case .setOwner(let l) = lhs, case .setOwner(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
|
|
@ -763,6 +782,10 @@ struct AdminMessage {
|
|||
///
|
||||
/// TODO: REPLACE
|
||||
case detectionsensorConfig // = 11
|
||||
|
||||
///
|
||||
/// TODO: REPLACE
|
||||
case paxcounterConfig // = 12
|
||||
case UNRECOGNIZED(Int)
|
||||
|
||||
init() {
|
||||
|
|
@ -783,6 +806,7 @@ struct AdminMessage {
|
|||
case 9: self = .neighborinfoConfig
|
||||
case 10: self = .ambientlightingConfig
|
||||
case 11: self = .detectionsensorConfig
|
||||
case 12: self = .paxcounterConfig
|
||||
default: self = .UNRECOGNIZED(rawValue)
|
||||
}
|
||||
}
|
||||
|
|
@ -801,6 +825,7 @@ struct AdminMessage {
|
|||
case .neighborinfoConfig: return 9
|
||||
case .ambientlightingConfig: return 10
|
||||
case .detectionsensorConfig: return 11
|
||||
case .paxcounterConfig: return 12
|
||||
case .UNRECOGNIZED(let i): return i
|
||||
}
|
||||
}
|
||||
|
|
@ -840,6 +865,7 @@ extension AdminMessage.ModuleConfigType: CaseIterable {
|
|||
.neighborinfoConfig,
|
||||
.ambientlightingConfig,
|
||||
.detectionsensorConfig,
|
||||
.paxcounterConfig,
|
||||
]
|
||||
}
|
||||
|
||||
|
|
@ -926,6 +952,7 @@ extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat
|
|||
18: .standard(proto: "set_ham_mode"),
|
||||
19: .standard(proto: "get_node_remote_hardware_pins_request"),
|
||||
20: .standard(proto: "get_node_remote_hardware_pins_response"),
|
||||
21: .standard(proto: "enter_dfu_mode_request"),
|
||||
32: .standard(proto: "set_owner"),
|
||||
33: .standard(proto: "set_channel"),
|
||||
34: .standard(proto: "set_config"),
|
||||
|
|
@ -1141,6 +1168,14 @@ extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat
|
|||
self.payloadVariant = .getNodeRemoteHardwarePinsResponse(v)
|
||||
}
|
||||
}()
|
||||
case 21: try {
|
||||
var v: Bool?
|
||||
try decoder.decodeSingularBoolField(value: &v)
|
||||
if let v = v {
|
||||
if self.payloadVariant != nil {try decoder.handleConflictingOneOf()}
|
||||
self.payloadVariant = .enterDfuModeRequest(v)
|
||||
}
|
||||
}()
|
||||
case 32: try {
|
||||
var v: User?
|
||||
var hadOneofValue = false
|
||||
|
|
@ -1368,6 +1403,10 @@ extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat
|
|||
guard case .getNodeRemoteHardwarePinsResponse(let v)? = self.payloadVariant else { preconditionFailure() }
|
||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 20)
|
||||
}()
|
||||
case .enterDfuModeRequest?: try {
|
||||
guard case .enterDfuModeRequest(let v)? = self.payloadVariant else { preconditionFailure() }
|
||||
try visitor.visitSingularBoolField(value: v, fieldNumber: 21)
|
||||
}()
|
||||
case .setOwner?: try {
|
||||
guard case .setOwner(let v)? = self.payloadVariant else { preconditionFailure() }
|
||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 32)
|
||||
|
|
@ -1466,6 +1505,7 @@ extension AdminMessage.ModuleConfigType: SwiftProtobuf._ProtoNameProviding {
|
|||
9: .same(proto: "NEIGHBORINFO_CONFIG"),
|
||||
10: .same(proto: "AMBIENTLIGHTING_CONFIG"),
|
||||
11: .same(proto: "DETECTIONSENSOR_CONFIG"),
|
||||
12: .same(proto: "PAXCOUNTER_CONFIG"),
|
||||
]
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -387,10 +387,7 @@ struct Config {
|
|||
var gpsUpdateInterval: UInt32 = 0
|
||||
|
||||
///
|
||||
/// How long should we try to get our position during each gps_update_interval attempt? (in seconds)
|
||||
/// Or if zero, use the default of 30 seconds.
|
||||
/// If we don't get a new gps fix in that time, the gps will be put into sleep until the next gps_update_rate
|
||||
/// window.
|
||||
/// Deprecated in favor of using smart / regular broadcast intervals as implicit attempt time
|
||||
var gpsAttemptTime: UInt32 = 0
|
||||
|
||||
///
|
||||
|
|
@ -1115,6 +1112,14 @@ struct Config {
|
|||
///
|
||||
/// Ukraine 868mhz
|
||||
case ua868 // = 15
|
||||
|
||||
///
|
||||
/// Malaysia 433mhz
|
||||
case my433 // = 16
|
||||
|
||||
///
|
||||
/// Malaysia 919mhz
|
||||
case my919 // = 17
|
||||
case UNRECOGNIZED(Int)
|
||||
|
||||
init() {
|
||||
|
|
@ -1139,6 +1144,8 @@ struct Config {
|
|||
case 13: self = .lora24
|
||||
case 14: self = .ua433
|
||||
case 15: self = .ua868
|
||||
case 16: self = .my433
|
||||
case 17: self = .my919
|
||||
default: self = .UNRECOGNIZED(rawValue)
|
||||
}
|
||||
}
|
||||
|
|
@ -1161,6 +1168,8 @@ struct Config {
|
|||
case .lora24: return 13
|
||||
case .ua433: return 14
|
||||
case .ua868: return 15
|
||||
case .my433: return 16
|
||||
case .my919: return 17
|
||||
case .UNRECOGNIZED(let i): return i
|
||||
}
|
||||
}
|
||||
|
|
@ -1420,6 +1429,8 @@ extension Config.LoRaConfig.RegionCode: CaseIterable {
|
|||
.lora24,
|
||||
.ua433,
|
||||
.ua868,
|
||||
.my433,
|
||||
.my919,
|
||||
]
|
||||
}
|
||||
|
||||
|
|
@ -2325,6 +2336,8 @@ extension Config.LoRaConfig.RegionCode: SwiftProtobuf._ProtoNameProviding {
|
|||
13: .same(proto: "LORA_24"),
|
||||
14: .same(proto: "UA_433"),
|
||||
15: .same(proto: "UA_868"),
|
||||
16: .same(proto: "MY_433"),
|
||||
17: .same(proto: "MY_919"),
|
||||
]
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -255,6 +255,17 @@ struct LocalModuleConfig {
|
|||
/// Clears the value of `detectionSensor`. Subsequent reads from it will return its default value.
|
||||
mutating func clearDetectionSensor() {_uniqueStorage()._detectionSensor = nil}
|
||||
|
||||
///
|
||||
/// Paxcounter Config
|
||||
var paxcounter: ModuleConfig.PaxcounterConfig {
|
||||
get {return _storage._paxcounter ?? ModuleConfig.PaxcounterConfig()}
|
||||
set {_uniqueStorage()._paxcounter = newValue}
|
||||
}
|
||||
/// Returns true if `paxcounter` has been explicitly set.
|
||||
var hasPaxcounter: Bool {return _storage._paxcounter != nil}
|
||||
/// Clears the value of `paxcounter`. Subsequent reads from it will return its default value.
|
||||
mutating func clearPaxcounter() {_uniqueStorage()._paxcounter = nil}
|
||||
|
||||
///
|
||||
/// A version integer used to invalidate old save files when we make
|
||||
/// incompatible changes This integer is set at build time and is private to
|
||||
|
|
@ -419,6 +430,7 @@ extension LocalModuleConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem
|
|||
11: .standard(proto: "neighbor_info"),
|
||||
12: .standard(proto: "ambient_lighting"),
|
||||
13: .standard(proto: "detection_sensor"),
|
||||
14: .same(proto: "paxcounter"),
|
||||
8: .same(proto: "version"),
|
||||
]
|
||||
|
||||
|
|
@ -435,6 +447,7 @@ extension LocalModuleConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem
|
|||
var _neighborInfo: ModuleConfig.NeighborInfoConfig? = nil
|
||||
var _ambientLighting: ModuleConfig.AmbientLightingConfig? = nil
|
||||
var _detectionSensor: ModuleConfig.DetectionSensorConfig? = nil
|
||||
var _paxcounter: ModuleConfig.PaxcounterConfig? = nil
|
||||
var _version: UInt32 = 0
|
||||
|
||||
static let defaultInstance = _StorageClass()
|
||||
|
|
@ -454,6 +467,7 @@ extension LocalModuleConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem
|
|||
_neighborInfo = source._neighborInfo
|
||||
_ambientLighting = source._ambientLighting
|
||||
_detectionSensor = source._detectionSensor
|
||||
_paxcounter = source._paxcounter
|
||||
_version = source._version
|
||||
}
|
||||
}
|
||||
|
|
@ -486,6 +500,7 @@ extension LocalModuleConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem
|
|||
case 11: try { try decoder.decodeSingularMessageField(value: &_storage._neighborInfo) }()
|
||||
case 12: try { try decoder.decodeSingularMessageField(value: &_storage._ambientLighting) }()
|
||||
case 13: try { try decoder.decodeSingularMessageField(value: &_storage._detectionSensor) }()
|
||||
case 14: try { try decoder.decodeSingularMessageField(value: &_storage._paxcounter) }()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
|
@ -537,6 +552,9 @@ extension LocalModuleConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem
|
|||
try { if let v = _storage._detectionSensor {
|
||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 13)
|
||||
} }()
|
||||
try { if let v = _storage._paxcounter {
|
||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 14)
|
||||
} }()
|
||||
}
|
||||
try unknownFields.traverse(visitor: &visitor)
|
||||
}
|
||||
|
|
@ -558,6 +576,7 @@ extension LocalModuleConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem
|
|||
if _storage._neighborInfo != rhs_storage._neighborInfo {return false}
|
||||
if _storage._ambientLighting != rhs_storage._ambientLighting {return false}
|
||||
if _storage._detectionSensor != rhs_storage._detectionSensor {return false}
|
||||
if _storage._paxcounter != rhs_storage._paxcounter {return false}
|
||||
if _storage._version != rhs_storage._version {return false}
|
||||
return true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -204,6 +204,16 @@ struct ModuleConfig {
|
|||
set {payloadVariant = .detectionSensor(newValue)}
|
||||
}
|
||||
|
||||
///
|
||||
/// TODO: REPLACE
|
||||
var paxcounter: ModuleConfig.PaxcounterConfig {
|
||||
get {
|
||||
if case .paxcounter(let v)? = payloadVariant {return v}
|
||||
return ModuleConfig.PaxcounterConfig()
|
||||
}
|
||||
set {payloadVariant = .paxcounter(newValue)}
|
||||
}
|
||||
|
||||
var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
///
|
||||
|
|
@ -245,6 +255,9 @@ struct ModuleConfig {
|
|||
///
|
||||
/// TODO: REPLACE
|
||||
case detectionSensor(ModuleConfig.DetectionSensorConfig)
|
||||
///
|
||||
/// TODO: REPLACE
|
||||
case paxcounter(ModuleConfig.PaxcounterConfig)
|
||||
|
||||
#if !swift(>=4.1)
|
||||
static func ==(lhs: ModuleConfig.OneOf_PayloadVariant, rhs: ModuleConfig.OneOf_PayloadVariant) -> Bool {
|
||||
|
|
@ -300,6 +313,10 @@ struct ModuleConfig {
|
|||
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
|
||||
}
|
||||
}
|
||||
|
|
@ -551,6 +568,24 @@ struct ModuleConfig {
|
|||
init() {}
|
||||
}
|
||||
|
||||
///
|
||||
/// Config for the Paxcounter Module
|
||||
struct PaxcounterConfig {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
// methods supported on all messages.
|
||||
|
||||
///
|
||||
/// Enable the Paxcounter Module
|
||||
var enabled: Bool = false
|
||||
|
||||
var paxcounterUpdateInterval: UInt32 = 0
|
||||
|
||||
var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
init() {}
|
||||
}
|
||||
|
||||
///
|
||||
/// Serial Config
|
||||
struct SerialConfig {
|
||||
|
|
@ -1177,6 +1212,7 @@ extension ModuleConfig.NeighborInfoConfig: @unchecked Sendable {}
|
|||
extension ModuleConfig.DetectionSensorConfig: @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 {}
|
||||
|
|
@ -1217,6 +1253,7 @@ extension ModuleConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat
|
|||
10: .standard(proto: "neighbor_info"),
|
||||
11: .standard(proto: "ambient_lighting"),
|
||||
12: .standard(proto: "detection_sensor"),
|
||||
13: .same(proto: "paxcounter"),
|
||||
]
|
||||
|
||||
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||
|
|
@ -1381,6 +1418,19 @@ extension ModuleConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat
|
|||
self.payloadVariant = .detectionSensor(v)
|
||||
}
|
||||
}()
|
||||
case 13: try {
|
||||
var v: ModuleConfig.PaxcounterConfig?
|
||||
var hadOneofValue = false
|
||||
if let current = self.payloadVariant {
|
||||
hadOneofValue = true
|
||||
if case .paxcounter(let m) = current {v = m}
|
||||
}
|
||||
try decoder.decodeSingularMessageField(value: &v)
|
||||
if let v = v {
|
||||
if hadOneofValue {try decoder.handleConflictingOneOf()}
|
||||
self.payloadVariant = .paxcounter(v)
|
||||
}
|
||||
}()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
|
@ -1440,6 +1490,10 @@ extension ModuleConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat
|
|||
guard case .detectionSensor(let v)? = self.payloadVariant else { preconditionFailure() }
|
||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 12)
|
||||
}()
|
||||
case .paxcounter?: try {
|
||||
guard case .paxcounter(let v)? = self.payloadVariant else { preconditionFailure() }
|
||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 13)
|
||||
}()
|
||||
case nil: break
|
||||
}
|
||||
try unknownFields.traverse(visitor: &visitor)
|
||||
|
|
@ -1770,6 +1824,44 @@ extension ModuleConfig.AudioConfig.Audio_Baud: SwiftProtobuf._ProtoNameProviding
|
|||
]
|
||||
}
|
||||
|
||||
extension ModuleConfig.PaxcounterConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
||||
static let protoMessageName: String = ModuleConfig.protoMessageName + ".PaxcounterConfig"
|
||||
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
||||
1: .same(proto: "enabled"),
|
||||
2: .standard(proto: "paxcounter_update_interval"),
|
||||
]
|
||||
|
||||
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(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.enabled) }()
|
||||
case 2: try { try decoder.decodeSingularUInt32Field(value: &self.paxcounterUpdateInterval) }()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
|
||||
if self.enabled != false {
|
||||
try visitor.visitSingularBoolField(value: self.enabled, fieldNumber: 1)
|
||||
}
|
||||
if self.paxcounterUpdateInterval != 0 {
|
||||
try visitor.visitSingularUInt32Field(value: self.paxcounterUpdateInterval, fieldNumber: 2)
|
||||
}
|
||||
try unknownFields.traverse(visitor: &visitor)
|
||||
}
|
||||
|
||||
static func ==(lhs: ModuleConfig.PaxcounterConfig, rhs: ModuleConfig.PaxcounterConfig) -> Bool {
|
||||
if lhs.enabled != rhs.enabled {return false}
|
||||
if lhs.paxcounterUpdateInterval != rhs.paxcounterUpdateInterval {return false}
|
||||
if lhs.unknownFields != rhs.unknownFields {return false}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
extension ModuleConfig.SerialConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
||||
static let protoMessageName: String = ModuleConfig.protoMessageName + ".SerialConfig"
|
||||
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
||||
|
|
|
|||
|
|
@ -118,6 +118,11 @@ enum PortNum: SwiftProtobuf.Enum {
|
|||
/// ENCODING: IP Packet. Handled by the python API, firmware ignores this one and pases on.
|
||||
case ipTunnelApp // = 33
|
||||
|
||||
///
|
||||
/// Paxcounter lib included in the firmware
|
||||
/// ENCODING: protobuf
|
||||
case paxcounterApp // = 34
|
||||
|
||||
///
|
||||
/// Provides a hardware serial interface to send and receive from the Meshtastic network.
|
||||
/// Connect to the RX/TX pins of a device with 38400 8N1. Packets received from the Meshtastic
|
||||
|
|
@ -206,6 +211,7 @@ enum PortNum: SwiftProtobuf.Enum {
|
|||
case 10: self = .detectionSensorApp
|
||||
case 32: self = .replyApp
|
||||
case 33: self = .ipTunnelApp
|
||||
case 34: self = .paxcounterApp
|
||||
case 64: self = .serialApp
|
||||
case 65: self = .storeForwardApp
|
||||
case 66: self = .rangeTestApp
|
||||
|
|
@ -236,6 +242,7 @@ enum PortNum: SwiftProtobuf.Enum {
|
|||
case .detectionSensorApp: return 10
|
||||
case .replyApp: return 32
|
||||
case .ipTunnelApp: return 33
|
||||
case .paxcounterApp: return 34
|
||||
case .serialApp: return 64
|
||||
case .storeForwardApp: return 65
|
||||
case .rangeTestApp: return 66
|
||||
|
|
@ -271,6 +278,7 @@ extension PortNum: CaseIterable {
|
|||
.detectionSensorApp,
|
||||
.replyApp,
|
||||
.ipTunnelApp,
|
||||
.paxcounterApp,
|
||||
.serialApp,
|
||||
.storeForwardApp,
|
||||
.rangeTestApp,
|
||||
|
|
@ -308,6 +316,7 @@ extension PortNum: SwiftProtobuf._ProtoNameProviding {
|
|||
10: .same(proto: "DETECTION_SENSOR_APP"),
|
||||
32: .same(proto: "REPLY_APP"),
|
||||
33: .same(proto: "IP_TUNNEL_APP"),
|
||||
34: .same(proto: "PAXCOUNTER_APP"),
|
||||
64: .same(proto: "SERIAL_APP"),
|
||||
65: .same(proto: "STORE_FORWARD_APP"),
|
||||
66: .same(proto: "RANGE_TEST_APP"),
|
||||
|
|
|
|||
|
|
@ -297,7 +297,9 @@ struct Connect: View {
|
|||
}
|
||||
}
|
||||
.onAppear(perform: {
|
||||
self.bleManager.context = context
|
||||
if self.bleManager.context == nil {
|
||||
self.bleManager.context = context
|
||||
}
|
||||
})
|
||||
}
|
||||
#if canImport(ActivityKit)
|
||||
|
|
|
|||
|
|
@ -373,7 +373,7 @@ struct ChannelMessageList: View {
|
|||
focusedField = nil
|
||||
replyMessageId = 0
|
||||
if sendPositionWithMessage {
|
||||
if bleManager.sendPosition(destNum: Int64(channel.index), wantResponse: false) {
|
||||
if bleManager.sendPosition(channel: Int32(channel.index), destNum: Int64(bleManager.emptyNodeNum), wantResponse: false) {
|
||||
print("Location Sent")
|
||||
}
|
||||
}
|
||||
|
|
@ -390,7 +390,7 @@ struct ChannelMessageList: View {
|
|||
focusedField = nil
|
||||
replyMessageId = 0
|
||||
if sendPositionWithMessage {
|
||||
if bleManager.sendPosition(destNum: Int64(channel.index), wantResponse: false) {
|
||||
if bleManager.sendPosition(channel: Int32(channel.index), destNum: Int64(bleManager.emptyNodeNum), wantResponse: false) {
|
||||
print("Location Sent")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -325,7 +325,7 @@ struct UserMessageList: View {
|
|||
focusedField = nil
|
||||
replyMessageId = 0
|
||||
if sendPositionWithMessage {
|
||||
if bleManager.sendPosition(destNum: user.num, wantResponse: true) {
|
||||
if bleManager.sendPosition(channel: 0, destNum: user.num, wantResponse: true) {
|
||||
print("Location Sent")
|
||||
}
|
||||
}
|
||||
|
|
@ -342,7 +342,7 @@ struct UserMessageList: View {
|
|||
focusedField = nil
|
||||
replyMessageId = 0
|
||||
if sendPositionWithMessage {
|
||||
if bleManager.sendPosition(destNum: user.num, wantResponse: true) {
|
||||
if bleManager.sendPosition(channel: 0, destNum: user.num, wantResponse: true) {
|
||||
print("Location Sent")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,12 +47,12 @@ struct PositionLog: View {
|
|||
}
|
||||
TableColumn("Speed") { position in
|
||||
let speed = Measurement(value: Double(position.speed), unit: UnitSpeed.kilometersPerHour)
|
||||
Text(speed.formatted())
|
||||
Text(speed.formatted(.measurement(width: .abbreviated, numberFormatStyle: .number.precision(.fractionLength(0)))))
|
||||
}
|
||||
TableColumn("Heading") { position in
|
||||
let degrees = Angle.degrees(Double(position.heading))
|
||||
let heading = Measurement(value: degrees.degrees, unit: UnitAngle.degrees)
|
||||
Text("\(heading.formatted())")
|
||||
Text(heading.formatted(.measurement(width: .narrow, numberFormatStyle: .number.precision(.fractionLength(0)))))
|
||||
}
|
||||
TableColumn("SNR") { position in
|
||||
Text("\(String(format: "%.2f", position.snr)) dB")
|
||||
|
|
|
|||
|
|
@ -17,11 +17,32 @@ struct AboutMeshtastic: View {
|
|||
|
||||
List {
|
||||
Section(header: Text("What is Meshtastic?")) {
|
||||
Text("An open source, off-grid, decentralized, mesh network built to run on affordable, low-power devices.")
|
||||
Text("An open source, off-grid, decentralized, mesh network that runs on affordable, low-power radios.")
|
||||
.font(.title3)
|
||||
|
||||
}
|
||||
Section(header: Text("Apple Apps")) {
|
||||
|
||||
if locale.region?.identifier ?? "US" == "US" {
|
||||
HStack {
|
||||
Image("SOLAR_NODE")
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 75)
|
||||
.cornerRadius(5)
|
||||
.padding()
|
||||
VStack(alignment: .leading) {
|
||||
Link("Buy Complete Radios", destination: URL(string: "http://garthvh.com")!)
|
||||
.font(.title2)
|
||||
Text("Get custom waterproof solar and detection sensor router nodes, aluminium desktop nodes and rugged handsets.")
|
||||
.font(.callout)
|
||||
}
|
||||
}
|
||||
}
|
||||
Link("Sponsor App Development", destination: URL(string: "https://github.com/sponsors/garthvh")!)
|
||||
.font(.title2)
|
||||
Link("GitHub Repository", destination: URL(string: "https://github.com/meshtastic/Meshtastic-Apple")!)
|
||||
.font(.title2)
|
||||
Button("Review the app") {
|
||||
if let scene = UIApplication.shared.connectedScenes
|
||||
.first(where: { $0.activationState == .foregroundActive }) as? UIWindowScene {
|
||||
|
|
@ -29,17 +50,14 @@ struct AboutMeshtastic: View {
|
|||
}
|
||||
}
|
||||
.font(.title2)
|
||||
Link("Sponsor App Development", destination: URL(string: "https://github.com/sponsors/garthvh")!)
|
||||
.font(.title2)
|
||||
Link("GitHub Repository", destination: URL(string: "https://github.com/meshtastic/Meshtastic-Apple")!)
|
||||
.font(.title2)
|
||||
}
|
||||
if locale.region?.identifier ?? "no locale" == "US" {
|
||||
Section(header: Text("Get Devices")) {
|
||||
Link("Buy Complete Radios", destination: URL(string: "http://garthvh.com")!)
|
||||
.font(.title2)
|
||||
}
|
||||
|
||||
Text("Version: \(Bundle.main.appVersionLong) (\(Bundle.main.appBuild)) ")
|
||||
|
||||
Text(Bundle.main.copyright)
|
||||
.font(.system(size: 10, weight: .thin))
|
||||
.multilineTextAlignment(.center)
|
||||
}
|
||||
|
||||
Section(header: Text("Project information")) {
|
||||
Link("Website", destination: URL(string: "https://meshtastic.org")!)
|
||||
.font(.title2)
|
||||
|
|
|
|||
|
|
@ -72,7 +72,9 @@ struct AdminMessageList: View {
|
|||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
|
||||
})
|
||||
.onAppear {
|
||||
self.bleManager.context = context
|
||||
if self.bleManager.context == nil {
|
||||
self.bleManager.context = context
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -143,7 +143,13 @@ struct AppSettings: View {
|
|||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
|
||||
})
|
||||
.onAppear {
|
||||
self.bleManager.context = context
|
||||
if provideLocationInterval <= 0 {
|
||||
provideLocationInterval = 30
|
||||
UserDefaults.provideLocationInterval = provideLocationInterval
|
||||
}
|
||||
if self.bleManager.context == nil {
|
||||
self.bleManager.context = context
|
||||
}
|
||||
}
|
||||
.onChange(of: blockRangeTest) { newBlockRangeTest in
|
||||
UserDefaults.blockRangeTest = newBlockRangeTest
|
||||
|
|
|
|||
|
|
@ -285,7 +285,9 @@ struct Channels: View {
|
|||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
|
||||
})
|
||||
.onAppear {
|
||||
bleManager.context = context
|
||||
if self.bleManager.context == nil {
|
||||
self.bleManager.context = context
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -137,7 +137,9 @@ struct BluetoothConfig: View {
|
|||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
|
||||
})
|
||||
.onAppear {
|
||||
self.bleManager.context = context
|
||||
if self.bleManager.context == nil {
|
||||
self.bleManager.context = context
|
||||
}
|
||||
setBluetoothValues()
|
||||
// Need to request a BluetoothConfig from the remote node before allowing changes
|
||||
if bleManager.connectedPeripheral != nil && node?.bluetoothConfig == nil {
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ struct DeviceConfig: View {
|
|||
}
|
||||
Section(header: Text("GPIO")) {
|
||||
Picker("Button GPIO", selection: $buttonGPIO) {
|
||||
ForEach(0..<46) {
|
||||
ForEach(0..<48) {
|
||||
if $0 == 0 {
|
||||
Text("unset")
|
||||
} else {
|
||||
|
|
@ -113,7 +113,7 @@ struct DeviceConfig: View {
|
|||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
Picker("Buzzer GPIO", selection: $buzzerGPIO) {
|
||||
ForEach(0..<46) {
|
||||
ForEach(0..<48) {
|
||||
if $0 == 0 {
|
||||
Text("unset")
|
||||
} else {
|
||||
|
|
@ -232,7 +232,9 @@ struct DeviceConfig: View {
|
|||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
|
||||
})
|
||||
.onAppear {
|
||||
self.bleManager.context = context
|
||||
if self.bleManager.context == nil {
|
||||
self.bleManager.context = context
|
||||
}
|
||||
setDeviceValues()
|
||||
// Need to request a LoRaConfig from the remote node before allowing changes
|
||||
if bleManager.connectedPeripheral != nil && node?.deviceConfig == nil {
|
||||
|
|
|
|||
|
|
@ -192,7 +192,9 @@ struct DisplayConfig: View {
|
|||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
|
||||
})
|
||||
.onAppear {
|
||||
self.bleManager.context = context
|
||||
if self.bleManager.context == nil {
|
||||
self.bleManager.context = context
|
||||
}
|
||||
setDisplayValues()
|
||||
|
||||
// Need to request a LoRaConfig from the remote node before allowing changes
|
||||
|
|
|
|||
|
|
@ -246,10 +246,10 @@ struct LoRaConfig: View {
|
|||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
|
||||
})
|
||||
.onAppear {
|
||||
|
||||
self.bleManager.context = context
|
||||
if self.bleManager.context == nil {
|
||||
self.bleManager.context = context
|
||||
}
|
||||
setLoRaValues()
|
||||
|
||||
// Need to request a LoRaConfig from the remote node before allowing changes
|
||||
if bleManager.connectedPeripheral != nil && node?.loRaConfig == nil {
|
||||
print("empty lora config")
|
||||
|
|
@ -314,6 +314,11 @@ struct LoRaConfig: View {
|
|||
if newTxPower != node!.loRaConfig!.txPower { hasChanges = true }
|
||||
}
|
||||
}
|
||||
.onChange(of: txEnabled) { newTxEnabled in
|
||||
if node != nil && node!.loRaConfig != nil {
|
||||
if newTxEnabled != node!.loRaConfig!.txEnabled { hasChanges = true }
|
||||
}
|
||||
}
|
||||
}
|
||||
func setLoRaValues() {
|
||||
self.hopLimit = Int(node?.loRaConfig?.hopLimit ?? 3)
|
||||
|
|
|
|||
|
|
@ -130,7 +130,9 @@ struct AmbientLightingConfig: View {
|
|||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
|
||||
})
|
||||
.onAppear {
|
||||
self.bleManager.context = context
|
||||
if self.bleManager.context == nil {
|
||||
self.bleManager.context = context
|
||||
}
|
||||
setAmbientLightingConfigValue()
|
||||
// Need to request a Ambient Lighting Config from the remote node before allowing changes
|
||||
if bleManager.connectedPeripheral != nil && node?.ambientLightingConfig == nil {
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ struct CannedMessagesConfig: View {
|
|||
.disabled(configPreset > 0)
|
||||
Section(header: Text("Inputs")) {
|
||||
Picker("Pin A", selection: $inputbrokerPinA) {
|
||||
ForEach(0..<46) {
|
||||
ForEach(0..<48) {
|
||||
if $0 == 0 {
|
||||
Text("unset")
|
||||
} else {
|
||||
|
|
@ -136,7 +136,7 @@ struct CannedMessagesConfig: View {
|
|||
Text("GPIO pin for rotary encoder A port.")
|
||||
.font(.caption)
|
||||
Picker("Pin B", selection: $inputbrokerPinB) {
|
||||
ForEach(0..<46) {
|
||||
ForEach(0..<48) {
|
||||
if $0 == 0 {
|
||||
Text("unset")
|
||||
} else {
|
||||
|
|
@ -148,7 +148,7 @@ struct CannedMessagesConfig: View {
|
|||
Text("GPIO pin for rotary encoder B port.")
|
||||
.font(.caption)
|
||||
Picker("Press Pin", selection: $inputbrokerPinPress) {
|
||||
ForEach(0..<46) {
|
||||
ForEach(0..<48) {
|
||||
if $0 == 0 {
|
||||
Text("unset")
|
||||
} else {
|
||||
|
|
@ -264,9 +264,10 @@ struct CannedMessagesConfig: View {
|
|||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
|
||||
})
|
||||
.onAppear {
|
||||
self.bleManager.context = context
|
||||
if self.bleManager.context == nil {
|
||||
self.bleManager.context = context
|
||||
}
|
||||
setCannedMessagesValues()
|
||||
|
||||
// Need to request a CannedMessagesModuleConfig from the remote node before allowing changes
|
||||
if bleManager.connectedPeripheral != nil && node?.cannedMessageConfig == nil {
|
||||
print("empty canned messages module config")
|
||||
|
|
|
|||
|
|
@ -139,7 +139,7 @@ struct DetectionSensorConfig: View {
|
|||
.listRowSeparator(.visible)
|
||||
.offset(y: -10)
|
||||
Picker("GPIO Pin to monitor", selection: $monitorPin) {
|
||||
ForEach(0..<46) {
|
||||
ForEach(0..<48) {
|
||||
if $0 == 0 {
|
||||
Text("unset")
|
||||
} else {
|
||||
|
|
@ -240,9 +240,10 @@ struct DetectionSensorConfig: View {
|
|||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
|
||||
})
|
||||
.onAppear {
|
||||
self.bleManager.context = context
|
||||
if self.bleManager.context == nil {
|
||||
self.bleManager.context = context
|
||||
}
|
||||
setDetectionSensorValues()
|
||||
|
||||
// Need to request a Detection Sensor Module Config from the remote node before allowing changes
|
||||
if bleManager.connectedPeripheral != nil && node?.detectionSensorConfig == nil {
|
||||
print("empty detection sensor module config")
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ struct ExternalNotificationConfig: View {
|
|||
Text("If enabled, the 'output' Pin will be pulled active high, disabled means active low.")
|
||||
.font(.caption)
|
||||
Picker("Output pin GPIO", selection: $output) {
|
||||
ForEach(0..<46) {
|
||||
ForEach(0..<48) {
|
||||
if $0 == 0 {
|
||||
Text("unset")
|
||||
} else {
|
||||
|
|
@ -147,7 +147,7 @@ struct ExternalNotificationConfig: View {
|
|||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
Picker("Output pin buzzer GPIO ", selection: $outputBuzzer) {
|
||||
ForEach(0..<46) {
|
||||
ForEach(0..<48) {
|
||||
if $0 == 0 {
|
||||
Text("unset")
|
||||
} else {
|
||||
|
|
@ -157,7 +157,7 @@ struct ExternalNotificationConfig: View {
|
|||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
Picker("Output pin vibra GPIO", selection: $outputVibra) {
|
||||
ForEach(0..<46) {
|
||||
ForEach(0..<48) {
|
||||
if $0 == 0 {
|
||||
Text("unset")
|
||||
} else {
|
||||
|
|
@ -226,9 +226,10 @@ struct ExternalNotificationConfig: View {
|
|||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
|
||||
})
|
||||
.onAppear {
|
||||
self.bleManager.context = context
|
||||
if self.bleManager.context == nil {
|
||||
self.bleManager.context = context
|
||||
}
|
||||
setExternalNotificationValues()
|
||||
|
||||
// Need to request a TelemetryModuleConfig from the remote node before allowing changes
|
||||
if bleManager.connectedPeripheral != nil && node?.externalNotificationConfig == nil {
|
||||
print("empty external notification module config")
|
||||
|
|
|
|||
|
|
@ -245,9 +245,10 @@ struct MQTTConfig: View {
|
|||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
|
||||
})
|
||||
.onAppear {
|
||||
self.bleManager.context = context
|
||||
if self.bleManager.context == nil {
|
||||
self.bleManager.context = context
|
||||
}
|
||||
setMqttValues()
|
||||
|
||||
// Need to request a TelemetryModuleConfig from the remote node before allowing changes
|
||||
if bleManager.connectedPeripheral != nil && node?.mqttConfig == nil {
|
||||
print("empty mqtt module config")
|
||||
|
|
|
|||
|
|
@ -117,9 +117,10 @@ struct RangeTestConfig: View {
|
|||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
|
||||
})
|
||||
.onAppear {
|
||||
self.bleManager.context = context
|
||||
if self.bleManager.context == nil {
|
||||
self.bleManager.context = context
|
||||
}
|
||||
setRangeTestValues()
|
||||
|
||||
// Need to request a RangeTestModule Config from the remote node before allowing changes
|
||||
if bleManager.connectedPeripheral != nil && node?.rangeTestConfig == nil {
|
||||
print("empty range test module config")
|
||||
|
|
|
|||
|
|
@ -115,7 +115,9 @@ struct RtttlConfig: View {
|
|||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
|
||||
})
|
||||
.onAppear {
|
||||
self.bleManager.context = context
|
||||
if self.bleManager.context == nil {
|
||||
self.bleManager.context = context
|
||||
}
|
||||
setRtttLConfigValue()
|
||||
// Need to request a Rtttl Config from the remote node before allowing changes
|
||||
if bleManager.connectedPeripheral != nil && (node?.rtttlConfig == nil || node?.rtttlConfig?.ringtone?.count ?? 0 == 0) {
|
||||
|
|
|
|||
|
|
@ -23,7 +23,10 @@ struct SerialConfig: View {
|
|||
@State var txd = 0
|
||||
@State var baudRate = 0
|
||||
@State var timeout = 0
|
||||
@State var overrideConsoleSerialPort = false
|
||||
@State var mode = 0
|
||||
|
||||
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
|
|
@ -96,7 +99,7 @@ struct SerialConfig: View {
|
|||
Section(header: Text("GPIO")) {
|
||||
|
||||
Picker("Receive data (rxd) GPIO pin", selection: $rxd) {
|
||||
ForEach(0..<46) {
|
||||
ForEach(0..<48) {
|
||||
if $0 == 0 {
|
||||
Text("unset")
|
||||
} else {
|
||||
|
|
@ -107,7 +110,7 @@ struct SerialConfig: View {
|
|||
.pickerStyle(DefaultPickerStyle())
|
||||
|
||||
Picker("Transmit data (txd) GPIO pin", selection: $txd) {
|
||||
ForEach(0..<46) {
|
||||
ForEach(0..<48) {
|
||||
if $0 == 0 {
|
||||
Text("unset")
|
||||
} else {
|
||||
|
|
@ -153,6 +156,7 @@ struct SerialConfig: View {
|
|||
sc.txd = UInt32(txd)
|
||||
sc.baud = SerialBaudRates(rawValue: baudRate)!.protoEnumValue()
|
||||
sc.timeout = UInt32(timeout)
|
||||
sc.overrideConsoleSerialPort = overrideConsoleSerialPort
|
||||
sc.mode = SerialModeTypes(rawValue: mode)!.protoEnumValue()
|
||||
|
||||
let adminMessageId = bleManager.saveSerialModuleConfig(config: sc, fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0)
|
||||
|
|
@ -176,8 +180,9 @@ struct SerialConfig: View {
|
|||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
|
||||
})
|
||||
.onAppear {
|
||||
|
||||
self.bleManager.context = context
|
||||
if self.bleManager.context == nil {
|
||||
self.bleManager.context = context
|
||||
}
|
||||
setSerialValues()
|
||||
// Need to request a SerialModuleConfig from the remote node before allowing changes
|
||||
if bleManager.connectedPeripheral != nil && node?.serialConfig == nil {
|
||||
|
|
@ -231,6 +236,13 @@ struct SerialConfig: View {
|
|||
if newTimeout != node!.serialConfig!.timeout { hasChanges = true }
|
||||
}
|
||||
}
|
||||
.onChange(of: overrideConsoleSerialPort) { newOverrideConsoleSerialPort in
|
||||
|
||||
if node != nil && node!.serialConfig != nil {
|
||||
|
||||
if newOverrideConsoleSerialPort != node!.serialConfig!.overrideConsoleSerialPort { hasChanges = true }
|
||||
}
|
||||
}
|
||||
.onChange(of: mode) { newMode in
|
||||
|
||||
if node != nil && node!.serialConfig != nil {
|
||||
|
|
@ -248,6 +260,7 @@ struct SerialConfig: View {
|
|||
self.baudRate = Int(node?.serialConfig?.baudRate ?? 0)
|
||||
self.timeout = Int(node?.serialConfig?.timeout ?? 0)
|
||||
self.mode = Int(node?.serialConfig?.mode ?? 0)
|
||||
self.overrideConsoleSerialPort = false // node?.serialConfig?.overrideConsoleSerialPort ?? false
|
||||
self.hasChanges = false
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -137,9 +137,10 @@ struct StoreForwardConfig: View {
|
|||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
|
||||
})
|
||||
.onAppear {
|
||||
self.bleManager.context = context
|
||||
if self.bleManager.context == nil {
|
||||
self.bleManager.context = context
|
||||
}
|
||||
setDetectionSensorValues()
|
||||
|
||||
// Need to request a Detection Sensor Module Config from the remote node before allowing changes
|
||||
if bleManager.connectedPeripheral != nil && node?.detectionSensorConfig == nil {
|
||||
print("empty store and forward module config")
|
||||
|
|
|
|||
|
|
@ -132,9 +132,10 @@ struct TelemetryConfig: View {
|
|||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
|
||||
})
|
||||
.onAppear {
|
||||
self.bleManager.context = context
|
||||
if self.bleManager.context == nil {
|
||||
self.bleManager.context = context
|
||||
}
|
||||
setTelemetryValues()
|
||||
|
||||
// Need to request a TelemetryModuleConfig from the remote node before allowing changes
|
||||
if bleManager.connectedPeripheral != nil && node?.telemetryConfig == nil {
|
||||
print("empty telemetry module config")
|
||||
|
|
|
|||
|
|
@ -166,9 +166,10 @@ struct NetworkConfig: View {
|
|||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
|
||||
})
|
||||
.onAppear {
|
||||
self.bleManager.context = context
|
||||
if self.bleManager.context == nil {
|
||||
self.bleManager.context = context
|
||||
}
|
||||
setNetworkValues()
|
||||
|
||||
// Need to request a NetworkConfig from the remote node before allowing changes
|
||||
if bleManager.connectedPeripheral != nil && node?.networkConfig == nil {
|
||||
print("empty network config")
|
||||
|
|
|
|||
|
|
@ -213,7 +213,7 @@ struct PositionConfig: View {
|
|||
if deviceGpsEnabled {
|
||||
|
||||
Picker("GPS Receive GPIO", selection: $rxGpio) {
|
||||
ForEach(0..<46) {
|
||||
ForEach(0..<48) {
|
||||
if $0 == 0 {
|
||||
Text("unset")
|
||||
} else {
|
||||
|
|
@ -223,7 +223,7 @@ struct PositionConfig: View {
|
|||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
Picker("GPS Transmit GPIO", selection: $txGpio) {
|
||||
ForEach(0..<46) {
|
||||
ForEach(0..<48) {
|
||||
if $0 == 0 {
|
||||
Text("unset")
|
||||
} else {
|
||||
|
|
@ -233,7 +233,7 @@ struct PositionConfig: View {
|
|||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
Picker("GPS EN GPIO", selection: $gpsEnGpio) {
|
||||
ForEach(0..<46) {
|
||||
ForEach(0..<48) {
|
||||
if $0 == 0 {
|
||||
Text("unset")
|
||||
} else {
|
||||
|
|
@ -276,7 +276,7 @@ struct PositionConfig: View {
|
|||
Button(buttonText) {
|
||||
|
||||
if fixedPosition {
|
||||
_ = bleManager.sendPosition(destNum: node!.num, wantResponse: true)
|
||||
_ = bleManager.sendPosition(channel: 0, destNum: node?.num ?? 0, wantResponse: true)
|
||||
}
|
||||
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context)
|
||||
|
||||
|
|
@ -324,10 +324,10 @@ struct PositionConfig: View {
|
|||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
|
||||
})
|
||||
.onAppear {
|
||||
|
||||
self.bleManager.context = context
|
||||
if self.bleManager.context == nil {
|
||||
self.bleManager.context = context
|
||||
}
|
||||
setPositionValues()
|
||||
|
||||
// Need to request a PositionConfig from the remote node before allowing changes
|
||||
if bleManager.connectedPeripheral != nil && node?.positionConfig == nil {
|
||||
print("empty position config")
|
||||
|
|
|
|||
|
|
@ -2,15 +2,9 @@
|
|||
// Firmware.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Created by Garth Vander Houwen on 3/10/23.
|
||||
// Copyright(c) by Garth Vander Houwen on 3/10/23.
|
||||
//
|
||||
|
||||
//
|
||||
// About.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Copyright(c) Garth Vander Houwen 10/6/22.
|
||||
//
|
||||
import SwiftUI
|
||||
import StoreKit
|
||||
|
||||
|
|
@ -18,28 +12,32 @@ struct Firmware: View {
|
|||
@Environment(\.managedObjectContext) var context
|
||||
@EnvironmentObject var bleManager: BLEManager
|
||||
var node: NodeInfoEntity?
|
||||
@State var minimumVersion = "2.1.0"
|
||||
@State var minimumVersion = "2.2.16"
|
||||
@State var version = ""
|
||||
@State private var firmwareReleaseData: FirmwareRelease = FirmwareRelease()
|
||||
var body: some View {
|
||||
// NavigationSplitView {
|
||||
NavigationStack {
|
||||
let hwModel: HardwareModels = HardwareModels.allCases.first(where: { $0.rawValue == node?.user?.hwModel ?? "UNSET" }) ?? HardwareModels.UNSET
|
||||
VStack(alignment: .leading) {
|
||||
Text("Current Version: \(bleManager.connectedVersion)")
|
||||
.font(.largeTitle)
|
||||
Text("Your device supports the following firmware: ")
|
||||
.font(.callout)
|
||||
HStack {
|
||||
ForEach(hwModel.firmwareStrings, id: \.self) { fs in
|
||||
Text(fs).font(.callout)
|
||||
}
|
||||
}
|
||||
.padding(.bottom)
|
||||
@State private var currentDevice: DeviceHardware?
|
||||
|
||||
if hwModel.platform() == HardwarePlatforms.nrf52 {
|
||||
var body: some View {
|
||||
VStack {
|
||||
VStack(alignment: .leading) {
|
||||
let deviceString = currentDevice?.hwModelSlug.replacingOccurrences(of: "_", with: "")
|
||||
|
||||
Text("Your Device Model: \(currentDevice?.displayName ?? "Unknown")")
|
||||
.font(.largeTitle)
|
||||
|
||||
VStack {
|
||||
Image(deviceString ?? "UNSET")
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 200, height: 200)
|
||||
.cornerRadius(5)
|
||||
}
|
||||
Text("Current Firmware Version: \(bleManager.connectedVersion)")
|
||||
.font(.title)
|
||||
|
||||
if currentDevice?.architecture == Meshtastic.Architecture.nrf52840 {
|
||||
VStack(alignment: .leading) {
|
||||
if hwModel == HardwareModels.RAK4631 {
|
||||
/// RAK 4631
|
||||
if currentDevice?.hwModel == 9 {
|
||||
Text("nRF OTA Device Firmware Update App")
|
||||
.font(.title3)
|
||||
Text("You can update your Meshtastic device over bluetooth using the Nordic DFU app. This currently works for RAK NRF devices.")
|
||||
|
|
@ -53,13 +51,13 @@ struct Firmware: View {
|
|||
.font(.callout)
|
||||
}
|
||||
}
|
||||
} else if hwModel.platform() == HardwarePlatforms.esp32 {
|
||||
} else if currentDevice?.architecture == Meshtastic.Architecture.esp32 {
|
||||
VStack(alignment: .leading) {
|
||||
Text("ESP32 Device Firmware Update")
|
||||
.font(.title3)
|
||||
Text("Currently the reccomended way to update ESP32 devices is using the web flasher from a chrome based browser. It does not work on mobile devices or over BLE.")
|
||||
.font(.caption)
|
||||
Link("Web Flasher", destination: URL(string: "https://flasher.meshtastic.org")!)
|
||||
Link("Web Flasher", destination: URL(string: "https://flash.meshtastic.org")!)
|
||||
.font(.callout)
|
||||
.padding(.bottom)
|
||||
Text("ESP 32 OTA update is a work in progress, click the button below to sent your device a reboot into ota admin message.")
|
||||
|
|
@ -88,169 +86,170 @@ struct Firmware: View {
|
|||
.font(.title3)
|
||||
Text(node?.user?.hwModel ?? "UNSET")
|
||||
.font(.title3)
|
||||
Text(hwModel.platform().description)
|
||||
.font(.title3)
|
||||
// Text(hwModel.platform().description)
|
||||
// .font(.title3)
|
||||
}
|
||||
}.padding()
|
||||
}
|
||||
.padding()
|
||||
VStack(alignment: .leading) {
|
||||
Text("Firmware Releases")
|
||||
.font(.title3)
|
||||
.padding([.leading, .trailing])
|
||||
List {
|
||||
Section(header: Text("Stable")) {
|
||||
ForEach(firmwareReleaseData.releases?.stable ?? [], id: \.id) { fr in
|
||||
Link(destination: URL(string: fr.zipUrl ?? "")!) {
|
||||
HStack {
|
||||
Text(fr.title ?? "Unknown")
|
||||
.font(.caption)
|
||||
Spacer()
|
||||
Image(systemName: "square.and.arrow.down")
|
||||
.font(.title3)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Section("Alpha") {
|
||||
ForEach(firmwareReleaseData.releases?.alpha ?? [], id: \.id) { fr in
|
||||
Link(destination: URL(string: fr.zipUrl ?? "")!) {
|
||||
HStack {
|
||||
Text(fr.title ?? "Unknown")
|
||||
.font(.caption)
|
||||
Spacer()
|
||||
Image(systemName: "square.and.arrow.down")
|
||||
.font(.title3)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Section("Pull Requests") {
|
||||
ForEach(firmwareReleaseData.pullRequests ?? [], id: \.id) { fr in
|
||||
Link(destination: URL(string: fr.zipUrl ?? "")!) {
|
||||
HStack {
|
||||
Text(fr.title ?? "Unknown")
|
||||
.font(.caption)
|
||||
Spacer()
|
||||
Image(systemName: "square.and.arrow.down")
|
||||
.font(.title3)
|
||||
}
|
||||
}
|
||||
// List {
|
||||
// Section(header: Text("Stable")) {
|
||||
// ForEach(firmwareReleaseData.releases?.stable ?? [], id: \.id) { fr in
|
||||
// Link(destination: URL(string: fr.zipUrl ?? "")!) {
|
||||
// HStack {
|
||||
// Text(fr.title ?? "Unknown")
|
||||
// .font(.caption)
|
||||
// Spacer()
|
||||
// Image(systemName: "square.and.arrow.down")
|
||||
// .font(.title3)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// Section("Alpha") {
|
||||
// ForEach(firmwareReleaseData.releases?.alpha ?? [], id: \.id) { fr in
|
||||
// Link(destination: URL(string: fr.zipUrl ?? "")!) {
|
||||
// HStack {
|
||||
// Text(fr.title ?? "Unknown")
|
||||
// .font(.caption)
|
||||
// Spacer()
|
||||
// Image(systemName: "square.and.arrow.down")
|
||||
// .font(.title3)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// Section("Pull Requests") {
|
||||
// ForEach(firmwareReleaseData.pullRequests ?? [], id: \.id) { fr in
|
||||
// Link(destination: URL(string: fr.zipUrl ?? "")!) {
|
||||
// HStack {
|
||||
// Text(fr.title ?? "Unknown")
|
||||
// .font(.caption)
|
||||
// Spacer()
|
||||
// Image(systemName: "square.and.arrow.down")
|
||||
// .font(.title3)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
.padding(.bottom, 5)
|
||||
.onAppear() {
|
||||
Api().loadDeviceHardwareData { (hw) in
|
||||
for device in hw {
|
||||
print(device)
|
||||
let currentHardware = node?.user?.hwModel ?? "UNSET"
|
||||
let deviceString = device.hwModelSlug.replacingOccurrences(of: "_", with: "")
|
||||
if deviceString == currentHardware {
|
||||
print("Selected: \(device)")
|
||||
currentDevice = device
|
||||
}
|
||||
}
|
||||
}
|
||||
// Api().loadFirmwareReleaseData { (bks) in
|
||||
// //sel = bks
|
||||
// }
|
||||
}
|
||||
.padding(.bottom, 5)
|
||||
.onAppear(perform: loadData)
|
||||
.navigationTitle("Firmware Updates")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
}
|
||||
}
|
||||
func loadData() {
|
||||
guard let url = URL(string: "https://api.meshtastic.org/github/firmware/list") else {
|
||||
return
|
||||
}
|
||||
let request = URLRequest(url: url)
|
||||
URLSession.shared.dataTask(with: request) { data, _, _ in
|
||||
if let data = data {
|
||||
if let response_obj = try? JSONDecoder().decode(FirmwareRelease.self, from: data) {
|
||||
DispatchQueue.main.async {
|
||||
self.firmwareReleaseData = response_obj
|
||||
}
|
||||
}
|
||||
}
|
||||
}.resume()
|
||||
}
|
||||
}
|
||||
|
||||
struct FirmwareRelease: Codable {
|
||||
var releases: Releases? = Releases()
|
||||
var pullRequests: [PullRequests]? = []
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case releases = "Releases"
|
||||
case pullRequests = "Pull Requests"
|
||||
}
|
||||
init(from decoder: Decoder) throws {
|
||||
let values = try decoder.container(keyedBy: CodingKeys.self)
|
||||
releases = try values.decodeIfPresent(Releases.self, forKey: .releases )
|
||||
pullRequests = try values.decodeIfPresent([PullRequests].self, forKey: .pullRequests )
|
||||
}
|
||||
init() {
|
||||
}
|
||||
}
|
||||
|
||||
struct Releases: Codable {
|
||||
var stable: [Stable]? = []
|
||||
var alpha: [Alpha]? = []
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case stable = "Stable"
|
||||
case alpha = "Alpha"
|
||||
}
|
||||
init(from decoder: Decoder) throws {
|
||||
let values = try decoder.container(keyedBy: CodingKeys.self)
|
||||
stable = try values.decodeIfPresent([Stable].self, forKey: .stable )
|
||||
alpha = try values.decodeIfPresent([Alpha].self, forKey: .alpha )
|
||||
}
|
||||
init() {}
|
||||
}
|
||||
|
||||
struct Alpha: Codable {
|
||||
var id: String?
|
||||
var title: String?
|
||||
var pageUrl: String?
|
||||
var zipUrl: String?
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case id = "id"
|
||||
case title = "title"
|
||||
case pageUrl = "page_url"
|
||||
case zipUrl = "zip_url"
|
||||
}
|
||||
init(from decoder: Decoder) throws {
|
||||
let values = try decoder.container(keyedBy: CodingKeys.self)
|
||||
id = try values.decodeIfPresent(String.self, forKey: .id )
|
||||
title = try values.decodeIfPresent(String.self, forKey: .title )
|
||||
pageUrl = try values.decodeIfPresent(String.self, forKey: .pageUrl )
|
||||
zipUrl = try values.decodeIfPresent(String.self, forKey: .zipUrl )
|
||||
}
|
||||
init() {}
|
||||
}
|
||||
|
||||
struct Stable: Codable {
|
||||
var id: String?
|
||||
var title: String?
|
||||
var pageUrl: String?
|
||||
var zipUrl: String?
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case id = "id"
|
||||
case title = "title"
|
||||
case pageUrl = "page_url"
|
||||
case zipUrl = "zip_url"
|
||||
}
|
||||
init(from decoder: Decoder) throws {
|
||||
let values = try decoder.container(keyedBy: CodingKeys.self)
|
||||
id = try values.decodeIfPresent(String.self, forKey: .id )
|
||||
title = try values.decodeIfPresent(String.self, forKey: .title )
|
||||
pageUrl = try values.decodeIfPresent(String.self, forKey: .pageUrl )
|
||||
zipUrl = try values.decodeIfPresent(String.self, forKey: .zipUrl )
|
||||
}
|
||||
init() {}
|
||||
}
|
||||
|
||||
struct PullRequests: Codable {
|
||||
var id: String?
|
||||
var title: String?
|
||||
var pageUrl: String?
|
||||
var zipUrl: String?
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case id = "id"
|
||||
case title = "title"
|
||||
case pageUrl = "page_url"
|
||||
case zipUrl = "zip_url"
|
||||
}
|
||||
init(from decoder: Decoder) throws {
|
||||
let values = try decoder.container(keyedBy: CodingKeys.self)
|
||||
id = try values.decodeIfPresent(String.self, forKey: .id )
|
||||
title = try values.decodeIfPresent(String.self, forKey: .title )
|
||||
pageUrl = try values.decodeIfPresent(String.self, forKey: .pageUrl )
|
||||
zipUrl = try values.decodeIfPresent(String.self, forKey: .zipUrl )
|
||||
}
|
||||
init() {}
|
||||
}
|
||||
//struct FirmwareRelease: Codable {
|
||||
// var releases: Releases? = Releases()
|
||||
// var pullRequests: [PullRequests]? = []
|
||||
// enum CodingKeys: String, CodingKey {
|
||||
// case releases = "Releases"
|
||||
// case pullRequests = "Pull Requests"
|
||||
// }
|
||||
// init(from decoder: Decoder) throws {
|
||||
// let values = try decoder.container(keyedBy: CodingKeys.self)
|
||||
// releases = try values.decodeIfPresent(Releases.self, forKey: .releases )
|
||||
// pullRequests = try values.decodeIfPresent([PullRequests].self, forKey: .pullRequests )
|
||||
// }
|
||||
// init() {
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//struct Releases: Codable {
|
||||
// var stable: [Stable]? = []
|
||||
// var alpha: [Alpha]? = []
|
||||
// enum CodingKeys: String, CodingKey {
|
||||
// case stable = "Stable"
|
||||
// case alpha = "Alpha"
|
||||
// }
|
||||
// init(from decoder: Decoder) throws {
|
||||
// let values = try decoder.container(keyedBy: CodingKeys.self)
|
||||
// stable = try values.decodeIfPresent([Stable].self, forKey: .stable )
|
||||
// alpha = try values.decodeIfPresent([Alpha].self, forKey: .alpha )
|
||||
// }
|
||||
// init() {}
|
||||
//}
|
||||
//
|
||||
//struct Alpha: Codable {
|
||||
// var id: String?
|
||||
// var title: String?
|
||||
// var pageUrl: String?
|
||||
// var zipUrl: String?
|
||||
// enum CodingKeys: String, CodingKey {
|
||||
// case id = "id"
|
||||
// case title = "title"
|
||||
// case pageUrl = "page_url"
|
||||
// case zipUrl = "zip_url"
|
||||
// }
|
||||
// init(from decoder: Decoder) throws {
|
||||
// let values = try decoder.container(keyedBy: CodingKeys.self)
|
||||
// id = try values.decodeIfPresent(String.self, forKey: .id )
|
||||
// title = try values.decodeIfPresent(String.self, forKey: .title )
|
||||
// pageUrl = try values.decodeIfPresent(String.self, forKey: .pageUrl )
|
||||
// zipUrl = try values.decodeIfPresent(String.self, forKey: .zipUrl )
|
||||
// }
|
||||
// init() {}
|
||||
//}
|
||||
//
|
||||
//struct Stable: Codable {
|
||||
// var id: String?
|
||||
// var title: String?
|
||||
// var pageUrl: String?
|
||||
// var zipUrl: String?
|
||||
// enum CodingKeys: String, CodingKey {
|
||||
// case id = "id"
|
||||
// case title = "title"
|
||||
// case pageUrl = "page_url"
|
||||
// case zipUrl = "zip_url"
|
||||
// }
|
||||
// init(from decoder: Decoder) throws {
|
||||
// let values = try decoder.container(keyedBy: CodingKeys.self)
|
||||
// id = try values.decodeIfPresent(String.self, forKey: .id )
|
||||
// title = try values.decodeIfPresent(String.self, forKey: .title )
|
||||
// pageUrl = try values.decodeIfPresent(String.self, forKey: .pageUrl )
|
||||
// zipUrl = try values.decodeIfPresent(String.self, forKey: .zipUrl )
|
||||
// }
|
||||
// init() {}
|
||||
//}
|
||||
//
|
||||
//struct PullRequests: Codable {
|
||||
// var id: String?
|
||||
// var title: String?
|
||||
// var pageUrl: String?
|
||||
// var zipUrl: String?
|
||||
// enum CodingKeys: String, CodingKey {
|
||||
// case id = "id"
|
||||
// case title = "title"
|
||||
// case pageUrl = "page_url"
|
||||
// case zipUrl = "zip_url"
|
||||
// }
|
||||
// init(from decoder: Decoder) throws {
|
||||
// let values = try decoder.container(keyedBy: CodingKeys.self)
|
||||
// id = try values.decodeIfPresent(String.self, forKey: .id )
|
||||
// title = try values.decodeIfPresent(String.self, forKey: .title )
|
||||
// pageUrl = try values.decodeIfPresent(String.self, forKey: .pageUrl )
|
||||
// zipUrl = try values.decodeIfPresent(String.self, forKey: .zipUrl )
|
||||
// }
|
||||
// init() {}
|
||||
//}
|
||||
|
|
|
|||
63
Meshtastic/Views/Settings/FirmwareApi.swift
Normal file
63
Meshtastic/Views/Settings/FirmwareApi.swift
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
//
|
||||
// FirmwareApi.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Created by Garth Vander Houwen on 12/27/23.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
//struct DeviceHardware: Codable {
|
||||
// var hwModel: Int
|
||||
// var hwModelSlug: String
|
||||
// var platformioTarget: String
|
||||
// var activelySupported: Bool
|
||||
// var displayName: String
|
||||
//}
|
||||
|
||||
struct DeviceHardware: Codable {
|
||||
let hwModel: Int
|
||||
let hwModelSlug, platformioTarget: String
|
||||
let architecture: Architecture
|
||||
let activelySupported: Bool
|
||||
let displayName: String
|
||||
}
|
||||
|
||||
enum Architecture: String, Codable {
|
||||
case esp32 = "esp32"
|
||||
case esp32C3 = "esp32-c3"
|
||||
case esp32S3 = "esp32-s3"
|
||||
case nrf52840 = "nrf52840"
|
||||
case rp2040 = "rp2040"
|
||||
}
|
||||
|
||||
class Api : ObservableObject{
|
||||
// @Published var devices = [DeviceHardware]()
|
||||
|
||||
func loadDeviceHardwareData(completion:@escaping ([DeviceHardware]) -> ()) {
|
||||
guard let url = URL(string: "https://api.meshtastic.org/resource/deviceHardware") else {
|
||||
print("Invalid url...")
|
||||
return
|
||||
}
|
||||
URLSession.shared.dataTask(with: url) { data, response, error in
|
||||
let deviceHardware = try! JSONDecoder().decode([DeviceHardware].self, from: data!)
|
||||
//print(deviceHardware)
|
||||
DispatchQueue.main.async {
|
||||
completion(deviceHardware)
|
||||
}
|
||||
}.resume()
|
||||
}
|
||||
// func loadFirmwareReleaseData(completion:@escaping ([FirmwareRelease]) -> ()) {
|
||||
// guard let url = URL(string: "https://api.meshtastic.org/github/firmware/list") else {
|
||||
// print("Invalid url...")
|
||||
// return
|
||||
// }
|
||||
// URLSession.shared.dataTask(with: url) { data, response, error in
|
||||
// let deviceHardware = try! JSONDecoder().decode([FirmwareRelease].self, from: data!)
|
||||
// print(deviceHardware)
|
||||
// DispatchQueue.main.async {
|
||||
// completion(deviceHardware)
|
||||
// }
|
||||
// }.resume()
|
||||
// }
|
||||
}
|
||||
|
|
@ -11,33 +11,37 @@ import CoreLocation
|
|||
@available(iOS 17.0, macOS 14.0, *)
|
||||
struct GPSStatus: View {
|
||||
|
||||
var largeFont: Font = .footnote
|
||||
var smallFont: Font = .caption2
|
||||
|
||||
@ObservedObject var locationsHandler: LocationsHandler = LocationsHandler.shared
|
||||
var body: some View {
|
||||
|
||||
if let newLocation = locationsHandler.locationsArray.last {
|
||||
let horizontalAccuracy = Measurement(value: newLocation.horizontalAccuracy, unit: UnitLength.meters)
|
||||
let verticalAccuracy = Measurement(value: newLocation.verticalAccuracy, unit: UnitLength.meters)
|
||||
let altitiude = Measurement(value: newLocation.altitude, unit: UnitLength.meters)
|
||||
let speed = Measurement(value: newLocation.speed, unit: UnitSpeed.kilometersPerHour)
|
||||
let speedAccuracy = Measurement(value: newLocation.speedAccuracy, unit: UnitSpeed.metersPerSecond)
|
||||
let courseAccuracy = Measurement(value: newLocation.courseAccuracy, unit: UnitAngle.degrees)
|
||||
let horizontalAccuracy = Measurement(value: newLocation.horizontalAccuracy, unit: UnitLength.meters)
|
||||
let verticalAccuracy = Measurement(value: newLocation.verticalAccuracy, unit: UnitLength.meters)
|
||||
let altitiude = Measurement(value: newLocation.altitude, unit: UnitLength.meters)
|
||||
let speed = Measurement(value: newLocation.speed, unit: UnitSpeed.kilometersPerHour)
|
||||
let speedAccuracy = Measurement(value: newLocation.speedAccuracy, unit: UnitSpeed.metersPerSecond)
|
||||
let courseAccuracy = Measurement(value: newLocation.courseAccuracy, unit: UnitAngle.degrees)
|
||||
|
||||
Label("Coordinate \(String(format: "%.5f", newLocation.coordinate.latitude)), \(String(format: "%.5f", newLocation.coordinate.longitude))", systemImage: "mappin")
|
||||
.font(.footnote)
|
||||
.font(largeFont)
|
||||
.textSelection(.enabled)
|
||||
HStack {
|
||||
Label("Accuracy \(horizontalAccuracy.formatted())", systemImage: "scope")
|
||||
.font(.footnote)
|
||||
.font(largeFont)
|
||||
Label("Sats Estimate \(LocationsHandler.satsInView)", systemImage: "sparkles")
|
||||
.font(.footnote)
|
||||
.font(largeFont)
|
||||
|
||||
}
|
||||
HStack {
|
||||
if newLocation.verticalAccuracy > 0 {
|
||||
Label("Altitude \(altitiude.formatted())", systemImage: "mountain.2")
|
||||
.font(.footnote)
|
||||
.font(largeFont)
|
||||
}
|
||||
Label("Accuracy \(verticalAccuracy.formatted())", systemImage: "lines.measurement.vertical")
|
||||
.font(.caption2)
|
||||
.font(smallFont)
|
||||
}
|
||||
HStack {
|
||||
let degrees = Angle.degrees(newLocation.course)
|
||||
|
|
@ -49,15 +53,15 @@ struct GPSStatus: View {
|
|||
.symbolRenderingMode(.hierarchical)
|
||||
.rotationEffect(degrees)
|
||||
}
|
||||
.font(.footnote)
|
||||
.font(largeFont)
|
||||
Label("Accuracy \(courseAccuracy.formatted(.measurement(width: .narrow, numberFormatStyle: .number.precision(.fractionLength(0)))))", systemImage: "safari")
|
||||
.font(.caption2)
|
||||
.font(smallFont)
|
||||
}
|
||||
HStack {
|
||||
Label("Speed \(speed.formatted(.measurement(width: .abbreviated, numberFormatStyle: .number.precision(.fractionLength(0)))))", systemImage: "speedometer")
|
||||
.font(.footnote)
|
||||
.font(largeFont)
|
||||
Label("Accuracy \(speedAccuracy.formatted(.measurement(width: .abbreviated, numberFormatStyle: .number.precision(.fractionLength(0)))))", systemImage: "gauge.with.dots.needle.bottom.50percent.badge.plus")
|
||||
.font(.caption2)
|
||||
.font(smallFont)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,160 +11,277 @@ import MapKit
|
|||
import CoreLocation
|
||||
import CoreMotion
|
||||
|
||||
struct TimerDisplayObject {
|
||||
var seconds: Int = 0
|
||||
var minutes: Int = 0
|
||||
var hours: Int = 0
|
||||
|
||||
var display: String {
|
||||
if self.seconds == 0 {
|
||||
"\(String(format: "%02d", self.hours)):\(String(format: "%02d", self.minutes)):00"
|
||||
} else {
|
||||
"\(String(format: "%02d", self.hours)):\(String(format: "%02d", self.minutes)):\(String(format: "%02d", self.seconds))"
|
||||
}
|
||||
}
|
||||
|
||||
var timeMinuteCalculator: Float { Float(hours*60+seconds/60+minutes) }
|
||||
}
|
||||
|
||||
@available(iOS 17.0, macOS 14.0, *)
|
||||
struct RouteRecorder: View {
|
||||
|
||||
@ObservedObject var locationsHandler = LocationsHandler.shared
|
||||
@ObservedObject var locationsHandler: LocationsHandler = LocationsHandler.shared
|
||||
@Environment(\.managedObjectContext) var context
|
||||
@State private var position: MapCameraPosition = .userLocation(followsHeading: true, fallback: .automatic)
|
||||
@State var isTimerRunning = false
|
||||
//@State var mapStyle: MapStyle = MapStyle.hybrid(elevation: .realistic, pointsOfInterest: .all, showsTraffic: true)
|
||||
@State var mapStyle: MapStyle = MapStyle.standard(elevation: .realistic)
|
||||
@State var isShowingDetails = false
|
||||
@State var timer: Timer?
|
||||
@Namespace var namespace
|
||||
@Namespace var routerecorderscope
|
||||
@State var timeElapsed: TimerDisplayObject = TimerDisplayObject()
|
||||
@State var timerDisplay = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
|
||||
@State var recording: RouteEntity?
|
||||
@State var color: Color = .blue
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
VStack {
|
||||
VStack {
|
||||
Map(position: $position, scope: routerecorderscope) {
|
||||
UserAnnotation()
|
||||
// ForEach(locations, id: \.id) { location in
|
||||
// Marker(location.name, systemImage: location.icon, coordinate: location.location)
|
||||
// .tint(location.colour)
|
||||
// }
|
||||
}
|
||||
ZStack {
|
||||
Map(position: $position, scope: routerecorderscope) {
|
||||
UserAnnotation()
|
||||
/// Route Lines
|
||||
let lineCoords = locationsHandler.locationsArray.compactMap({(position) -> CLLocationCoordinate2D in
|
||||
return position.coordinate
|
||||
})
|
||||
|
||||
let gradient = LinearGradient(
|
||||
colors: [color],
|
||||
startPoint: .leading, endPoint: .trailing
|
||||
)
|
||||
let dashed = StrokeStyle(
|
||||
lineWidth: 3,
|
||||
lineCap: .round, lineJoin: .round, dash: [10, 10]
|
||||
)
|
||||
MapPolyline(coordinates: lineCoords)
|
||||
.stroke(gradient, style: dashed)
|
||||
|
||||
}
|
||||
.mapScope(routerecorderscope)
|
||||
.mapControls {
|
||||
MapUserLocationButton()
|
||||
MapCompass()
|
||||
MapScaleView()
|
||||
MapPitchToggle()
|
||||
}
|
||||
.mapStyle(.hybrid(elevation: .realistic, showsTraffic: true))
|
||||
.transition(.slide)
|
||||
.mapControlVisibility(.visible)
|
||||
.safeAreaInset(edge: .bottom) {
|
||||
ZStack {
|
||||
VStack {
|
||||
HStack(spacing: 10) {
|
||||
Spacer()
|
||||
if isTimerRunning {
|
||||
Button {
|
||||
isShowingDetails = true
|
||||
isTimerRunning = false
|
||||
} label: {
|
||||
Image(systemName: "pause.fill")
|
||||
.frame(width: 60, height: 60)
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
.buttonBorderShape(.circle)
|
||||
.matchedGeometryEffect(id: "Pause Button", in: namespace)
|
||||
} else {
|
||||
Button {
|
||||
isShowingDetails = true
|
||||
isTimerRunning = true
|
||||
timeElapsed.seconds -= 1
|
||||
} label: {
|
||||
Image(systemName: "play.fill")
|
||||
.frame(width: 60, height: 60)
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
.buttonBorderShape(.circle)
|
||||
.matchedGeometryEffect(id: "Play Button", in: namespace)
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
.onReceive(timerDisplay) { _ in
|
||||
if isTimerRunning {
|
||||
timeElapsed.seconds += 1
|
||||
if timeElapsed.seconds == 60 {
|
||||
timeElapsed.seconds = 0
|
||||
timeElapsed.minutes += 1
|
||||
if timeElapsed.minutes == 60 {
|
||||
timeElapsed.minutes = 0
|
||||
timeElapsed.hours += 1
|
||||
}
|
||||
}
|
||||
.mapStyle(mapStyle)
|
||||
}
|
||||
.ignoresSafeArea(.all, edges: [.top, .leading, .trailing])
|
||||
.mapScope(routerecorderscope)
|
||||
.safeAreaInset(edge: .bottom) {
|
||||
ZStack {
|
||||
VStack {
|
||||
HStack(spacing: 10) {
|
||||
Spacer()
|
||||
|
||||
Button {
|
||||
isShowingDetails = true
|
||||
} label: {
|
||||
Image(systemName: locationsHandler.isRecording ? "record.circle.fill" : "record.circle")
|
||||
.font(.system(size: 72))
|
||||
.symbolRenderingMode(.multicolor)
|
||||
.foregroundColor(.red)
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
.foregroundColor(.red)
|
||||
.buttonBorderShape(.circle)
|
||||
.matchedGeometryEffect(id: "Details Button", in: namespace)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
.sheet(isPresented: $isShowingDetails) {
|
||||
NavigationStack {
|
||||
VStack {
|
||||
HStack {
|
||||
Text(timeElapsed.display)
|
||||
.font(.largeTitle)
|
||||
Text("Time Elapseed")
|
||||
.font(.callout)
|
||||
.padding()
|
||||
}
|
||||
.sheet(isPresented: $isShowingDetails) {
|
||||
NavigationStack {
|
||||
VStack {
|
||||
if locationsHandler.isRecording {
|
||||
HStack (alignment: .center) {
|
||||
Image(systemName: "record.circle.fill")
|
||||
.symbolRenderingMode(.multicolor)
|
||||
.font(.title)
|
||||
.foregroundColor(.red)
|
||||
Text("Recording route")
|
||||
.font(.title)
|
||||
Spacer()
|
||||
Text("\(locationsHandler.count)")
|
||||
.foregroundColor(.red)
|
||||
.font(.title2)
|
||||
}
|
||||
.padding()
|
||||
} else if locationsHandler.isRecordingPaused {
|
||||
HStack (alignment: .center) {
|
||||
|
||||
Image(systemName: "playpause")
|
||||
.symbolRenderingMode(.multicolor)
|
||||
.font(.title3)
|
||||
.foregroundColor(.red)
|
||||
Text("Route recording paused")
|
||||
.font(.title)
|
||||
}
|
||||
.padding(.top)
|
||||
}
|
||||
|
||||
if locationsHandler.isRecording || locationsHandler.isRecordingPaused {
|
||||
Divider()
|
||||
VStack(alignment: .leading) {
|
||||
if let lastLocation = locationsHandler.locationsArray.last {
|
||||
|
||||
let horizontalAccuracy = Measurement(value: lastLocation.horizontalAccuracy, unit: UnitLength.meters)
|
||||
let verticalAccuracy = Measurement(value: lastLocation.verticalAccuracy, unit: UnitLength.meters)
|
||||
let altitiude = Measurement(value: lastLocation.altitude, unit: UnitLength.meters)
|
||||
let speed = Measurement(value: lastLocation.speed, unit: UnitSpeed.kilometersPerHour)
|
||||
List {
|
||||
Label("Coordinate \(String(format: "%.5f", lastLocation.coordinate.latitude)), \(String(format: "%.5f", lastLocation.coordinate.longitude))", systemImage: "mappin")
|
||||
.textSelection(.enabled)
|
||||
Label("Horizontal Accuracy \(horizontalAccuracy.formatted())", systemImage: "scope")
|
||||
if lastLocation.verticalAccuracy > 0 {
|
||||
Label("Altitude \(altitiude.formatted())", systemImage: "mountain.2")
|
||||
}
|
||||
Label("Vertical Accuracy \(verticalAccuracy.formatted())", systemImage: "lines.measurement.vertical")
|
||||
Label("Satellites Estimate \(LocationHelper.satsInView)", systemImage: "sparkles")
|
||||
Label("\(locationsHandler.isStationary ? "Moving" : "Stationary")", systemImage: locationsHandler.isStationary ? "figure.walk.motion" : "figure.stand")
|
||||
if lastLocation.speedAccuracy > 0 {
|
||||
Label("Speed \(speed.formatted())", systemImage: "speedometer")
|
||||
}
|
||||
if lastLocation.courseAccuracy > 0 {
|
||||
/// Heading
|
||||
let degrees = Angle.degrees(Double(lastLocation.course))
|
||||
Label {
|
||||
let heading = Measurement(value: degrees.degrees, unit: UnitAngle.degrees)
|
||||
/// Text("Heading: \(heading.formatted())")
|
||||
Text("Heading \(String(format: "%.2f", lastLocation.course))°")
|
||||
.foregroundColor(.primary)
|
||||
} icon: {
|
||||
Image(systemName: "location.circle")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.frame(width: 35)
|
||||
.rotationEffect(degrees)
|
||||
}
|
||||
HStack {
|
||||
VStack {
|
||||
Text(locationsHandler.recordingStarted ?? Date(), style: .timer)
|
||||
.font(.title)
|
||||
.fixedSize()
|
||||
Text("Time")
|
||||
.font(.callout)
|
||||
.fixedSize()
|
||||
}
|
||||
.padding(.horizontal)
|
||||
Divider()
|
||||
VStack {
|
||||
let distance = Measurement(value: locationsHandler.distanceTraveled, unit: UnitLength.meters)
|
||||
Text("\(distance.formatted())")
|
||||
.font(.title)
|
||||
.fixedSize()
|
||||
Text("Distance")
|
||||
.font(.callout)
|
||||
.fixedSize()
|
||||
}
|
||||
.padding(.horizontal)
|
||||
Divider()
|
||||
VStack {
|
||||
let gain = Measurement(value: locationsHandler.elevationGain, unit: UnitLength.meters)
|
||||
Text(gain.formatted())
|
||||
.font(.title)
|
||||
Text("Elev. Gain")
|
||||
.font(.callout)
|
||||
}
|
||||
.padding(.horizontal)
|
||||
}
|
||||
.frame(maxHeight: 90)
|
||||
}
|
||||
Divider()
|
||||
VStack(alignment: .leading) {
|
||||
List {
|
||||
GPSStatus(largeFont: .body, smallFont: .callout)
|
||||
}
|
||||
.listStyle(.plain)
|
||||
HStack {
|
||||
Spacer()
|
||||
if !locationsHandler.isRecording && !locationsHandler.isRecordingPaused {
|
||||
/// We are not recording or paused, show start recording button
|
||||
Button {
|
||||
locationsHandler.isRecording = true
|
||||
locationsHandler.count = 0
|
||||
locationsHandler.distanceTraveled = 0.0
|
||||
locationsHandler.elevationGain = 0.0
|
||||
locationsHandler.locationsArray.removeAll()
|
||||
locationsHandler.recordingStarted = Date()
|
||||
let newRoute = RouteEntity(context: context)
|
||||
newRoute.name = String("Route Recording")
|
||||
newRoute.id = Int32.random(in: Int32(Int8.max) ... Int32.max)
|
||||
newRoute.color = Int64(UIColor.random.hex)
|
||||
newRoute.date = Date()
|
||||
newRoute.enabled = false
|
||||
color = Color(UIColor(hex: UInt32(newRoute.color)))
|
||||
self.recording = newRoute
|
||||
do {
|
||||
try context.save()
|
||||
print("💾 Saved a new route")
|
||||
} catch {
|
||||
context.rollback()
|
||||
let nsError = error as NSError
|
||||
print("💥 Error Saving RouteEntity from the Route Recorder \(nsError)")
|
||||
}
|
||||
} label: {
|
||||
Label("start", systemImage: "play")
|
||||
}
|
||||
.listStyle(.plain)
|
||||
.buttonStyle(.bordered)
|
||||
.buttonBorderShape(.capsule)
|
||||
.controlSize(.large)
|
||||
.padding(.bottom)
|
||||
|
||||
} else if locationsHandler.isRecording {
|
||||
/// We are recording show pause button
|
||||
Button {
|
||||
locationsHandler.isRecording = false
|
||||
locationsHandler.isRecordingPaused = true
|
||||
} label: {
|
||||
Label("pause", systemImage: "pause")
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
.buttonBorderShape(.capsule)
|
||||
.controlSize(.large)
|
||||
.padding(.bottom)
|
||||
} else if locationsHandler.isRecordingPaused {
|
||||
/// We are paused show resume button
|
||||
Button {
|
||||
locationsHandler.isRecording = true
|
||||
locationsHandler.isRecordingPaused = false
|
||||
} label: {
|
||||
Label("resume", systemImage: "playpause")
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
.buttonBorderShape(.capsule)
|
||||
.controlSize(.large)
|
||||
.padding(.bottom)
|
||||
}
|
||||
|
||||
if locationsHandler.isRecording || locationsHandler.isRecordingPaused {
|
||||
/// We are recording or paused, show finish button
|
||||
Button {
|
||||
locationsHandler.isRecording = false
|
||||
locationsHandler.isRecordingPaused = false
|
||||
locationsHandler.distanceTraveled = 0.0
|
||||
locationsHandler.elevationGain = 0.0
|
||||
locationsHandler.locationsArray.removeAll()
|
||||
locationsHandler.recordingStarted = nil
|
||||
if let rec = recording {
|
||||
rec.enabled = true
|
||||
context.refresh(rec, mergeChanges:true)
|
||||
}
|
||||
|
||||
do {
|
||||
try context.save()
|
||||
print("💾 Saved a route finish")
|
||||
} catch {
|
||||
context.rollback()
|
||||
let nsError = error as NSError
|
||||
print("💥 Error Saving RouteEntity from the Route Recorder \(nsError)")
|
||||
}
|
||||
} label: {
|
||||
Label("finish", systemImage: "flag.checkered")
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
.buttonBorderShape(.capsule)
|
||||
.controlSize(.large)
|
||||
.padding(.bottom)
|
||||
}
|
||||
#if targetEnvironment(macCatalyst)
|
||||
Button(role: .cancel) {
|
||||
isShowingDetails = false
|
||||
} label: {
|
||||
Label("close", systemImage: "xmark")
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
.buttonBorderShape(.capsule)
|
||||
.controlSize(.large)
|
||||
.padding(.bottom)
|
||||
#endif
|
||||
Spacer()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
.presentationDetents([.fraction(0.30), .fraction(0.65)])
|
||||
.presentationDragIndicator(.hidden)
|
||||
.interactiveDismissDisabled(false)
|
||||
.onChange(of: locationsHandler.locationsArray.last) { newLoc in
|
||||
if locationsHandler.isRecording {
|
||||
if let loc = newLoc {
|
||||
if recording != nil {
|
||||
let locationEntity = LocationEntity(context: context)
|
||||
locationEntity.routeLocation = recording
|
||||
locationEntity.id = Int32(locationsHandler.count)
|
||||
locationEntity.altitude = Int32(loc.altitude)
|
||||
locationEntity.heading = Int32(loc.course)
|
||||
locationEntity.speed = Int32(loc.speed)
|
||||
locationEntity.latitudeI = Int32(loc.coordinate.latitude * 1e7)
|
||||
locationEntity.longitudeI = Int32(loc.coordinate.longitude * 1e7)
|
||||
do {
|
||||
try context.save()
|
||||
print("💾 Saved a new route location")
|
||||
//print("💾 Updated Canned Messages Messages For: \(fetchedNode[0].num)")
|
||||
} catch {
|
||||
context.rollback()
|
||||
let nsError = error as NSError
|
||||
print("💥 Error Saving LocationEntity from the Route Recorder \(nsError)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.presentationDetents([.fraction(0.6)])
|
||||
.presentationDragIndicator(.visible)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -135,6 +135,7 @@ struct Routes: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
.badge(route.locations?.count ?? 0)
|
||||
.swipeActions {
|
||||
Button(role: .destructive) {
|
||||
context.delete(route)
|
||||
|
|
@ -147,6 +148,7 @@ struct Routes: View {
|
|||
Label("delete", systemImage: "trash")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
.listStyle(.plain)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -313,16 +313,15 @@ struct Settings: View {
|
|||
}
|
||||
}
|
||||
.onAppear {
|
||||
if self.bleManager.context == nil {
|
||||
self.bleManager.context = context
|
||||
}
|
||||
self.preferredNodeNum = UserDefaults.preferredPeripheralNum
|
||||
self.selectedNode = Int(bleManager.connectedPeripheral != nil ? UserDefaults.preferredPeripheralNum : 0)
|
||||
if selectedNode == 0 {
|
||||
self.selectedNode = Int(bleManager.connectedPeripheral != nil ? UserDefaults.preferredPeripheralNum : 0)
|
||||
}
|
||||
}
|
||||
.listStyle(GroupedListStyle())
|
||||
.navigationTitle("settings")
|
||||
.navigationBarItems(leading:
|
||||
MeshtasticLogo()
|
||||
MeshtasticLogo()
|
||||
)
|
||||
}
|
||||
detail: {
|
||||
|
|
|
|||
|
|
@ -190,7 +190,6 @@ struct UserConfig: View {
|
|||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
|
||||
})
|
||||
.onAppear {
|
||||
self.bleManager.context = context
|
||||
self.shortName = node?.user?.shortName ?? ""
|
||||
self.longName = node?.user?.longName ?? ""
|
||||
self.isLicensed = node?.user?.isLicensed ?? false
|
||||
|
|
|
|||
|
|
@ -84,6 +84,7 @@
|
|||
"encrypted"="Verschlüsselt";
|
||||
"external.notification"="Externe Benachrichtigung";
|
||||
"external.notification.config"="Einstellungen der externen Benachrichtigung";
|
||||
"finish"="Finish";
|
||||
"firmware.version"="Firmware Version";
|
||||
"firmware.version.unsupported"="Nicht unterstützte Firmware Version erkannt. Kann nicht verbinden.";
|
||||
"gas"="Gas";
|
||||
|
|
@ -213,6 +214,7 @@
|
|||
"on.boot"="Nur beim Starten";
|
||||
"options"="Optionen";
|
||||
"password"="Passwort";
|
||||
"pause"="Pause";
|
||||
"phone.gps"="Telefon GPS";
|
||||
"phone.gps.interval.description"="Wie häufig das Telefon den Standort an das Gerät sendet. Standortaktualisierungen an das Mesh werden vom Gerät verwaltet.";
|
||||
"position"="Position";
|
||||
|
|
@ -228,8 +230,10 @@
|
|||
"reply"="Antworten";
|
||||
"received.ack"="Empfangsbestätigung";
|
||||
"received.ack.real"="Recipient Ack";
|
||||
"resume"="Resume";
|
||||
"ringtone"="Ringtone";
|
||||
"ringtone.config"="Ringtone Config";
|
||||
"route.recorder"="Route Recorder";
|
||||
"routes"="Routes";
|
||||
"routing.acknowledged"="Bestätigt";
|
||||
"routing.noroute"="Keine Route";
|
||||
|
|
@ -264,6 +268,7 @@
|
|||
"set.region"="Setze LoRa Region";
|
||||
"standard"="Standard";
|
||||
"standard.muted"="Standard Muted";
|
||||
"start"="Start";
|
||||
"storeforward"="Store & Forward";
|
||||
"storeforward.config"="Store & Forward Config";
|
||||
"storeforward.heartbeat"="Send Heartbeat";
|
||||
|
|
|
|||
|
|
@ -88,6 +88,7 @@
|
|||
"encrypted"="Encrypted";
|
||||
"external.notification"="External Notification";
|
||||
"external.notification.config"="External Notification Config";
|
||||
"finish"="Finish";
|
||||
"firmware.version"="Firmware Version";
|
||||
"firmware.version.unsupported"="Unsupported Firmware Version Detected, unable to connect to device.";
|
||||
"gas"="Gas";
|
||||
|
|
@ -217,6 +218,7 @@
|
|||
"on.boot"="On Boot Only";
|
||||
"options"="Options";
|
||||
"password"="Password";
|
||||
"pause"="Pause";
|
||||
"phone.gps"="Phone GPS";
|
||||
"phone.gps.interval.description"="How frequently your phone will send your location to the device, location updates to the mesh are managed by the device.";
|
||||
"position"="Position";
|
||||
|
|
@ -232,8 +234,10 @@
|
|||
"reboot.node"="Reboot node?";
|
||||
"received.ack"="Received Ack";
|
||||
"received.ack.real"="Recipient Ack";
|
||||
"resume"="Resume";
|
||||
"ringtone"="Ringtone";
|
||||
"ringtone.config"="Ringtone Config";
|
||||
"route.recorder"="Route Recorder";
|
||||
"routes"="Routes";
|
||||
"routing.acknowledged"="Acknowledged";
|
||||
"routing.noroute"="No Route";
|
||||
|
|
@ -268,6 +272,7 @@
|
|||
"set.region"="Set LoRa Region";
|
||||
"standard"="Standard";
|
||||
"standard.muted"="Standard Muted";
|
||||
"start"="Start";
|
||||
"storeforward"="Store & Forward";
|
||||
"storeforward.config"="Store & Forward Config";
|
||||
"storeforward.heartbeat"="Send Heartbeat";
|
||||
|
|
|
|||
|
|
@ -86,6 +86,7 @@
|
|||
"encrypted"="Zaszyfrowany";
|
||||
"external.notification"="Zewnętrzne Powiadomienie";
|
||||
"external.notification.config"="Konfiguracja Zewnętrznego Powiadomienia";
|
||||
"finish"="Finish";
|
||||
"firmware.version"="Wersja Oprogramowania";
|
||||
"firmware.version.unsupported"="Wykryto nieobsługiwany wersję oprogramowania, brak możliwości połączenia z urządzeniem.";
|
||||
"gas"="Gaz";
|
||||
|
|
@ -214,6 +215,7 @@
|
|||
"on.boot"="Tylko przy uruchomieniu";
|
||||
"options"="Opcje";
|
||||
"password"="Hasło";
|
||||
"pause"="Pause";
|
||||
"phone.gps"="GPS telefonu";
|
||||
"phone.gps.interval.description"="Jak często Twój telefon będzie wysyłał swoją lokalizację do urządzenia, aktualizacje lokalizacji w sieci są zarządzane przez urządzenie.";
|
||||
"position"="Pozycja";
|
||||
|
|
@ -229,8 +231,10 @@
|
|||
"reboot.node"="Uruchomić ponownie węzeł?";
|
||||
"received.ack"="Odebrano potwierdzenie";
|
||||
"received.ack.real"="Odbiorca potwierdzenia";
|
||||
"resume"="Resume";
|
||||
"ringtone"="Dzwonek";
|
||||
"ringtone.config"="Konfiguracja dzwonka";
|
||||
"route.recorder"="Route Recorder";
|
||||
"routes"="Routes";
|
||||
"routing.acknowledged"="Potwierdzono";
|
||||
"routing.noroute"="Brak trasy";
|
||||
|
|
@ -265,6 +269,7 @@
|
|||
"set.region"="Ustaw region LoRa";
|
||||
"standard"="Standardowy";
|
||||
"standard.muted"="Standardowy wyłączony";
|
||||
"start"="Start";
|
||||
"storeforward"="Store & Forward";
|
||||
"storeforward.config"="Store & Forward Config";
|
||||
"storeforward.heartbeat"="Send Heartbeat";
|
||||
|
|
|
|||
|
|
@ -84,6 +84,7 @@
|
|||
"encrypted"="加密";
|
||||
"external.notification"="外部通知";
|
||||
"external.notification.config"="外部通知配置";
|
||||
"finish"="Finish";
|
||||
"firmware.version"="固件版本";
|
||||
"firmware.version.unsupported"="检测到不支持的固件版本,无法连接到电台。";
|
||||
"gas"="Gas";
|
||||
|
|
@ -213,6 +214,7 @@
|
|||
"on.boot"="仅在启动时";
|
||||
"options"="选项";
|
||||
"password"="密码";
|
||||
"pause"="Pause";
|
||||
"phone.gps"="手机 GPS";
|
||||
"phone.gps.interval.description"="电台通过手机获取定位的时间间隔,但是向 Mesh 网络中刷新定位的时间间隔由电台控制。";
|
||||
"position"="定位";
|
||||
|
|
@ -228,8 +230,10 @@
|
|||
"reboot.node"="重启节点?";
|
||||
"received.ack"="收到确认";
|
||||
"received.ack.real"="收件人确认";
|
||||
"resume"="Resume";
|
||||
"ringtone"="铃声";
|
||||
"ringtone.config"="铃声设置";
|
||||
"route.recorder"="Route Recorder";
|
||||
"routes"="Routes";
|
||||
"routing.acknowledged"="确认";
|
||||
"routing.noroute"="找不到目标";
|
||||
|
|
@ -264,6 +268,7 @@
|
|||
"set.region"="设置 LoRa 区域";
|
||||
"standard"="标准";
|
||||
"standard.muted"="标准静音";
|
||||
"start"="Start";
|
||||
"ssid"="SSID";
|
||||
"storeforward"="储存 & 转发";
|
||||
"storeforward.config"="储存 & 转发设置";
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue