Merge branch 'meshtastic:main' into main
2
.gitmodules
vendored
|
|
@ -1,3 +1,3 @@
|
|||
[submodule "protobufs"]
|
||||
path = protobufs
|
||||
url = http://github.com/meshtastic/protobufs
|
||||
url = https://github.com/meshtastic/protobufs.git
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@
|
|||
DD2AD8A8296D2DF9001FF0E7 /* MapViewSwiftUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2AD8A7296D2DF9001FF0E7 /* MapViewSwiftUI.swift */; };
|
||||
DD33DB622B3D27C7003E1EA0 /* FirmwareApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD33DB612B3D27C7003E1EA0 /* FirmwareApi.swift */; };
|
||||
DD3501892852FC3B000FC853 /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3501882852FC3B000FC853 /* Settings.swift */; };
|
||||
DD354FD92BD96A0B0061A25F /* IAQScale.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD354FD82BD96A0B0061A25F /* IAQScale.swift */; };
|
||||
DD3619152B1EF9F900C41C8C /* LocationsHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3619142B1EF9F900C41C8C /* LocationsHandler.swift */; };
|
||||
DD3CC6B528E33FD100FA9159 /* ShareChannels.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3CC6B428E33FD100FA9159 /* ShareChannels.swift */; };
|
||||
DD3CC6BC28E366DF00FA9159 /* Meshtastic.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = DD3CC6BA28E366DF00FA9159 /* Meshtastic.xcdatamodeld */; };
|
||||
|
|
@ -87,7 +88,7 @@
|
|||
DD5E5211298EE33B00D21B61 /* remote_hardware.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5E51FF298EE33B00D21B61 /* remote_hardware.pb.swift */; };
|
||||
DD5E5212298EE33B00D21B61 /* apponly.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5E5200298EE33B00D21B61 /* apponly.pb.swift */; };
|
||||
DD5E5213298EE33B00D21B61 /* deviceonly.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5E5201298EE33B00D21B61 /* deviceonly.pb.swift */; };
|
||||
DD5E523F298F5A9E00D21B61 /* AirQualityIndexCompact.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5E523E298F5A9E00D21B61 /* AirQualityIndexCompact.swift */; };
|
||||
DD5E523F298F5A9E00D21B61 /* AirQualityIndex.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5E523E298F5A9E00D21B61 /* AirQualityIndex.swift */; };
|
||||
DD6193752862F6E600E59241 /* ExternalNotificationConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6193742862F6E600E59241 /* ExternalNotificationConfig.swift */; };
|
||||
DD6193772862F90F00E59241 /* CannedMessagesConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6193762862F90F00E59241 /* CannedMessagesConfig.swift */; };
|
||||
DD6193792863875F00E59241 /* SerialConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6193782863875F00E59241 /* SerialConfig.swift */; };
|
||||
|
|
@ -292,10 +293,12 @@
|
|||
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>"; };
|
||||
DD31B04D2BDC6FD30024FA63 /* MeshtasticDataModelV 36.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 36.xcdatamodel"; sourceTree = "<group>"; };
|
||||
DD31EC492B7F18B7006A3995 /* he */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = he; path = he.lproj/Localizable.strings; 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>"; };
|
||||
DD354FD82BD96A0B0061A25F /* IAQScale.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IAQScale.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>"; };
|
||||
DD398EBD2B93F640002B4C51 /* MeshtasticDataModelV 29.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 29.xcdatamodel"; sourceTree = "<group>"; };
|
||||
|
|
@ -339,7 +342,7 @@
|
|||
DD5E51FF298EE33B00D21B61 /* remote_hardware.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = remote_hardware.pb.swift; sourceTree = "<group>"; };
|
||||
DD5E5200298EE33B00D21B61 /* apponly.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = apponly.pb.swift; sourceTree = "<group>"; };
|
||||
DD5E5201298EE33B00D21B61 /* deviceonly.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = deviceonly.pb.swift; sourceTree = "<group>"; };
|
||||
DD5E523E298F5A9E00D21B61 /* AirQualityIndexCompact.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirQualityIndexCompact.swift; sourceTree = "<group>"; };
|
||||
DD5E523E298F5A9E00D21B61 /* AirQualityIndex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirQualityIndex.swift; sourceTree = "<group>"; };
|
||||
DD6193742862F6E600E59241 /* ExternalNotificationConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExternalNotificationConfig.swift; sourceTree = "<group>"; };
|
||||
DD6193762862F90F00E59241 /* CannedMessagesConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CannedMessagesConfig.swift; sourceTree = "<group>"; };
|
||||
DD6193782863875F00E59241 /* SerialConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SerialConfig.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -649,9 +652,11 @@
|
|||
DD5E523D298F5A7D00D21B61 /* Weather */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DD5E523E298F5A9E00D21B61 /* AirQualityIndexCompact.swift */,
|
||||
DD5E523E298F5A9E00D21B61 /* AirQualityIndex.swift */,
|
||||
DDFEB3BA29900C1200EE7472 /* CurrentConditionsCompact.swift */,
|
||||
DDA9515D2BC6F56F00CEA535 /* IndoorAirQuality.swift */,
|
||||
DD41A61429AB0035003C5A37 /* NodeWeatherForecast.swift */,
|
||||
DD354FD82BD96A0B0061A25F /* IAQScale.swift */,
|
||||
);
|
||||
path = Weather;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -921,7 +926,6 @@
|
|||
DDB75A1D2A0B0CD0006ED576 /* LoRaSignalStrengthIndicator.swift */,
|
||||
DDB75A202A12B954006ED576 /* LoRaSignalStrength.swift */,
|
||||
DDB75A222A13CDA9006ED576 /* BatteryLevelCompact.swift */,
|
||||
DDA9515D2BC6F56F00CEA535 /* IndoorAirQuality.swift */,
|
||||
);
|
||||
path = Helpers;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -1204,7 +1208,7 @@
|
|||
DD836AE726F6B38600ABCC23 /* Connect.swift in Sources */,
|
||||
DD0E20FD2B87090400F2D100 /* clientonly.pb.swift in Sources */,
|
||||
D93069082B81DF040066FBC8 /* SaveConfigButton.swift in Sources */,
|
||||
DD5E523F298F5A9E00D21B61 /* AirQualityIndexCompact.swift in Sources */,
|
||||
DD5E523F298F5A9E00D21B61 /* AirQualityIndex.swift in Sources */,
|
||||
DD964FBF296E76EF007C176F /* WaypointFormMapKit.swift in Sources */,
|
||||
DD3501892852FC3B000FC853 /* Settings.swift in Sources */,
|
||||
DDDC22382BA92344002C44F1 /* MeshMapContent.swift in Sources */,
|
||||
|
|
@ -1239,6 +1243,7 @@
|
|||
DD964FBD296E6B01007C176F /* EmojiOnlyTextField.swift in Sources */,
|
||||
DD8169FF272476C700F4AB02 /* LogDocument.swift in Sources */,
|
||||
DDC94FC129CE063B0082EA6E /* BatteryLevel.swift in Sources */,
|
||||
DD354FD92BD96A0B0061A25F /* IAQScale.swift in Sources */,
|
||||
DDDB445429F8AD1600EE2349 /* Data.swift in Sources */,
|
||||
DDDB26462AACC0B7003AFCB7 /* NodeInfoItem.swift in Sources */,
|
||||
DD2AD8A8296D2DF9001FF0E7 /* MapViewSwiftUI.swift in Sources */,
|
||||
|
|
@ -1578,7 +1583,7 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.3.6;
|
||||
MARKETING_VERSION = 2.3.8;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
|
|
@ -1612,7 +1617,7 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.3.6;
|
||||
MARKETING_VERSION = 2.3.8;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
|
|
@ -1685,7 +1690,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.3.6;
|
||||
MARKETING_VERSION = 2.3.8;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
|
|
@ -1718,7 +1723,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.3.6;
|
||||
MARKETING_VERSION = 2.3.8;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
|
|
@ -1820,6 +1825,7 @@
|
|||
DD3CC6BA28E366DF00FA9159 /* Meshtastic.xcdatamodeld */ = {
|
||||
isa = XCVersionGroup;
|
||||
children = (
|
||||
DD31B04D2BDC6FD30024FA63 /* MeshtasticDataModelV 36.xcdatamodel */,
|
||||
DD268D8C2BCC7D11008073AE /* MeshtasticDataModelV 35.xcdatamodel */,
|
||||
DDDBC87C2BC65682001E8DF7 /* MeshtasticDataModelV 34.xcdatamodel */,
|
||||
DDF45C382BC46B16005ED5F2 /* MeshtasticDataModelV33.xcdatamodel */,
|
||||
|
|
@ -1856,7 +1862,7 @@
|
|||
DD5D0A9A2931AD6B00F7EA61 /* MeshtasticDataModelV2.xcdatamodel */,
|
||||
DD3CC6BB28E366DF00FA9159 /* MeshtasticDataModel.xcdatamodel */,
|
||||
);
|
||||
currentVersion = DD268D8C2BCC7D11008073AE /* MeshtasticDataModelV 35.xcdatamodel */;
|
||||
currentVersion = DD31B04D2BDC6FD30024FA63 /* MeshtasticDataModelV 36.xcdatamodel */;
|
||||
name = Meshtastic.xcdatamodeld;
|
||||
path = Meshtastic/Meshtastic.xcdatamodeld;
|
||||
sourceTree = "<group>";
|
||||
|
|
|
|||
23
Meshtastic/Assets.xcassets/CANARY1.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "play_store_icon_114px-2.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "play_store_icon_114px-3.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "play_store_icon_114px-4.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
23
Meshtastic/Assets.xcassets/CDEBYTEEORAS3.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "play_store_icon_114px-2.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "play_store_icon_114px-3.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "play_store_icon_114px-4.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
23
Meshtastic/Assets.xcassets/CHATTER2.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "play_store_icon_114px-2.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "play_store_icon_114px-3.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "play_store_icon_114px-4.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
Meshtastic/Assets.xcassets/CHATTER2.imageset/play_store_icon_114px-2.png
vendored
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
Meshtastic/Assets.xcassets/CHATTER2.imageset/play_store_icon_114px-3.png
vendored
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
Meshtastic/Assets.xcassets/CHATTER2.imageset/play_store_icon_114px-4.png
vendored
Normal file
|
After Width: | Height: | Size: 12 KiB |
23
Meshtastic/Assets.xcassets/ESP32S3PICO.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "play_store_icon_114px-2.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "play_store_icon_114px-3.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "play_store_icon_114px-4.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
Meshtastic/Assets.xcassets/ESP32S3PICO.imageset/play_store_icon_114px-2.png
vendored
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
Meshtastic/Assets.xcassets/ESP32S3PICO.imageset/play_store_icon_114px-3.png
vendored
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
Meshtastic/Assets.xcassets/ESP32S3PICO.imageset/play_store_icon_114px-4.png
vendored
Normal file
|
After Width: | Height: | Size: 12 KiB |
|
|
@ -1,17 +1,17 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "play_store_icon_114px-2.png",
|
||||
"filename" : "Paper-Meshtastic-2 copy.jpg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "play_store_icon_114px-3.png",
|
||||
"filename" : "Paper-Meshtastic-2 copy 1.jpg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "play_store_icon_114px-4.png",
|
||||
"filename" : "Paper-Meshtastic-2 copy 2.jpg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
|
|
|
|||
BIN
Meshtastic/Assets.xcassets/HELTECWIRELESSPAPER.imageset/Paper-Meshtastic-2 copy 1.jpg
vendored
Normal file
|
After Width: | Height: | Size: 66 KiB |
BIN
Meshtastic/Assets.xcassets/HELTECWIRELESSPAPER.imageset/Paper-Meshtastic-2 copy 2.jpg
vendored
Normal file
|
After Width: | Height: | Size: 66 KiB |
BIN
Meshtastic/Assets.xcassets/HELTECWIRELESSPAPER.imageset/Paper-Meshtastic-2 copy.jpg
vendored
Normal file
|
After Width: | Height: | Size: 66 KiB |
23
Meshtastic/Assets.xcassets/HELTECWIRELESSPAPERV10.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "Paper-Meshtastic-2 copy.jpg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "Paper-Meshtastic-2 copy 1.jpg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "Paper-Meshtastic-2 copy 2.jpg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
Meshtastic/Assets.xcassets/HELTECWIRELESSPAPERV10.imageset/Paper-Meshtastic-2 copy 1.jpg
vendored
Normal file
|
After Width: | Height: | Size: 66 KiB |
BIN
Meshtastic/Assets.xcassets/HELTECWIRELESSPAPERV10.imageset/Paper-Meshtastic-2 copy 2.jpg
vendored
Normal file
|
After Width: | Height: | Size: 66 KiB |
BIN
Meshtastic/Assets.xcassets/HELTECWIRELESSPAPERV10.imageset/Paper-Meshtastic-2 copy.jpg
vendored
Normal file
|
After Width: | Height: | Size: 66 KiB |
|
|
@ -1,17 +1,17 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "play_store_icon_114px-2.png",
|
||||
"filename" : "images.jpeg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "play_store_icon_114px-3.png",
|
||||
"filename" : "images 1.jpeg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "play_store_icon_114px-4.png",
|
||||
"filename" : "images 2.jpeg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
|
|
|
|||
BIN
Meshtastic/Assets.xcassets/HELTECWIRELESSTRACKER.imageset/images 1.jpeg
vendored
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
BIN
Meshtastic/Assets.xcassets/HELTECWIRELESSTRACKER.imageset/images 2.jpeg
vendored
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
BIN
Meshtastic/Assets.xcassets/HELTECWIRELESSTRACKER.imageset/images.jpeg
vendored
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
23
Meshtastic/Assets.xcassets/HELTECWIRELESSTRACKERV10.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "images.jpeg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "images 1.jpeg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "images 2.jpeg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
Meshtastic/Assets.xcassets/HELTECWIRELESSTRACKERV10.imageset/images 1.jpeg
vendored
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
BIN
Meshtastic/Assets.xcassets/HELTECWIRELESSTRACKERV10.imageset/images 2.jpeg
vendored
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
BIN
Meshtastic/Assets.xcassets/HELTECWIRELESSTRACKERV10.imageset/images.jpeg
vendored
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
23
Meshtastic/Assets.xcassets/TDLORAC.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "play_store_icon_114px-2.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "play_store_icon_114px-2 1.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "play_store_icon_114px-2 2.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
Meshtastic/Assets.xcassets/TDLORAC.imageset/play_store_icon_114px-2 1.png
vendored
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
Meshtastic/Assets.xcassets/TDLORAC.imageset/play_store_icon_114px-2 2.png
vendored
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
Meshtastic/Assets.xcassets/TDLORAC.imageset/play_store_icon_114px-2.png
vendored
Normal file
|
After Width: | Height: | Size: 12 KiB |
23
Meshtastic/Assets.xcassets/TWCMESHV4.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "play_store_icon_114px-2.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "play_store_icon_114px-3.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "play_store_icon_114px-4.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
Meshtastic/Assets.xcassets/TWCMESHV4.imageset/play_store_icon_114px-2.png
vendored
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
Meshtastic/Assets.xcassets/TWCMESHV4.imageset/play_store_icon_114px-3.png
vendored
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
Meshtastic/Assets.xcassets/TWCMESHV4.imageset/play_store_icon_114px-4.png
vendored
Normal file
|
After Width: | Height: | Size: 12 KiB |
23
Meshtastic/Assets.xcassets/UNPHONE.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "UNPHONE.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "UNPHONE@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "UNPHONE@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
Meshtastic/Assets.xcassets/UNPHONE.imageset/UNPHONE.png
vendored
Normal file
|
After Width: | Height: | Size: 756 KiB |
BIN
Meshtastic/Assets.xcassets/UNPHONE.imageset/UNPHONE@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 756 KiB |
BIN
Meshtastic/Assets.xcassets/UNPHONE.imageset/UNPHONE@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 756 KiB |
23
Meshtastic/Assets.xcassets/WIPHONE.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "play_store_icon_114px-2.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "play_store_icon_114px-3.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "play_store_icon_114px-4.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
Meshtastic/Assets.xcassets/WIPHONE.imageset/play_store_icon_114px-2.png
vendored
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
Meshtastic/Assets.xcassets/WIPHONE.imageset/play_store_icon_114px-3.png
vendored
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
Meshtastic/Assets.xcassets/WIPHONE.imageset/play_store_icon_114px-4.png
vendored
Normal file
|
After Width: | Height: | Size: 12 KiB |
|
|
@ -100,8 +100,8 @@ enum GpsUpdateIntervals: Int, CaseIterable, Identifiable {
|
|||
}
|
||||
|
||||
enum GpsMode: Int, CaseIterable, Equatable {
|
||||
case disabled = 0
|
||||
case enabled = 1
|
||||
case disabled = 0
|
||||
case notPresent = 2
|
||||
|
||||
var id: Int { self.rawValue }
|
||||
|
|
|
|||
|
|
@ -8,6 +8,86 @@
|
|||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
enum Aqi: Int, CaseIterable, Identifiable {
|
||||
case good = 0
|
||||
case moderate = 1
|
||||
case sensitive = 2
|
||||
case unhealthy = 3
|
||||
case veryUnhealthy = 4
|
||||
case hazardous = 5
|
||||
|
||||
var id: Int { self.rawValue }
|
||||
var description: String {
|
||||
switch self {
|
||||
case .good:
|
||||
return "Good"
|
||||
case .moderate:
|
||||
return "Moderate"
|
||||
case .sensitive:
|
||||
return "Unhealthy for Sensitive Groups"
|
||||
case .unhealthy:
|
||||
return "Unhealthy"
|
||||
case .veryUnhealthy:
|
||||
return "Very Unhealthy"
|
||||
case .hazardous:
|
||||
return "Hazardous"
|
||||
}
|
||||
}
|
||||
var color: Color {
|
||||
switch self {
|
||||
case .good:
|
||||
return .green
|
||||
case .moderate:
|
||||
return .yellow
|
||||
case .sensitive:
|
||||
return .orange
|
||||
case .unhealthy:
|
||||
return .red
|
||||
case .veryUnhealthy:
|
||||
return .purple
|
||||
case .hazardous:
|
||||
return .magenta
|
||||
}
|
||||
}
|
||||
var range: Range<Int> {
|
||||
switch self {
|
||||
case .good:
|
||||
return Range(0...50)
|
||||
case .moderate:
|
||||
return Range(51...100)
|
||||
case .sensitive:
|
||||
return Range(101...150)
|
||||
case .unhealthy:
|
||||
return Range(151...200)
|
||||
case .veryUnhealthy:
|
||||
return Range(201...300)
|
||||
case .hazardous:
|
||||
return Range(301...500)
|
||||
}
|
||||
}
|
||||
|
||||
static func getAqi(for value: Int) -> Aqi {
|
||||
let aqi: Aqi
|
||||
switch value {
|
||||
case 0...50:
|
||||
aqi = .good
|
||||
case 51...100:
|
||||
aqi = .moderate
|
||||
case 101...150:
|
||||
aqi = .sensitive
|
||||
case 151...200:
|
||||
aqi = .unhealthy
|
||||
case 201...300:
|
||||
aqi = .veryUnhealthy
|
||||
case 301...500:
|
||||
aqi = .hazardous
|
||||
default:
|
||||
fatalError("Invalid int value")
|
||||
}
|
||||
return aqi
|
||||
}
|
||||
}
|
||||
|
||||
enum Iaq: Int, CaseIterable, Identifiable {
|
||||
case excellent = 0
|
||||
case good = 1
|
||||
|
|
@ -27,7 +107,7 @@ enum Iaq: Int, CaseIterable, Identifiable {
|
|||
case .lightlyPolluted:
|
||||
return "Lightly Polluted"
|
||||
case .moderatelyPolluted:
|
||||
return "Lightly Polluted"
|
||||
return "Moderately Polluted"
|
||||
case .heavilyPolluted:
|
||||
return "Heavily Polluted"
|
||||
case .severelyPolluted:
|
||||
|
|
@ -49,11 +129,30 @@ enum Iaq: Int, CaseIterable, Identifiable {
|
|||
case .heavilyPolluted:
|
||||
return .red
|
||||
case .severelyPolluted:
|
||||
return .purple
|
||||
return .magenta
|
||||
case .extremelyPolluted:
|
||||
return .brown
|
||||
}
|
||||
}
|
||||
|
||||
var range: Range<Int> {
|
||||
switch self {
|
||||
case .excellent:
|
||||
return Range(0...50)
|
||||
case .good:
|
||||
return Range(51...100)
|
||||
case .lightlyPolluted:
|
||||
return Range(101...150)
|
||||
case .moderatelyPolluted:
|
||||
return Range(151...200)
|
||||
case .heavilyPolluted:
|
||||
return Range(201...250)
|
||||
case .severelyPolluted:
|
||||
return Range(251...350)
|
||||
case .extremelyPolluted:
|
||||
return Range(351...500)
|
||||
}
|
||||
}
|
||||
static func getIaq(for value: Int) -> Iaq {
|
||||
let iaq: Iaq
|
||||
switch value {
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ func telemetryToCsvFile(telemetry: [TelemetryEntity], metricsType: Int) -> Strin
|
|||
}
|
||||
} else if metricsType == 1 {
|
||||
// Create Environment Telemetry Header
|
||||
csvString = "Temperature, Relative Humidity, Barometric Pressure, Indoor Air Quality, Gas Resistance, \("voltage".localized), \("current".localized), \("timestamp".localized)"
|
||||
csvString = "Temperature, Relative Humidity, Barometric Pressure, Indoor Air Quality, Gas Resistance, \("timestamp".localized)"
|
||||
for dm in telemetry {
|
||||
if dm.metricsType == 1 {
|
||||
csvString += "\n"
|
||||
|
|
@ -46,10 +46,6 @@ func telemetryToCsvFile(telemetry: [TelemetryEntity], metricsType: Int) -> Strin
|
|||
csvString += ", "
|
||||
csvString += String(dm.gasResistance)
|
||||
csvString += ", "
|
||||
csvString += String(dm.voltage)
|
||||
csvString += ", "
|
||||
csvString += String(dm.current)
|
||||
csvString += ", "
|
||||
csvString += dm.time?.formattedDate(format: dateFormatString) ?? "unknown.age".localized
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ extension Color {
|
|||
let brightness = ((components[0] * 299) + (components[1] * 587) + (components[2] * 114)) / 1000
|
||||
return (brightness > 0.5)
|
||||
}
|
||||
public static let magenta = Color(red: 0.50, green: 0.00, blue: 0.00)
|
||||
}
|
||||
|
||||
extension UIColor {
|
||||
|
|
|
|||
|
|
@ -18,4 +18,14 @@ extension ChannelEntity {
|
|||
let unreadMessages = allPrivateMessages.filter{ ($0 as AnyObject).read == false }
|
||||
return unreadMessages.count
|
||||
}
|
||||
|
||||
var protoBuf: Channel {
|
||||
var channel = Channel()
|
||||
channel.index = self.index
|
||||
channel.settings.name = self.name ?? ""
|
||||
channel.settings.psk = self.psk ?? Data()
|
||||
channel.role = Channel.Role(rawValue: Int(self.role)) ?? Channel.Role.secondary
|
||||
channel.settings.moduleSettings.positionPrecision = UInt32(self.positionPrecision)
|
||||
return channel
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@ extension UserDefaults {
|
|||
case enableSmartPosition
|
||||
case newNodeNotifications
|
||||
case lowBatteryNotifications
|
||||
case channelMessageNotifications
|
||||
case modemPreset
|
||||
case firmwareVersion
|
||||
case testIntEnum
|
||||
|
|
@ -146,6 +147,9 @@ extension UserDefaults {
|
|||
@UserDefault(.enableSmartPosition, defaultValue: false)
|
||||
static var enableSmartPosition: Bool
|
||||
|
||||
@UserDefault(.channelMessageNotifications, defaultValue: true)
|
||||
static var channelMessageNotifications: Bool
|
||||
|
||||
@UserDefault(.newNodeNotifications, defaultValue: true)
|
||||
static var newNodeNotifications: Bool
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
}
|
||||
}
|
||||
var context: NSManagedObjectContext?
|
||||
|
||||
static let shared = BLEManager()
|
||||
//var userSettings: UserSettings?
|
||||
private var centralManager: CBCentralManager!
|
||||
@Published var peripherals: [Peripheral] = []
|
||||
|
|
@ -151,12 +153,15 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
isConnecting = false
|
||||
isConnected = false
|
||||
isSubscribed = false
|
||||
self.connectedPeripheral = nil
|
||||
invalidVersion = false
|
||||
connectedVersion = "0.0.0"
|
||||
connectedPeripheral = nil
|
||||
if timeoutTimer != nil {
|
||||
timeoutTimer!.invalidate()
|
||||
}
|
||||
automaticallyReconnect = false
|
||||
stopScanning()
|
||||
startScanning()
|
||||
}
|
||||
|
||||
|
|
@ -542,10 +547,13 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
// Handle Any local only packets we get over BLE
|
||||
case .unknownApp:
|
||||
var nowKnown = false
|
||||
guard let ctx = context else {
|
||||
return
|
||||
}
|
||||
|
||||
// MyInfo from initial connection
|
||||
if context != nil && decodedInfo.myInfo.isInitialized && decodedInfo.myInfo.myNodeNum > 0 {
|
||||
let myInfo = myInfoPacket(myInfo: decodedInfo.myInfo, peripheralId: self.connectedPeripheral.id, context: context!)
|
||||
if decodedInfo.myInfo.isInitialized && decodedInfo.myInfo.myNodeNum > 0 {
|
||||
let myInfo = myInfoPacket(myInfo: decodedInfo.myInfo, peripheralId: self.connectedPeripheral.id, context: ctx)
|
||||
|
||||
if myInfo != nil {
|
||||
UserDefaults.preferredPeripheralNum = Int(myInfo?.myNodeNum ?? 0)
|
||||
|
|
@ -556,15 +564,13 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
tryClearExistingChannels()
|
||||
}
|
||||
// NodeInfo
|
||||
if decodedInfo.nodeInfo.num > 0 {// && !invalidVersion {
|
||||
if decodedInfo.nodeInfo.num > 0 {
|
||||
nowKnown = true
|
||||
let nodeInfo = nodeInfoPacket(nodeInfo: decodedInfo.nodeInfo, channel: decodedInfo.packet.channel, context: context!)
|
||||
|
||||
if nodeInfo != nil {
|
||||
if self.connectedPeripheral != nil && self.connectedPeripheral.num == nodeInfo?.num ?? -1 {
|
||||
if nodeInfo!.user != nil {
|
||||
connectedPeripheral.shortName = nodeInfo?.user?.shortName ?? "?"
|
||||
connectedPeripheral.longName = nodeInfo?.user?.longName ?? "unknown".localized
|
||||
if let nodeInfo = nodeInfoPacket(nodeInfo: decodedInfo.nodeInfo, channel: decodedInfo.packet.channel, context: ctx) {
|
||||
if self.connectedPeripheral != nil && self.connectedPeripheral.num == nodeInfo.num {
|
||||
if nodeInfo.user != nil {
|
||||
connectedPeripheral.shortName = nodeInfo.user?.shortName ?? "?"
|
||||
connectedPeripheral.longName = nodeInfo.user?.longName ?? "unknown".localized
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -572,17 +578,17 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
// Channels
|
||||
if decodedInfo.channel.isInitialized && connectedPeripheral != nil {
|
||||
nowKnown = true
|
||||
channelPacket(channel: decodedInfo.channel, fromNum: Int64(truncatingIfNeeded: connectedPeripheral.num), context: context!)
|
||||
channelPacket(channel: decodedInfo.channel, fromNum: Int64(truncatingIfNeeded: connectedPeripheral.num), context: ctx)
|
||||
}
|
||||
// Config
|
||||
if decodedInfo.config.isInitialized && !invalidVersion && connectedPeripheral != nil {
|
||||
nowKnown = true
|
||||
localConfig(config: decodedInfo.config, context: context!, nodeNum: Int64(truncatingIfNeeded: self.connectedPeripheral.num), nodeLongName: self.connectedPeripheral.longName)
|
||||
localConfig(config: decodedInfo.config, context: ctx, nodeNum: Int64(truncatingIfNeeded: self.connectedPeripheral.num), nodeLongName: self.connectedPeripheral.longName)
|
||||
}
|
||||
// Module Config
|
||||
if decodedInfo.moduleConfig.isInitialized && !invalidVersion && self.connectedPeripheral?.num != 0{
|
||||
nowKnown = true
|
||||
moduleConfig(config: decodedInfo.moduleConfig, context: context!, nodeNum: Int64(truncatingIfNeeded: self.connectedPeripheral?.num ?? 0), nodeLongName: self.connectedPeripheral.longName)
|
||||
moduleConfig(config: decodedInfo.moduleConfig, context: ctx, nodeNum: Int64(truncatingIfNeeded: self.connectedPeripheral?.num ?? 0), nodeLongName: self.connectedPeripheral.longName)
|
||||
if decodedInfo.moduleConfig.payloadVariant == ModuleConfig.OneOf_PayloadVariant.cannedMessage(decodedInfo.moduleConfig.cannedMessage) {
|
||||
if decodedInfo.moduleConfig.cannedMessage.enabled {
|
||||
_ = self.getCannedMessageModuleMessages(destNum: self.connectedPeripheral.num, wantResponse: true)
|
||||
|
|
@ -592,7 +598,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
// Device Metadata
|
||||
if decodedInfo.metadata.firmwareVersion.count > 0 && !invalidVersion {
|
||||
nowKnown = true
|
||||
deviceMetadataPacket(metadata: decodedInfo.metadata, fromNum: connectedPeripheral.num, context: context!)
|
||||
deviceMetadataPacket(metadata: decodedInfo.metadata, fromNum: connectedPeripheral.num, context: ctx)
|
||||
connectedPeripheral.firmwareVersion = decodedInfo.metadata.firmwareVersion
|
||||
let lastDotIndex = decodedInfo.metadata.firmwareVersion.lastIndex(of: ".")
|
||||
if lastDotIndex == nil {
|
||||
|
|
@ -994,37 +1000,28 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
return success
|
||||
}
|
||||
|
||||
public func getPositionFromPhoneGPS(channel: Int32, destNum: Int64) -> Position? {
|
||||
public func getPositionFromPhoneGPS(destNum: Int64) -> Position? {
|
||||
var positionPacket = Position()
|
||||
|
||||
let fetchChannelRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "ChannelEntity")
|
||||
fetchChannelRequest.predicate = NSPredicate(format: "index == %lld", channel)
|
||||
|
||||
do {
|
||||
guard let fetchedChannel = try context!.fetch(fetchChannelRequest) as? [ChannelEntity] else {
|
||||
return nil
|
||||
}
|
||||
if #available(iOS 17.0, macOS 14.0, *) {
|
||||
|
||||
if let lastLocation = LocationsHandler.shared.locationsArray.last {
|
||||
|
||||
if fetchedChannel.count > 0 {
|
||||
positionPacket.latitudeI = Int32(lastLocation.coordinate.latitude * 1e7)
|
||||
positionPacket.longitudeI = Int32(lastLocation.coordinate.longitude * 1e7)
|
||||
let timestamp = lastLocation.timestamp
|
||||
positionPacket.time = UInt32(timestamp.timeIntervalSince1970)
|
||||
positionPacket.timestamp = UInt32(timestamp.timeIntervalSince1970)
|
||||
positionPacket.altitude = Int32(lastLocation.altitude)
|
||||
positionPacket.satsInView = UInt32(LocationsHandler.satsInView)
|
||||
positionPacket.precisionBits = UInt32(fetchedChannel[0].positionPrecision)
|
||||
let currentSpeed = lastLocation.speed
|
||||
if currentSpeed > 0 && (!currentSpeed.isNaN || !currentSpeed.isInfinite) {
|
||||
positionPacket.groundSpeed = UInt32(currentSpeed * 3.6)
|
||||
}
|
||||
let currentHeading = lastLocation.course
|
||||
if currentHeading > 0 && (!currentHeading.isNaN || !currentHeading.isInfinite) {
|
||||
positionPacket.groundTrack = UInt32(currentHeading)
|
||||
}
|
||||
positionPacket.latitudeI = Int32(lastLocation.coordinate.latitude * 1e7)
|
||||
positionPacket.longitudeI = Int32(lastLocation.coordinate.longitude * 1e7)
|
||||
let timestamp = lastLocation.timestamp
|
||||
positionPacket.time = UInt32(timestamp.timeIntervalSince1970)
|
||||
positionPacket.timestamp = UInt32(timestamp.timeIntervalSince1970)
|
||||
positionPacket.altitude = Int32(lastLocation.altitude)
|
||||
positionPacket.satsInView = UInt32(LocationsHandler.satsInView)
|
||||
|
||||
let currentSpeed = lastLocation.speed
|
||||
if currentSpeed > 0 && (!currentSpeed.isNaN || !currentSpeed.isInfinite) {
|
||||
positionPacket.groundSpeed = UInt32(currentSpeed * 3.6)
|
||||
}
|
||||
let currentHeading = lastLocation.course
|
||||
if currentHeading > 0 && (!currentHeading.isNaN || !currentHeading.isInfinite) {
|
||||
positionPacket.groundTrack = UInt32(currentHeading)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1032,9 +1029,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
if destNum <= 0 || LocationHelper.currentLocation.distance(from: LocationHelper.DefaultLocation) == 0.0 {
|
||||
return nil
|
||||
}
|
||||
if fetchedChannel.count <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
positionPacket.latitudeI = Int32(LocationHelper.currentLocation.latitude * 1e7)
|
||||
positionPacket.longitudeI = Int32(LocationHelper.currentLocation.longitude * 1e7)
|
||||
|
|
@ -1043,7 +1037,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
positionPacket.timestamp = UInt32(timestamp.timeIntervalSince1970)
|
||||
positionPacket.altitude = Int32(LocationHelper.shared.locationManager.location?.altitude ?? 0)
|
||||
positionPacket.satsInView = UInt32(LocationHelper.satsInView)
|
||||
positionPacket.precisionBits = UInt32(fetchedChannel[0].positionPrecision)
|
||||
let currentSpeed = LocationHelper.shared.locationManager.location?.speed ?? 0
|
||||
if currentSpeed > 0 && (!currentSpeed.isNaN || !currentSpeed.isInfinite) {
|
||||
positionPacket.groundSpeed = UInt32(currentSpeed * 3.6)
|
||||
|
|
@ -1053,17 +1046,15 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
positionPacket.groundTrack = UInt32(currentHeading)
|
||||
}
|
||||
}
|
||||
|
||||
} catch {
|
||||
return nil
|
||||
}
|
||||
|
||||
return positionPacket
|
||||
}
|
||||
|
||||
public func setFixedPosition(fromUser: UserEntity, channel: Int32) -> Bool {
|
||||
var adminPacket = AdminMessage()
|
||||
guard let positionPacket = getPositionFromPhoneGPS(channel: channel, destNum: fromUser.num) else {
|
||||
guard let positionPacket = getPositionFromPhoneGPS(destNum: fromUser.num) else {
|
||||
return false
|
||||
}
|
||||
adminPacket.setFixedPosition = positionPacket
|
||||
|
|
@ -1109,7 +1100,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
public func sendPosition(channel: Int32, destNum: Int64, wantResponse: Bool) -> Bool {
|
||||
var success = false
|
||||
let fromNodeNum = connectedPeripheral.num
|
||||
guard let positionPacket = getPositionFromPhoneGPS(channel: channel, destNum: destNum) else {
|
||||
guard let positionPacket = getPositionFromPhoneGPS(destNum: destNum) else {
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
@ -2635,6 +2626,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
do {
|
||||
connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse)
|
||||
try context!.save()
|
||||
print(adminDescription)
|
||||
return true
|
||||
} catch {
|
||||
context!.rollback()
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
import Foundation
|
||||
import CoreData
|
||||
import SwiftUI
|
||||
import RegexBuilder
|
||||
#if canImport(ActivityKit)
|
||||
import ActivityKit
|
||||
#endif
|
||||
|
|
@ -160,12 +161,14 @@ func channelPacket (channel: Channel, fromNum: Int64, context: NSManagedObjectCo
|
|||
newChannel.name = channel.settings.name
|
||||
newChannel.role = Int32(channel.role.rawValue)
|
||||
newChannel.psk = channel.settings.psk
|
||||
newChannel.positionPrecision = Int32(truncatingIfNeeded: channel.settings.moduleSettings.positionPrecision)
|
||||
if channel.settings.hasModuleSettings {
|
||||
newChannel.positionPrecision = Int32(truncatingIfNeeded: channel.settings.moduleSettings.positionPrecision)
|
||||
newChannel.mute = channel.settings.moduleSettings.isClientMuted
|
||||
}
|
||||
guard let mutableChannels = fetchedMyInfo[0].channels!.mutableCopy() as? NSMutableOrderedSet else {
|
||||
return
|
||||
}
|
||||
if let oldChannel = mutableChannels.first(where: {($0 as AnyObject).index == newChannel.index }) as? ChannelEntity {
|
||||
newChannel.mute = oldChannel.mute
|
||||
let index = mutableChannels.index(of: oldChannel as Any)
|
||||
mutableChannels.replaceObject(at: index, with: newChannel)
|
||||
} else {
|
||||
|
|
@ -772,7 +775,19 @@ func telemetryPacket(packet: MeshPacket, connectedNode: Int64, context: NSManage
|
|||
func textMessageAppPacket(packet: MeshPacket, wantRangeTestPackets: Bool, connectedNode: Int64, storeForward: Bool = false, context: NSManagedObjectContext) {
|
||||
|
||||
var messageText = String(bytes: packet.decoded.payload, encoding: .utf8)
|
||||
if !wantRangeTestPackets && (String(messageText ?? "seq ").starts(with: "seq ")) {
|
||||
let rangeRef = Reference(Int.self)
|
||||
let rangeTestRegex = Regex {
|
||||
"seq "
|
||||
|
||||
TryCapture(as: rangeRef) {
|
||||
OneOrMore(.digit)
|
||||
} transform: { match in
|
||||
Int(match)
|
||||
}
|
||||
}
|
||||
let rangeTest = messageText?.contains(rangeTestRegex) ?? false && messageText?.starts(with: "seq ") ?? false
|
||||
|
||||
if !wantRangeTestPackets && rangeTest {
|
||||
return
|
||||
}
|
||||
var storeForwardBroadcast = false
|
||||
|
|
@ -841,26 +856,28 @@ func textMessageAppPacket(packet: MeshPacket, wantRangeTestPackets: Bool, connec
|
|||
return
|
||||
}
|
||||
let appState = AppState.shared
|
||||
if newMessage.fromUser != nil && newMessage.toUser != nil && !(newMessage.fromUser?.mute ?? false) {
|
||||
if newMessage.fromUser != nil && newMessage.toUser != nil {
|
||||
// Set Unread Message Indicators
|
||||
if packet.to == connectedNode {
|
||||
appState.unreadDirectMessages = newMessage.toUser?.unreadMessages ?? 0
|
||||
UIApplication.shared.applicationIconBadgeNumber = appState.unreadChannelMessages + appState.unreadDirectMessages
|
||||
}
|
||||
// Create an iOS Notification for the received DM message and schedule it immediately
|
||||
let manager = LocalNotificationManager()
|
||||
manager.notifications = [
|
||||
Notification(
|
||||
id: ("notification.id.\(newMessage.messageId)"),
|
||||
title: "\(newMessage.fromUser?.longName ?? "unknown".localized)",
|
||||
subtitle: "AKA \(newMessage.fromUser?.shortName ?? "?")",
|
||||
content: messageText!,
|
||||
target: "message",
|
||||
path: "meshtastic://open-dm?userid=\(newMessage.fromUser?.num ?? 0)&id=\(newMessage.messageId)"
|
||||
)
|
||||
]
|
||||
manager.schedule()
|
||||
print("💬 iOS Notification Scheduled for text message from \(newMessage.fromUser?.longName ?? "unknown".localized)")
|
||||
if !(newMessage.fromUser?.mute ?? false) {
|
||||
// Create an iOS Notification for the received DM message and schedule it immediately
|
||||
let manager = LocalNotificationManager()
|
||||
manager.notifications = [
|
||||
Notification(
|
||||
id: ("notification.id.\(newMessage.messageId)"),
|
||||
title: "\(newMessage.fromUser?.longName ?? "unknown".localized)",
|
||||
subtitle: "AKA \(newMessage.fromUser?.shortName ?? "?")",
|
||||
content: messageText!,
|
||||
target: "message",
|
||||
path: "meshtastic://open-dm?userid=\(newMessage.fromUser?.num ?? 0)&id=\(newMessage.messageId)"
|
||||
)
|
||||
]
|
||||
manager.schedule()
|
||||
print("💬 iOS Notification Scheduled for text message from \(newMessage.fromUser?.longName ?? "unknown".localized)")
|
||||
}
|
||||
} else if newMessage.fromUser != nil && newMessage.toUser == nil {
|
||||
|
||||
let fetchMyInfoRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "MyInfoEntity")
|
||||
|
|
@ -878,7 +895,7 @@ func textMessageAppPacket(packet: MeshPacket, wantRangeTestPackets: Bool, connec
|
|||
if channel.index == newMessage.channel {
|
||||
context.refresh(channel, mergeChanges: true)
|
||||
}
|
||||
if channel.index == newMessage.channel && !channel.mute {
|
||||
if channel.index == newMessage.channel && !channel.mute && UserDefaults.channelMessageNotifications {
|
||||
// Create an iOS Notification for the received private channel message and schedule it immediately
|
||||
let manager = LocalNotificationManager()
|
||||
manager.notifications = [
|
||||
|
|
|
|||
|
|
@ -3,6 +3,6 @@
|
|||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>_XCCurrentVersionName</key>
|
||||
<string>MeshtasticDataModelV 35.xcdatamodel</string>
|
||||
<string>MeshtasticDataModelV 36.xcdatamodel</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,461 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="22757" systemVersion="23F5064f" 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" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="id" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="index" attributeType="Integer 32" minValueString="0" maxValueString="13" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="mute" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="name" optional="YES" attributeType="String"/>
|
||||
<attribute name="positionPrecision" optional="YES" attributeType="Integer 32" defaultValueString="32" usesScalarValueType="YES"/>
|
||||
<attribute name="psk" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="role" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="uplinkEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<relationship name="myInfoChannel" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MyInfoEntity" inverseName="channels" inverseEntity="MyInfoEntity"/>
|
||||
<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="ledHeartbeatEnabled" optional="YES" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<attribute name="nodeInfoBroadcastSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="rebroadcastMode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="role" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="serialEnabled" optional="YES" attributeType="Boolean" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="tzdef" optional="YES" attributeType="String"/>
|
||||
<relationship name="deviceConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="deviceConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="DeviceMetadataEntity" representedClassName="DeviceMetadataEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="canShutdown" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="deviceStateVersion" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="firmwareVersion" optional="YES" attributeType="String"/>
|
||||
<attribute name="hasBluetooth" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="hasEthernet" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="hasWifi" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="hwModel" optional="YES" attributeType="String"/>
|
||||
<attribute name="positionFlags" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="role" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="time" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<relationship name="metadataNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="metadata" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="DisplayConfigEntity" representedClassName="DisplayConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="compassNorthTop" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="displayMode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="flipScreen" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="gpsFormat" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="headingBold" optional="YES" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<attribute name="oledType" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="screenCarouselInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="screenOnSeconds" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="units" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="wakeOnTapOrMotion" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<relationship name="displayConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="displayConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="ExternalNotificationConfigEntity" representedClassName="ExternalNotificationConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="active" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="alertBell" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="alertBellBuzzer" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="alertBellVibra" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="alertMessage" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="alertMessageBuzzer" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="alertMessageVibra" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="nagTimeout" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="output" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="outputBuzzer" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="outputMilliseconds" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="outputVibra" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="useI2SAsBuzzer" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="usePWM" optional="YES" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<relationship name="externalNotificationConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="externalNotificationConfig" inverseEntity="NodeInfoEntity"/>
|
||||
<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="ignoreMqtt" optional="YES" attributeType="Boolean" defaultValueString="NO" 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="mapPositionPrecision" optional="YES" attributeType="Integer 32" defaultValueString="13" usesScalarValueType="YES"/>
|
||||
<attribute name="mapPublishIntervalSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="mapReportingEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="password" optional="YES" attributeType="String" maxValueString="30"/>
|
||||
<attribute name="proxyToClientEnabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="root" optional="YES" attributeType="String" defaultValueString="msh"/>
|
||||
<attribute name="tlsEnabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="username" optional="YES" attributeType="String" maxValueString="30"/>
|
||||
<relationship name="mqttConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="mqttConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="MyInfoEntity" representedClassName="MyInfoEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="adminIndex" optional="YES" attributeType="Integer 32" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="bleName" optional="YES" attributeType="String"/>
|
||||
<attribute name="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="favorite" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="hopsAway" optional="YES" attributeType="Integer 32" defaultValueString="0" 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"/>
|
||||
<attribute name="viaMqtt" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<relationship name="ambientLightingConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="AmbientLightingConfigEntity" inverseName="ambientLightingConfigNode" inverseEntity="AmbientLightingConfigEntity"/>
|
||||
<relationship name="bluetoothConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="BluetoothConfigEntity" inverseName="bluetoothConfigNode" inverseEntity="BluetoothConfigEntity"/>
|
||||
<relationship name="cannedMessageConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="CannedMessageConfigEntity" inverseName="cannedMessagesConfigNode" inverseEntity="CannedMessageConfigEntity"/>
|
||||
<relationship name="detectionSensorConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="DetectionSensorConfigEntity" inverseName="detectionSensorConfigNode" inverseEntity="DetectionSensorConfigEntity"/>
|
||||
<relationship name="deviceConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="DeviceConfigEntity" inverseName="deviceConfigNode" inverseEntity="DeviceConfigEntity"/>
|
||||
<relationship name="displayConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="DisplayConfigEntity" inverseName="displayConfigNode" inverseEntity="DisplayConfigEntity"/>
|
||||
<relationship name="externalNotificationConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="ExternalNotificationConfigEntity" inverseName="externalNotificationConfigNode" inverseEntity="ExternalNotificationConfigEntity"/>
|
||||
<relationship name="loRaConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="LoRaConfigEntity" inverseName="loRaConfigNode" inverseEntity="LoRaConfigEntity"/>
|
||||
<relationship name="metadata" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="DeviceMetadataEntity" inverseName="metadataNode" inverseEntity="DeviceMetadataEntity"/>
|
||||
<relationship name="mqttConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MQTTConfigEntity" inverseName="mqttConfigNode" inverseEntity="MQTTConfigEntity"/>
|
||||
<relationship name="myInfo" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MyInfoEntity" inverseName="myInfoNode" inverseEntity="MyInfoEntity"/>
|
||||
<relationship name="networkConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NetworkConfigEntity" inverseName="networkConfigNode" inverseEntity="NetworkConfigEntity"/>
|
||||
<relationship name="pax" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="PaxCounterEntity" inverseName="paxNode" inverseEntity="PaxCounterEntity"/>
|
||||
<relationship name="paxCounterConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="PaxCounterConfigEntity" inverseName="paxCounterConfigNode" inverseEntity="PaxCounterConfigEntity"/>
|
||||
<relationship name="positionConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="PositionConfigEntity" inverseName="positionConfigNode" inverseEntity="PositionConfigEntity"/>
|
||||
<relationship name="positions" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="PositionEntity" inverseName="nodePosition" inverseEntity="PositionEntity"/>
|
||||
<relationship name="powerConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="PowerConfigEntity" inverseName="powerConfigNode" inverseEntity="PowerConfigEntity"/>
|
||||
<relationship name="rangeTestConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="RangeTestConfigEntity" inverseName="rangeTestConfigNode" inverseEntity="RangeTestConfigEntity"/>
|
||||
<relationship name="rtttlConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="RTTTLConfigEntity" inverseName="rtttlConfigNode" inverseEntity="RTTTLConfigEntity"/>
|
||||
<relationship name="serialConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="SerialConfigEntity" inverseName="serialConfigNode" inverseEntity="SerialConfigEntity"/>
|
||||
<relationship name="storeForwardConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="StoreForwardConfigEntity" inverseName="storeForwardConfigNode" inverseEntity="StoreForwardConfigEntity"/>
|
||||
<relationship name="telemetries" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="TelemetryEntity" inverseName="nodeTelemetry" inverseEntity="TelemetryEntity"/>
|
||||
<relationship name="telemetryConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="TelemetryConfigEntity" inverseName="telemetryConfigNode" inverseEntity="TelemetryConfigEntity"/>
|
||||
<relationship name="traceRoutes" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="TraceRouteEntity" inverseName="node" inverseEntity="TraceRouteEntity"/>
|
||||
<relationship name="user" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="UserEntity" inverseName="userNode" inverseEntity="UserEntity"/>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="num"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="PaxCounterConfigEntity" representedClassName="PaxCounterConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="paxcounterUpdateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="paxCounterConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="paxCounterConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="PaxCounterEntity" representedClassName="PaxCounterEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="ble" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="time" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="uptime" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="wifi" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="paxNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="pax" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="PositionConfigEntity" representedClassName="PositionConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="broadcastSmartMinimumDistance" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="broadcastSmartMinimumIntervalSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="deviceGpsEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="fixedPosition" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="gpsAttemptTime" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="gpsEnGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="gpsMode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="gpsUpdateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="positionBroadcastSeconds" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="positionFlags" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="rxGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="smartPositionEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="txGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="positionConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="positionConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="PositionEntity" representedClassName="PositionEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="altitude" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="heading" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="latest" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="latitudeI" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="longitudeI" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="precisionBits" optional="YES" attributeType="Integer 32" defaultValueString="32" usesScalarValueType="YES"/>
|
||||
<attribute name="rssi" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="satsInView" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="seqNo" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="speed" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="time" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<relationship name="nodePosition" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="positions" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="PowerConfigEntity" representedClassName="PowerConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="adcMultiplierOverride" optional="YES" attributeType="Float" usesScalarValueType="YES"/>
|
||||
<attribute name="deviceBatteryInaAddress" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="isPowerSaving" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="lsSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="minWakeSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="onBatteryShutdownAfterSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="waitBluetoothSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="powerConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="powerConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="RangeTestConfigEntity" representedClassName="RangeTestConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="save" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="sender" optional="YES" attributeType="Integer 32" usesScalarValueType="YES"/>
|
||||
<relationship name="rangeTestConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="rangeTestConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="RouteEntity" representedClassName="RouteEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="color" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="date" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="distance" optional="YES" attributeType="Double" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="elevationGain" optional="YES" attributeType="Double" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="endDate" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="id" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="name" optional="YES" attributeType="String"/>
|
||||
<attribute name="notes" optional="YES" attributeType="String"/>
|
||||
<relationship name="locations" optional="YES" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="LocationEntity" inverseName="routeLocation" inverseEntity="LocationEntity"/>
|
||||
</entity>
|
||||
<entity name="RTTTLConfigEntity" representedClassName="RTTTLConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="ringtone" optional="YES" attributeType="String" maxValueString="228" defaultValueString=""/>
|
||||
<relationship name="rtttlConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="rtttlConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="SerialConfigEntity" representedClassName="SerialConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="baudRate" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="echo" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="mode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="overrideConsoleSerialPort" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="rxd" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="timeout" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="txd" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="serialConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="serialConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="StoreForwardConfigEntity" representedClassName="StoreForwardConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="enabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="heartbeat" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<attribute name="historyReturnMax" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="historyReturnWindow" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="isRouter" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="lastHeartbeat" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="lastRequest" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="records" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="storeForwardConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="storeForwardConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="TelemetryConfigEntity" representedClassName="TelemetryConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="deviceUpdateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="environmentDisplayFahrenheit" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="environmentMeasurementEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="environmentScreenEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="environmentUpdateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="powerMeasurementEnabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="powerScreenEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="powerUpdateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="telemetryConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="telemetryConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="TelemetryEntity" representedClassName="TelemetryEntity" syncable="YES" 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="iaq" optional="YES" attributeType="Integer 32" defaultValueString="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="uptimeSeconds" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<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="numString" optional="YES" attributeType="String"/>
|
||||
<attribute name="role" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="shortName" attributeType="String"/>
|
||||
<attribute name="userId" attributeType="String"/>
|
||||
<relationship name="receivedMessages" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="MessageEntity" inverseName="toUser" inverseEntity="MessageEntity"/>
|
||||
<relationship name="sentMessages" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="MessageEntity" inverseName="fromUser" inverseEntity="MessageEntity"/>
|
||||
<relationship name="userNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="user" inverseEntity="NodeInfoEntity"/>
|
||||
<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"/>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="id"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
</model>
|
||||
|
|
@ -11,7 +11,7 @@ struct MeshtasticAppleApp: App {
|
|||
|
||||
@UIApplicationDelegateAdaptor(MeshtasticAppDelegate.self) var appDelegate
|
||||
let persistenceController = PersistenceController.shared
|
||||
@ObservedObject private var bleManager: BLEManager = BLEManager()
|
||||
@ObservedObject private var bleManager: BLEManager = BLEManager.shared
|
||||
|
||||
@Environment(\.scenePhase) var scenePhase
|
||||
|
||||
|
|
|
|||
|
|
@ -355,7 +355,7 @@ func upsertPositionPacket (packet: MeshPacket, context: NSManagedObjectContext)
|
|||
if mostRecent.coordinate.distance(from: position.coordinate) < 15.0 {
|
||||
mutablePositions.remove(mostRecent)
|
||||
}
|
||||
} else if mutablePositions.count > 0 && 11...16 ~= position.precisionBits {
|
||||
} else if mutablePositions.count > 0 {
|
||||
/// Don't store any history for reduced accuracy positions, we will just show a circle
|
||||
mutablePositions.removeAllObjects()
|
||||
}
|
||||
|
|
@ -462,6 +462,7 @@ func upsertDeviceConfigPacket(config: Meshtastic.Config.DeviceConfig, nodeNum: I
|
|||
newDeviceConfig.rebroadcastMode = Int32(config.rebroadcastMode.rawValue)
|
||||
newDeviceConfig.nodeInfoBroadcastSecs = Int32(truncating: config.nodeInfoBroadcastSecs as NSNumber)
|
||||
newDeviceConfig.doubleTapAsButtonPress = config.doubleTapAsButtonPress
|
||||
newDeviceConfig.ledHeartbeatEnabled = !config.ledHeartbeatDisabled
|
||||
newDeviceConfig.isManaged = config.isManaged
|
||||
newDeviceConfig.tzdef = config.tzdef
|
||||
fetchedNode[0].deviceConfig = newDeviceConfig
|
||||
|
|
@ -474,6 +475,7 @@ func upsertDeviceConfigPacket(config: Meshtastic.Config.DeviceConfig, nodeNum: I
|
|||
fetchedNode[0].deviceConfig?.rebroadcastMode = Int32(config.rebroadcastMode.rawValue)
|
||||
fetchedNode[0].deviceConfig?.nodeInfoBroadcastSecs = Int32(truncating: config.nodeInfoBroadcastSecs as NSNumber)
|
||||
fetchedNode[0].deviceConfig?.doubleTapAsButtonPress = config.doubleTapAsButtonPress
|
||||
fetchedNode[0].deviceConfig?.ledHeartbeatEnabled = !config.ledHeartbeatDisabled
|
||||
fetchedNode[0].deviceConfig?.isManaged = config.isManaged
|
||||
fetchedNode[0].deviceConfig?.tzdef = config.tzdef
|
||||
}
|
||||
|
|
|
|||
|
|
@ -924,7 +924,7 @@ struct AdminMessage {
|
|||
|
||||
extension AdminMessage.ConfigType: CaseIterable {
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
static var allCases: [AdminMessage.ConfigType] = [
|
||||
static let allCases: [AdminMessage.ConfigType] = [
|
||||
.deviceConfig,
|
||||
.positionConfig,
|
||||
.powerConfig,
|
||||
|
|
@ -937,7 +937,7 @@ extension AdminMessage.ConfigType: CaseIterable {
|
|||
|
||||
extension AdminMessage.ModuleConfigType: CaseIterable {
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
static var allCases: [AdminMessage.ModuleConfigType] = [
|
||||
static let allCases: [AdminMessage.ModuleConfigType] = [
|
||||
.mqttConfig,
|
||||
.serialConfig,
|
||||
.extnotifConfig,
|
||||
|
|
|
|||
|
|
@ -75,7 +75,15 @@ extension ChannelSet: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio
|
|||
var _settings: [ChannelSettings] = []
|
||||
var _loraConfig: Config.LoRaConfig? = nil
|
||||
|
||||
static let defaultInstance = _StorageClass()
|
||||
#if swift(>=5.10)
|
||||
// This property is used as the initial default value for new instances of the type.
|
||||
// The type itself is protecting the reference to its storage via CoW semantics.
|
||||
// This will force a copy to be made of this reference when the first mutation occurs;
|
||||
// hence, it is safe to mark this as `nonisolated(unsafe)`.
|
||||
static nonisolated(unsafe) let defaultInstance = _StorageClass()
|
||||
#else
|
||||
static let defaultInstance = _StorageClass()
|
||||
#endif
|
||||
|
||||
private init() {}
|
||||
|
||||
|
|
|
|||
|
|
@ -136,7 +136,7 @@ enum Team: SwiftProtobuf.Enum {
|
|||
|
||||
extension Team: CaseIterable {
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
static var allCases: [Team] = [
|
||||
static let allCases: [Team] = [
|
||||
.unspecifedColor,
|
||||
.white,
|
||||
.yellow,
|
||||
|
|
@ -239,7 +239,7 @@ enum MemberRole: SwiftProtobuf.Enum {
|
|||
|
||||
extension MemberRole: CaseIterable {
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
static var allCases: [MemberRole] = [
|
||||
static let allCases: [MemberRole] = [
|
||||
.unspecifed,
|
||||
.teamMember,
|
||||
.teamLead,
|
||||
|
|
|
|||
|
|
@ -120,6 +120,11 @@ struct ModuleSettings {
|
|||
/// Bits of precision for the location sent in position packets.
|
||||
var positionPrecision: UInt32 = 0
|
||||
|
||||
///
|
||||
/// Controls whether or not the phone / clients should mute the current channel
|
||||
/// Useful for noisy public channels you don't necessarily want to disable
|
||||
var isClientMuted: Bool = false
|
||||
|
||||
var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
init() {}
|
||||
|
|
@ -215,7 +220,7 @@ struct Channel {
|
|||
|
||||
extension Channel.Role: CaseIterable {
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
static var allCases: [Channel.Role] = [
|
||||
static let allCases: [Channel.Role] = [
|
||||
.disabled,
|
||||
.primary,
|
||||
.secondary,
|
||||
|
|
@ -311,6 +316,7 @@ extension ModuleSettings: SwiftProtobuf.Message, SwiftProtobuf._MessageImplement
|
|||
static let protoMessageName: String = _protobuf_package + ".ModuleSettings"
|
||||
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
||||
1: .standard(proto: "position_precision"),
|
||||
2: .standard(proto: "is_client_muted"),
|
||||
]
|
||||
|
||||
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||
|
|
@ -320,6 +326,7 @@ extension ModuleSettings: SwiftProtobuf.Message, SwiftProtobuf._MessageImplement
|
|||
// enabled. https://github.com/apple/swift-protobuf/issues/1034
|
||||
switch fieldNumber {
|
||||
case 1: try { try decoder.decodeSingularUInt32Field(value: &self.positionPrecision) }()
|
||||
case 2: try { try decoder.decodeSingularBoolField(value: &self.isClientMuted) }()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
|
@ -329,11 +336,15 @@ extension ModuleSettings: SwiftProtobuf.Message, SwiftProtobuf._MessageImplement
|
|||
if self.positionPrecision != 0 {
|
||||
try visitor.visitSingularUInt32Field(value: self.positionPrecision, fieldNumber: 1)
|
||||
}
|
||||
if self.isClientMuted != false {
|
||||
try visitor.visitSingularBoolField(value: self.isClientMuted, fieldNumber: 2)
|
||||
}
|
||||
try unknownFields.traverse(visitor: &visitor)
|
||||
}
|
||||
|
||||
static func ==(lhs: ModuleSettings, rhs: ModuleSettings) -> Bool {
|
||||
if lhs.positionPrecision != rhs.positionPrecision {return false}
|
||||
if lhs.isClientMuted != rhs.isClientMuted {return false}
|
||||
if lhs.unknownFields != rhs.unknownFields {return false}
|
||||
return true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1386,7 +1386,7 @@ struct Config {
|
|||
|
||||
extension Config.DeviceConfig.Role: CaseIterable {
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
static var allCases: [Config.DeviceConfig.Role] = [
|
||||
static let allCases: [Config.DeviceConfig.Role] = [
|
||||
.client,
|
||||
.clientMute,
|
||||
.router,
|
||||
|
|
@ -1403,7 +1403,7 @@ extension Config.DeviceConfig.Role: CaseIterable {
|
|||
|
||||
extension Config.DeviceConfig.RebroadcastMode: CaseIterable {
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
static var allCases: [Config.DeviceConfig.RebroadcastMode] = [
|
||||
static let allCases: [Config.DeviceConfig.RebroadcastMode] = [
|
||||
.all,
|
||||
.allSkipDecoding,
|
||||
.localOnly,
|
||||
|
|
@ -1413,7 +1413,7 @@ extension Config.DeviceConfig.RebroadcastMode: CaseIterable {
|
|||
|
||||
extension Config.PositionConfig.PositionFlags: CaseIterable {
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
static var allCases: [Config.PositionConfig.PositionFlags] = [
|
||||
static let allCases: [Config.PositionConfig.PositionFlags] = [
|
||||
.unset,
|
||||
.altitude,
|
||||
.altitudeMsl,
|
||||
|
|
@ -1430,7 +1430,7 @@ extension Config.PositionConfig.PositionFlags: CaseIterable {
|
|||
|
||||
extension Config.PositionConfig.GpsMode: CaseIterable {
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
static var allCases: [Config.PositionConfig.GpsMode] = [
|
||||
static let allCases: [Config.PositionConfig.GpsMode] = [
|
||||
.disabled,
|
||||
.enabled,
|
||||
.notPresent,
|
||||
|
|
@ -1439,7 +1439,7 @@ extension Config.PositionConfig.GpsMode: CaseIterable {
|
|||
|
||||
extension Config.NetworkConfig.AddressMode: CaseIterable {
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
static var allCases: [Config.NetworkConfig.AddressMode] = [
|
||||
static let allCases: [Config.NetworkConfig.AddressMode] = [
|
||||
.dhcp,
|
||||
.static,
|
||||
]
|
||||
|
|
@ -1447,7 +1447,7 @@ extension Config.NetworkConfig.AddressMode: CaseIterable {
|
|||
|
||||
extension Config.DisplayConfig.GpsCoordinateFormat: CaseIterable {
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
static var allCases: [Config.DisplayConfig.GpsCoordinateFormat] = [
|
||||
static let allCases: [Config.DisplayConfig.GpsCoordinateFormat] = [
|
||||
.dec,
|
||||
.dms,
|
||||
.utm,
|
||||
|
|
@ -1459,7 +1459,7 @@ extension Config.DisplayConfig.GpsCoordinateFormat: CaseIterable {
|
|||
|
||||
extension Config.DisplayConfig.DisplayUnits: CaseIterable {
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
static var allCases: [Config.DisplayConfig.DisplayUnits] = [
|
||||
static let allCases: [Config.DisplayConfig.DisplayUnits] = [
|
||||
.metric,
|
||||
.imperial,
|
||||
]
|
||||
|
|
@ -1467,7 +1467,7 @@ extension Config.DisplayConfig.DisplayUnits: CaseIterable {
|
|||
|
||||
extension Config.DisplayConfig.OledType: CaseIterable {
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
static var allCases: [Config.DisplayConfig.OledType] = [
|
||||
static let allCases: [Config.DisplayConfig.OledType] = [
|
||||
.oledAuto,
|
||||
.oledSsd1306,
|
||||
.oledSh1106,
|
||||
|
|
@ -1477,7 +1477,7 @@ extension Config.DisplayConfig.OledType: CaseIterable {
|
|||
|
||||
extension Config.DisplayConfig.DisplayMode: CaseIterable {
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
static var allCases: [Config.DisplayConfig.DisplayMode] = [
|
||||
static let allCases: [Config.DisplayConfig.DisplayMode] = [
|
||||
.default,
|
||||
.twocolor,
|
||||
.inverted,
|
||||
|
|
@ -1487,7 +1487,7 @@ extension Config.DisplayConfig.DisplayMode: CaseIterable {
|
|||
|
||||
extension Config.LoRaConfig.RegionCode: CaseIterable {
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
static var allCases: [Config.LoRaConfig.RegionCode] = [
|
||||
static let allCases: [Config.LoRaConfig.RegionCode] = [
|
||||
.unset,
|
||||
.us,
|
||||
.eu433,
|
||||
|
|
@ -1512,7 +1512,7 @@ extension Config.LoRaConfig.RegionCode: CaseIterable {
|
|||
|
||||
extension Config.LoRaConfig.ModemPreset: CaseIterable {
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
static var allCases: [Config.LoRaConfig.ModemPreset] = [
|
||||
static let allCases: [Config.LoRaConfig.ModemPreset] = [
|
||||
.longFast,
|
||||
.longSlow,
|
||||
.veryLongSlow,
|
||||
|
|
@ -1526,7 +1526,7 @@ extension Config.LoRaConfig.ModemPreset: CaseIterable {
|
|||
|
||||
extension Config.BluetoothConfig.PairingMode: CaseIterable {
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
static var allCases: [Config.BluetoothConfig.PairingMode] = [
|
||||
static let allCases: [Config.BluetoothConfig.PairingMode] = [
|
||||
.randomPin,
|
||||
.fixedPin,
|
||||
.noPin,
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ enum ScreenFonts: SwiftProtobuf.Enum {
|
|||
|
||||
extension ScreenFonts: CaseIterable {
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
static var allCases: [ScreenFonts] = [
|
||||
static let allCases: [ScreenFonts] = [
|
||||
.fontSmall,
|
||||
.fontMedium,
|
||||
.fontLarge,
|
||||
|
|
@ -509,7 +509,15 @@ extension NodeInfoLite: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat
|
|||
var _hopsAway: UInt32 = 0
|
||||
var _isFavorite: Bool = false
|
||||
|
||||
static let defaultInstance = _StorageClass()
|
||||
#if swift(>=5.10)
|
||||
// This property is used as the initial default value for new instances of the type.
|
||||
// The type itself is protecting the reference to its storage via CoW semantics.
|
||||
// This will force a copy to be made of this reference when the first mutation occurs;
|
||||
// hence, it is safe to mark this as `nonisolated(unsafe)`.
|
||||
static nonisolated(unsafe) let defaultInstance = _StorageClass()
|
||||
#else
|
||||
static let defaultInstance = _StorageClass()
|
||||
#endif
|
||||
|
||||
private init() {}
|
||||
|
||||
|
|
@ -649,7 +657,15 @@ extension DeviceState: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementati
|
|||
var _nodeRemoteHardwarePins: [NodeRemoteHardwarePin] = []
|
||||
var _nodeDbLite: [NodeInfoLite] = []
|
||||
|
||||
static let defaultInstance = _StorageClass()
|
||||
#if swift(>=5.10)
|
||||
// This property is used as the initial default value for new instances of the type.
|
||||
// The type itself is protecting the reference to its storage via CoW semantics.
|
||||
// This will force a copy to be made of this reference when the first mutation occurs;
|
||||
// hence, it is safe to mark this as `nonisolated(unsafe)`.
|
||||
static nonisolated(unsafe) let defaultInstance = _StorageClass()
|
||||
#else
|
||||
static let defaultInstance = _StorageClass()
|
||||
#endif
|
||||
|
||||
private init() {}
|
||||
|
||||
|
|
|
|||
|
|
@ -314,7 +314,15 @@ extension LocalConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementati
|
|||
var _bluetooth: Config.BluetoothConfig? = nil
|
||||
var _version: UInt32 = 0
|
||||
|
||||
static let defaultInstance = _StorageClass()
|
||||
#if swift(>=5.10)
|
||||
// This property is used as the initial default value for new instances of the type.
|
||||
// The type itself is protecting the reference to its storage via CoW semantics.
|
||||
// This will force a copy to be made of this reference when the first mutation occurs;
|
||||
// hence, it is safe to mark this as `nonisolated(unsafe)`.
|
||||
static nonisolated(unsafe) let defaultInstance = _StorageClass()
|
||||
#else
|
||||
static let defaultInstance = _StorageClass()
|
||||
#endif
|
||||
|
||||
private init() {}
|
||||
|
||||
|
|
@ -450,7 +458,15 @@ extension LocalModuleConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem
|
|||
var _paxcounter: ModuleConfig.PaxcounterConfig? = nil
|
||||
var _version: UInt32 = 0
|
||||
|
||||
static let defaultInstance = _StorageClass()
|
||||
#if swift(>=5.10)
|
||||
// This property is used as the initial default value for new instances of the type.
|
||||
// The type itself is protecting the reference to its storage via CoW semantics.
|
||||
// This will force a copy to be made of this reference when the first mutation occurs;
|
||||
// hence, it is safe to mark this as `nonisolated(unsafe)`.
|
||||
static nonisolated(unsafe) let defaultInstance = _StorageClass()
|
||||
#else
|
||||
static let defaultInstance = _StorageClass()
|
||||
#endif
|
||||
|
||||
private init() {}
|
||||
|
||||
|
|
|
|||
|
|
@ -110,6 +110,10 @@ enum HardwareModel: SwiftProtobuf.Enum {
|
|||
/// LoRAType device: https://loratype.org/
|
||||
case loraType // = 19
|
||||
|
||||
///
|
||||
/// wiphone https://www.wiphone.io/
|
||||
case wiphone // = 20
|
||||
|
||||
///
|
||||
/// B&Q Consulting Station Edition G1: https://uniteng.com/wiki/doku.php?id=meshtastic:station
|
||||
case stationG1 // = 25
|
||||
|
|
@ -307,6 +311,7 @@ enum HardwareModel: SwiftProtobuf.Enum {
|
|||
case 17: self = .nanoG1Explorer
|
||||
case 18: self = .nanoG2Ultra
|
||||
case 19: self = .loraType
|
||||
case 20: self = .wiphone
|
||||
case 25: self = .stationG1
|
||||
case 26: self = .rak11310
|
||||
case 27: self = .senseloraRp2040
|
||||
|
|
@ -372,6 +377,7 @@ enum HardwareModel: SwiftProtobuf.Enum {
|
|||
case .nanoG1Explorer: return 17
|
||||
case .nanoG2Ultra: return 18
|
||||
case .loraType: return 19
|
||||
case .wiphone: return 20
|
||||
case .stationG1: return 25
|
||||
case .rak11310: return 26
|
||||
case .senseloraRp2040: return 27
|
||||
|
|
@ -421,7 +427,7 @@ enum HardwareModel: SwiftProtobuf.Enum {
|
|||
|
||||
extension HardwareModel: CaseIterable {
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
static var allCases: [HardwareModel] = [
|
||||
static let allCases: [HardwareModel] = [
|
||||
.unset,
|
||||
.tloraV2,
|
||||
.tloraV1,
|
||||
|
|
@ -442,6 +448,7 @@ extension HardwareModel: CaseIterable {
|
|||
.nanoG1Explorer,
|
||||
.nanoG2Ultra,
|
||||
.loraType,
|
||||
.wiphone,
|
||||
.stationG1,
|
||||
.rak11310,
|
||||
.senseloraRp2040,
|
||||
|
|
@ -529,7 +536,7 @@ enum Constants: SwiftProtobuf.Enum {
|
|||
|
||||
extension Constants: CaseIterable {
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
static var allCases: [Constants] = [
|
||||
static let allCases: [Constants] = [
|
||||
.zero,
|
||||
.dataPayloadLen,
|
||||
]
|
||||
|
|
@ -642,7 +649,7 @@ enum CriticalErrorCode: SwiftProtobuf.Enum {
|
|||
|
||||
extension CriticalErrorCode: CaseIterable {
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
static var allCases: [CriticalErrorCode] = [
|
||||
static let allCases: [CriticalErrorCode] = [
|
||||
.none,
|
||||
.txWatchdog,
|
||||
.sleepEnterWait,
|
||||
|
|
@ -961,7 +968,7 @@ struct Position {
|
|||
|
||||
extension Position.LocSource: CaseIterable {
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
static var allCases: [Position.LocSource] = [
|
||||
static let allCases: [Position.LocSource] = [
|
||||
.locUnset,
|
||||
.locManual,
|
||||
.locInternal,
|
||||
|
|
@ -971,7 +978,7 @@ extension Position.LocSource: CaseIterable {
|
|||
|
||||
extension Position.AltSource: CaseIterable {
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
static var allCases: [Position.AltSource] = [
|
||||
static let allCases: [Position.AltSource] = [
|
||||
.altUnset,
|
||||
.altManual,
|
||||
.altInternal,
|
||||
|
|
@ -1252,7 +1259,7 @@ struct Routing {
|
|||
|
||||
extension Routing.Error: CaseIterable {
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
static var allCases: [Routing.Error] = [
|
||||
static let allCases: [Routing.Error] = [
|
||||
.none,
|
||||
.noRoute,
|
||||
.gotNak,
|
||||
|
|
@ -1770,7 +1777,7 @@ struct MeshPacket {
|
|||
|
||||
extension MeshPacket.Priority: CaseIterable {
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
static var allCases: [MeshPacket.Priority] = [
|
||||
static let allCases: [MeshPacket.Priority] = [
|
||||
.unset,
|
||||
.min,
|
||||
.background,
|
||||
|
|
@ -1783,7 +1790,7 @@ extension MeshPacket.Priority: CaseIterable {
|
|||
|
||||
extension MeshPacket.Delayed: CaseIterable {
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
static var allCases: [MeshPacket.Delayed] = [
|
||||
static let allCases: [MeshPacket.Delayed] = [
|
||||
.noDelay,
|
||||
.broadcast,
|
||||
.direct,
|
||||
|
|
@ -2037,7 +2044,7 @@ struct LogRecord {
|
|||
|
||||
extension LogRecord.Level: CaseIterable {
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
static var allCases: [LogRecord.Level] = [
|
||||
static let allCases: [LogRecord.Level] = [
|
||||
.unset,
|
||||
.critical,
|
||||
.error,
|
||||
|
|
@ -2741,6 +2748,7 @@ extension HardwareModel: SwiftProtobuf._ProtoNameProviding {
|
|||
17: .same(proto: "NANO_G1_EXPLORER"),
|
||||
18: .same(proto: "NANO_G2_ULTRA"),
|
||||
19: .same(proto: "LORA_TYPE"),
|
||||
20: .same(proto: "WIPHONE"),
|
||||
25: .same(proto: "STATION_G1"),
|
||||
26: .same(proto: "RAK11310"),
|
||||
27: .same(proto: "SENSELORA_RP2040"),
|
||||
|
|
@ -2860,7 +2868,15 @@ extension Position: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB
|
|||
var _seqNumber: UInt32 = 0
|
||||
var _precisionBits: UInt32 = 0
|
||||
|
||||
static let defaultInstance = _StorageClass()
|
||||
#if swift(>=5.10)
|
||||
// This property is used as the initial default value for new instances of the type.
|
||||
// The type itself is protecting the reference to its storage via CoW semantics.
|
||||
// This will force a copy to be made of this reference when the first mutation occurs;
|
||||
// hence, it is safe to mark this as `nonisolated(unsafe)`.
|
||||
static nonisolated(unsafe) let defaultInstance = _StorageClass()
|
||||
#else
|
||||
static let defaultInstance = _StorageClass()
|
||||
#endif
|
||||
|
||||
private init() {}
|
||||
|
||||
|
|
@ -3522,7 +3538,15 @@ extension MeshPacket: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio
|
|||
var _viaMqtt: Bool = false
|
||||
var _hopStart: UInt32 = 0
|
||||
|
||||
static let defaultInstance = _StorageClass()
|
||||
#if swift(>=5.10)
|
||||
// This property is used as the initial default value for new instances of the type.
|
||||
// The type itself is protecting the reference to its storage via CoW semantics.
|
||||
// This will force a copy to be made of this reference when the first mutation occurs;
|
||||
// hence, it is safe to mark this as `nonisolated(unsafe)`.
|
||||
static nonisolated(unsafe) let defaultInstance = _StorageClass()
|
||||
#else
|
||||
static let defaultInstance = _StorageClass()
|
||||
#endif
|
||||
|
||||
private init() {}
|
||||
|
||||
|
|
@ -3734,7 +3758,15 @@ extension NodeInfo: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB
|
|||
var _hopsAway: UInt32 = 0
|
||||
var _isFavorite: Bool = false
|
||||
|
||||
static let defaultInstance = _StorageClass()
|
||||
#if swift(>=5.10)
|
||||
// This property is used as the initial default value for new instances of the type.
|
||||
// The type itself is protecting the reference to its storage via CoW semantics.
|
||||
// This will force a copy to be made of this reference when the first mutation occurs;
|
||||
// hence, it is safe to mark this as `nonisolated(unsafe)`.
|
||||
static nonisolated(unsafe) let defaultInstance = _StorageClass()
|
||||
#else
|
||||
static let defaultInstance = _StorageClass()
|
||||
#endif
|
||||
|
||||
private init() {}
|
||||
|
||||
|
|
@ -4026,7 +4058,15 @@ extension FromRadio: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementation
|
|||
var _id: UInt32 = 0
|
||||
var _payloadVariant: FromRadio.OneOf_PayloadVariant?
|
||||
|
||||
static let defaultInstance = _StorageClass()
|
||||
#if swift(>=5.10)
|
||||
// This property is used as the initial default value for new instances of the type.
|
||||
// The type itself is protecting the reference to its storage via CoW semantics.
|
||||
// This will force a copy to be made of this reference when the first mutation occurs;
|
||||
// hence, it is safe to mark this as `nonisolated(unsafe)`.
|
||||
static nonisolated(unsafe) let defaultInstance = _StorageClass()
|
||||
#else
|
||||
static let defaultInstance = _StorageClass()
|
||||
#endif
|
||||
|
||||
private init() {}
|
||||
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ enum RemoteHardwarePinType: SwiftProtobuf.Enum {
|
|||
|
||||
extension RemoteHardwarePinType: CaseIterable {
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
static var allCases: [RemoteHardwarePinType] = [
|
||||
static let allCases: [RemoteHardwarePinType] = [
|
||||
.unknown,
|
||||
.digitalRead,
|
||||
.digitalWrite,
|
||||
|
|
@ -1152,7 +1152,7 @@ struct ModuleConfig {
|
|||
|
||||
extension ModuleConfig.AudioConfig.Audio_Baud: CaseIterable {
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
static var allCases: [ModuleConfig.AudioConfig.Audio_Baud] = [
|
||||
static let allCases: [ModuleConfig.AudioConfig.Audio_Baud] = [
|
||||
.codec2Default,
|
||||
.codec23200,
|
||||
.codec22400,
|
||||
|
|
@ -1167,7 +1167,7 @@ extension ModuleConfig.AudioConfig.Audio_Baud: CaseIterable {
|
|||
|
||||
extension ModuleConfig.SerialConfig.Serial_Baud: CaseIterable {
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
static var allCases: [ModuleConfig.SerialConfig.Serial_Baud] = [
|
||||
static let allCases: [ModuleConfig.SerialConfig.Serial_Baud] = [
|
||||
.baudDefault,
|
||||
.baud110,
|
||||
.baud300,
|
||||
|
|
@ -1189,7 +1189,7 @@ extension ModuleConfig.SerialConfig.Serial_Baud: CaseIterable {
|
|||
|
||||
extension ModuleConfig.SerialConfig.Serial_Mode: CaseIterable {
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
static var allCases: [ModuleConfig.SerialConfig.Serial_Mode] = [
|
||||
static let allCases: [ModuleConfig.SerialConfig.Serial_Mode] = [
|
||||
.default,
|
||||
.simple,
|
||||
.proto,
|
||||
|
|
@ -1201,7 +1201,7 @@ extension ModuleConfig.SerialConfig.Serial_Mode: CaseIterable {
|
|||
|
||||
extension ModuleConfig.CannedMessageConfig.InputEventChar: CaseIterable {
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
static var allCases: [ModuleConfig.CannedMessageConfig.InputEventChar] = [
|
||||
static let allCases: [ModuleConfig.CannedMessageConfig.InputEventChar] = [
|
||||
.none,
|
||||
.up,
|
||||
.down,
|
||||
|
|
|
|||
|
|
@ -277,7 +277,7 @@ enum PortNum: SwiftProtobuf.Enum {
|
|||
|
||||
extension PortNum: CaseIterable {
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
static var allCases: [PortNum] = [
|
||||
static let allCases: [PortNum] = [
|
||||
.unknownApp,
|
||||
.textMessageApp,
|
||||
.remoteHardwareApp,
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ struct HardwareMessage {
|
|||
|
||||
extension HardwareMessage.TypeEnum: CaseIterable {
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
static var allCases: [HardwareMessage.TypeEnum] = [
|
||||
static let allCases: [HardwareMessage.TypeEnum] = [
|
||||
.unset,
|
||||
.writeGpios,
|
||||
.watchGpios,
|
||||
|
|
|
|||
|
|
@ -344,7 +344,7 @@ struct StoreAndForward {
|
|||
|
||||
extension StoreAndForward.RequestResponse: CaseIterable {
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
static var allCases: [StoreAndForward.RequestResponse] = [
|
||||
static let allCases: [StoreAndForward.RequestResponse] = [
|
||||
.unset,
|
||||
.routerError,
|
||||
.routerHeartbeat,
|
||||
|
|
|
|||
|
|
@ -150,7 +150,7 @@ enum TelemetrySensorType: SwiftProtobuf.Enum {
|
|||
|
||||
extension TelemetrySensorType: CaseIterable {
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
static var allCases: [TelemetrySensorType] = [
|
||||
static let allCases: [TelemetrySensorType] = [
|
||||
.sensorUnset,
|
||||
.bme280,
|
||||
.bme680,
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ struct XModem {
|
|||
|
||||
extension XModem.Control: CaseIterable {
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
static var allCases: [XModem.Control] = [
|
||||
static let allCases: [XModem.Control] = [
|
||||
.nul,
|
||||
.soh,
|
||||
.stx,
|
||||
|
|
|
|||
|
|
@ -266,6 +266,18 @@ struct Connect: View {
|
|||
.controlSize(.large)
|
||||
.padding()
|
||||
}
|
||||
if bleManager.isConnecting {
|
||||
Button(role: .destructive, action: {
|
||||
bleManager.cancelPeripheralConnection()
|
||||
|
||||
}) {
|
||||
Label("disconnect", systemImage: "antenna.radiowaves.left.and.right.slash")
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
.buttonBorderShape(.capsule)
|
||||
.controlSize(.large)
|
||||
.padding()
|
||||
}
|
||||
#endif
|
||||
Spacer()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,10 +29,8 @@ struct CircleText: View {
|
|||
|
||||
struct CircleText_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
|
||||
HStack {
|
||||
VStack {
|
||||
|
||||
VStack {
|
||||
HStack {
|
||||
CircleText(text: "N1", color: Color.yellow, circleSize: 80)
|
||||
.previewLayout(.fixed(width: 300, height: 100))
|
||||
CircleText(text: "8", color: Color.purple, circleSize: 80)
|
||||
|
|
@ -41,17 +39,20 @@ struct CircleText_Previews: PreviewProvider {
|
|||
.previewLayout(.fixed(width: 300, height: 100))
|
||||
CircleText(text: "🍔", color: Color.brown, circleSize: 80)
|
||||
.previewLayout(.fixed(width: 300, height: 100))
|
||||
}
|
||||
HStack {
|
||||
CircleText(text: "👻", color: Color.orange, circleSize: 80)
|
||||
.previewLayout(.fixed(width: 300, height: 100))
|
||||
CircleText(text: "🤙", color: Color.orange, circleSize: 80)
|
||||
.previewLayout(.fixed(width: 300, height: 100))
|
||||
|
||||
}
|
||||
VStack {
|
||||
CircleText(text: "69", color: Color.green, circleSize: 80)
|
||||
.previewLayout(.fixed(width: 300, height: 100))
|
||||
CircleText(text: "WWWW", color: Color.cyan, circleSize: 80)
|
||||
.previewLayout(.fixed(width: 300, height: 100))
|
||||
}
|
||||
HStack {
|
||||
|
||||
|
||||
CircleText(text: "CW-A", color: Color.secondary)
|
||||
.previewLayout(.fixed(width: 300, height: 100))
|
||||
CircleText(text: "CW-A", color: Color.secondary, circleSize: 80)
|
||||
|
|
@ -60,7 +61,20 @@ struct CircleText_Previews: PreviewProvider {
|
|||
.previewLayout(.fixed(width: 300, height: 100))
|
||||
CircleText(text: "IIII", color: Color.accentColor, circleSize: 80)
|
||||
.previewLayout(.fixed(width: 300, height: 100))
|
||||
CircleText(text: "LCP", color: Color.primary, circleSize: 80)
|
||||
}
|
||||
HStack {
|
||||
|
||||
CircleText(text: "🚗", color: Color.orange)
|
||||
.previewLayout(.fixed(width: 300, height: 100))
|
||||
CircleText(text: "🔋", color: Color.indigo, circleSize: 80)
|
||||
.previewLayout(.fixed(width: 300, height: 100))
|
||||
CircleText(text: "🛢️", color: Color.orange, circleSize: 80)
|
||||
.previewLayout(.fixed(width: 300, height: 100))
|
||||
CircleText(text: "LCP", color: Color.indigo, circleSize: 80)
|
||||
.previewLayout(.fixed(width: 300, height: 100))
|
||||
}
|
||||
HStack {
|
||||
CircleText(text: "🤡", color: Color.red, circleSize: 80)
|
||||
.previewLayout(.fixed(width: 300, height: 100))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,125 +0,0 @@
|
|||
//
|
||||
// IndoorAirQuality.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Copyright(c) by Garth Vander Houwen on 4/10/24.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
enum IaqDisplayMode: Int, CaseIterable, Identifiable {
|
||||
|
||||
case pill = 0
|
||||
case dot = 1
|
||||
case text = 2
|
||||
case gauge = 3
|
||||
|
||||
var id: Int { self.rawValue }
|
||||
}
|
||||
|
||||
struct IndoorAirQuality: View {
|
||||
var iaq: Int = 0
|
||||
var displayMode: IaqDisplayMode = .pill
|
||||
let gradient = Gradient(colors: [.green, .mint, .yellow, .orange, .red, .purple, .purple, .brown, .brown, .brown, .brown])
|
||||
|
||||
var body: some View {
|
||||
let iaqEnum = Iaq.getIaq(for: iaq)
|
||||
switch displayMode {
|
||||
case .pill:
|
||||
ZStack (alignment: .leading) {
|
||||
RoundedRectangle(cornerRadius: 10)
|
||||
.fill(iaqEnum.color)
|
||||
.frame(width: 125, height: 30)
|
||||
Label("IAQ \(iaq)", systemImage: iaq < 100 ? "aqi.low" : ((iaq > 100 && iaq < 201) ? "aqi.medium" : "aqi.high"))
|
||||
.padding(.leading, 4)
|
||||
}
|
||||
case .dot:
|
||||
VStack {
|
||||
HStack {
|
||||
Text("\(iaq)")
|
||||
Circle()
|
||||
.fill(iaqEnum.color)
|
||||
.frame(width: 10, height: 10)
|
||||
}
|
||||
}
|
||||
case .text:
|
||||
Text(iaqEnum.description)
|
||||
.font(.caption)
|
||||
case .gauge:
|
||||
Gauge(value: Double(iaq), in: 0...500) {
|
||||
|
||||
Text("IAQ")
|
||||
.foregroundColor(iaqEnum.color)
|
||||
} currentValueLabel: {
|
||||
Text("\(Int(iaq))")
|
||||
}
|
||||
.tint(gradient)
|
||||
.gaugeStyle(.accessoryCircular)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct IndoorAirQuality_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
VStack {
|
||||
Text(".pill")
|
||||
.font(.title)
|
||||
HStack {
|
||||
VStack{
|
||||
IndoorAirQuality(iaq: 6)
|
||||
IndoorAirQuality(iaq: 51)
|
||||
IndoorAirQuality(iaq: 101)
|
||||
}
|
||||
VStack {
|
||||
IndoorAirQuality(iaq: 201)
|
||||
IndoorAirQuality(iaq: 350)
|
||||
IndoorAirQuality(iaq: 351)
|
||||
}
|
||||
}
|
||||
Text(".dot")
|
||||
.font(.title)
|
||||
HStack {
|
||||
VStack (alignment: .leading) {
|
||||
IndoorAirQuality(iaq: 6, displayMode: .dot)
|
||||
IndoorAirQuality(iaq: 51, displayMode: .dot)
|
||||
IndoorAirQuality(iaq: 101, displayMode: .dot)
|
||||
}
|
||||
VStack (alignment: .leading) {
|
||||
IndoorAirQuality(iaq: 201, displayMode: .dot)
|
||||
IndoorAirQuality(iaq: 350, displayMode: .dot)
|
||||
IndoorAirQuality(iaq: 351, displayMode: .dot)
|
||||
}
|
||||
}
|
||||
Text(".text")
|
||||
.font(.title)
|
||||
IndoorAirQuality(iaq: 6, displayMode: .text)
|
||||
IndoorAirQuality(iaq: 51, displayMode: .text)
|
||||
IndoorAirQuality(iaq: 101, displayMode: .text)
|
||||
IndoorAirQuality(iaq: 201, displayMode: .text)
|
||||
IndoorAirQuality(iaq: 350, displayMode: .text)
|
||||
IndoorAirQuality(iaq: 351, displayMode: .text)
|
||||
Text(".gauge")
|
||||
.font(.title)
|
||||
HStack (alignment: .top) {
|
||||
VStack{
|
||||
IndoorAirQuality(iaq: 6, displayMode: .gauge)
|
||||
IndoorAirQuality(iaq: 51, displayMode: .gauge)
|
||||
IndoorAirQuality(iaq: 101, displayMode: .gauge)
|
||||
IndoorAirQuality(iaq: 151, displayMode: .gauge)
|
||||
}
|
||||
VStack{
|
||||
IndoorAirQuality(iaq: 201, displayMode: .gauge)
|
||||
IndoorAirQuality(iaq: 251, displayMode: .gauge)
|
||||
IndoorAirQuality(iaq: 301, displayMode: .gauge)
|
||||
IndoorAirQuality(iaq: 350, displayMode: .gauge)
|
||||
}
|
||||
VStack{
|
||||
IndoorAirQuality(iaq: 351, displayMode: .gauge)
|
||||
IndoorAirQuality(iaq: 401, displayMode: .gauge)
|
||||
IndoorAirQuality(iaq: 500, displayMode: .gauge)
|
||||
}
|
||||
}
|
||||
}.previewLayout(.fixed(width: 300, height: 800))
|
||||
}
|
||||
}
|
||||
146
Meshtastic/Views/Helpers/Weather/AirQualityIndex.swift
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
//
|
||||
// AQICircleDisplay.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Copyright(c) Garth Vander Houwen 2/4/23.
|
||||
//
|
||||
import SwiftUI
|
||||
|
||||
enum AqiDisplayMode: Int, CaseIterable, Identifiable {
|
||||
|
||||
case pill = 0
|
||||
case dot = 1
|
||||
case text = 2
|
||||
case gauge = 3
|
||||
case gradient = 4
|
||||
|
||||
var id: Int { self.rawValue }
|
||||
}
|
||||
|
||||
struct AirQualityIndex: View {
|
||||
var aqi: Int
|
||||
var displayMode: IaqDisplayMode = .pill
|
||||
let gradient = Gradient(colors: [.green, .yellow, .orange, .red, .purple, .magenta])
|
||||
|
||||
var body: some View {
|
||||
|
||||
let aqiEnum = Aqi.getAqi(for: aqi)
|
||||
switch displayMode {
|
||||
case .pill:
|
||||
ZStack (alignment: .leading) {
|
||||
RoundedRectangle(cornerRadius: 10)
|
||||
.fill(aqiEnum.color)
|
||||
.frame(width: 125, height: 30)
|
||||
Label("IAQ \(aqi)", systemImage: aqi < 100 ? "aqi.low" : ((aqi > 100 && aqi < 201) ? "aqi.medium" : "aqi.high"))
|
||||
.padding(.leading, 4)
|
||||
}
|
||||
case .dot:
|
||||
VStack {
|
||||
HStack {
|
||||
Text("\(aqi)")
|
||||
Circle()
|
||||
.fill(aqiEnum.color)
|
||||
.frame(width: 10, height: 10)
|
||||
}
|
||||
}
|
||||
case .text:
|
||||
Text(aqiEnum.description)
|
||||
.font(.caption)
|
||||
case .gauge:
|
||||
Gauge(value: Double(aqi), in: 0...500) {
|
||||
|
||||
Text("IAQ")
|
||||
.foregroundColor(aqiEnum.color)
|
||||
} currentValueLabel: {
|
||||
Text("\(Int(aqi))")
|
||||
}
|
||||
.tint(gradient)
|
||||
.gaugeStyle(.accessoryCircular)
|
||||
case .gradient:
|
||||
HStack {
|
||||
Gauge(value: Double(aqi), in: 0...500) {
|
||||
Text("IAQ")
|
||||
.foregroundColor(aqiEnum.color)
|
||||
} currentValueLabel: {
|
||||
Text("IAQ ")+Text("\(Int(aqi))")
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
.tint(gradient)
|
||||
.gaugeStyle(.accessoryLinear)
|
||||
Text(aqiEnum.description)
|
||||
.font(.caption)
|
||||
}
|
||||
.padding([.leading, .trailing])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct AirQualityIndex_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
VStack {
|
||||
Text(".pill")
|
||||
.font(.title2)
|
||||
HStack {
|
||||
AirQualityIndex(aqi: 6)
|
||||
AirQualityIndex(aqi: 51)
|
||||
}
|
||||
HStack {
|
||||
AirQualityIndex(aqi: 101)
|
||||
AirQualityIndex(aqi: 151)
|
||||
}
|
||||
HStack {
|
||||
AirQualityIndex(aqi: 201)
|
||||
AirQualityIndex(aqi: 351)
|
||||
}
|
||||
Text(".dot")
|
||||
.font(.title2)
|
||||
HStack {
|
||||
AirQualityIndex(aqi: 6, displayMode: .dot)
|
||||
AirQualityIndex(aqi: 51, displayMode: .dot)
|
||||
AirQualityIndex(aqi: 101, displayMode: .dot)
|
||||
AirQualityIndex(aqi: 201, displayMode: .dot)
|
||||
AirQualityIndex(aqi: 350, displayMode: .dot)
|
||||
AirQualityIndex(aqi: 351, displayMode: .dot)
|
||||
}
|
||||
Text(".text")
|
||||
.font(.title2)
|
||||
HStack {
|
||||
AirQualityIndex(aqi: 6, displayMode: .text)
|
||||
AirQualityIndex(aqi: 51, displayMode: .text)
|
||||
AirQualityIndex(aqi: 101, displayMode: .text)
|
||||
}
|
||||
HStack {
|
||||
AirQualityIndex(aqi: 201, displayMode: .text)
|
||||
AirQualityIndex(aqi: 350, displayMode: .text)
|
||||
}
|
||||
Text(".gauge")
|
||||
.font(.title2)
|
||||
HStack (alignment: .top) {
|
||||
AirQualityIndex(aqi: 6, displayMode: .gauge)
|
||||
AirQualityIndex(aqi: 51, displayMode: .gauge)
|
||||
AirQualityIndex(aqi: 101, displayMode: .gauge)
|
||||
AirQualityIndex(aqi: 151, displayMode: .gauge)
|
||||
}
|
||||
HStack (alignment: .top) {
|
||||
AirQualityIndex(aqi: 201, displayMode: .gauge)
|
||||
AirQualityIndex(aqi: 251, displayMode: .gauge)
|
||||
AirQualityIndex(aqi: 301, displayMode: .gauge)
|
||||
AirQualityIndex(aqi: 351, displayMode: .gauge)
|
||||
}
|
||||
HStack (alignment: .top) {
|
||||
AirQualityIndex(aqi: 401, displayMode: .gauge)
|
||||
AirQualityIndex(aqi: 500, displayMode: .gauge)
|
||||
}
|
||||
Text(".gradient")
|
||||
.font(.title2)
|
||||
AirQualityIndex(aqi: 6, displayMode: .gradient)
|
||||
AirQualityIndex(aqi: 51, displayMode: .gradient)
|
||||
AirQualityIndex(aqi: 101, displayMode: .gradient)
|
||||
AirQualityIndex(aqi: 201, displayMode: .gradient)
|
||||
AirQualityIndex(aqi: 351, displayMode: .gradient)
|
||||
AirQualityIndex(aqi: 401, displayMode: .gradient)
|
||||
AirQualityIndex(aqi: 500, displayMode: .gradient)
|
||||
|
||||
}.previewLayout(.fixed(width: 300, height: 800))
|
||||
}
|
||||
}
|
||||
|
|
@ -1,67 +0,0 @@
|
|||
//
|
||||
// AQICircleDisplay.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Copyright(c) Garth Vander Houwen 2/4/23.
|
||||
//
|
||||
import SwiftUI
|
||||
|
||||
struct AirQualityIndexCompact: View {
|
||||
var aqi: Int
|
||||
|
||||
var body: some View {
|
||||
|
||||
HStack(spacing: 0.5) {
|
||||
Text("AQI \(aqi)")
|
||||
.foregroundColor(.gray)
|
||||
.padding(.trailing, 0)
|
||||
.font(.caption)
|
||||
|
||||
if aqi > 0 && aqi < 51 {
|
||||
// Good
|
||||
Circle()
|
||||
.fill(.green)
|
||||
.frame(width: 10, height: 10)
|
||||
} else if aqi > 50 && aqi < 101 {
|
||||
// Satisfactory
|
||||
Circle()
|
||||
.fill(Color(red: 0, green: 0.9882, blue: 0.1804))
|
||||
.frame(width: 10, height: 10)
|
||||
} else if aqi > 100 && aqi < 201 {
|
||||
// Moderate
|
||||
Circle()
|
||||
.fill(.yellow)
|
||||
.frame(width: 10, height: 10)
|
||||
} else if aqi > 200 && aqi < 301 {
|
||||
// Poor
|
||||
Circle()
|
||||
.fill(.orange)
|
||||
.frame(width: 10, height: 10)
|
||||
|
||||
} else if aqi > 300 && aqi < 401 {
|
||||
// Very Poor
|
||||
Circle()
|
||||
.fill(.red)
|
||||
.frame(width: 10, height: 10)
|
||||
} else if aqi >= 401 {
|
||||
// Very Poor
|
||||
Circle()
|
||||
.fill(Color(red: 0.8392, green: 0.0667, blue: 0))
|
||||
.frame(width: 10, height: 10)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
struct AQICircleDisplay_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
|
||||
VStack {
|
||||
AirQualityIndexCompact(aqi: 5)
|
||||
AirQualityIndexCompact(aqi: 51)
|
||||
AirQualityIndexCompact(aqi: 101)
|
||||
AirQualityIndexCompact(aqi: 201)
|
||||
AirQualityIndexCompact(aqi: 301)
|
||||
AirQualityIndexCompact(aqi: 401)
|
||||
}
|
||||
}
|
||||
}
|
||||
39
Meshtastic/Views/Helpers/Weather/IAQScale.swift
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
//
|
||||
// IAQScale.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Created by Garth Vander Houwen on 4/24/24.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct IAQScale: View {
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment:.leading) {
|
||||
ForEach(Iaq.allCases) { iaq in
|
||||
HStack {
|
||||
RoundedRectangle(cornerRadius: 5)
|
||||
.fill(iaq.color)
|
||||
.frame(width: 30, height: 20)
|
||||
Text(iaq.description)
|
||||
.font(.callout)
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
.background(.thinMaterial, in: RoundedRectangle(cornerRadius: 20, style: .continuous))
|
||||
// .overlay(
|
||||
// RoundedRectangle(cornerRadius: 20)
|
||||
// .stroke(.secondary, lineWidth: 5)
|
||||
// )
|
||||
}
|
||||
}
|
||||
|
||||
struct IAQSCalePreviews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
VStack {
|
||||
IAQScale()
|
||||
}
|
||||
}
|
||||
}
|
||||
148
Meshtastic/Views/Helpers/Weather/IndoorAirQuality.swift
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
//
|
||||
// IndoorAirQuality.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Copyright(c) by Garth Vander Houwen on 4/10/24.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
enum IaqDisplayMode: Int, CaseIterable, Identifiable {
|
||||
|
||||
case pill = 0
|
||||
case dot = 1
|
||||
case text = 2
|
||||
case gauge = 3
|
||||
case gradient = 4
|
||||
|
||||
var id: Int { self.rawValue }
|
||||
}
|
||||
|
||||
struct IndoorAirQuality: View {
|
||||
var iaq: Int = 0
|
||||
var displayMode: IaqDisplayMode = .pill
|
||||
let gradient = Gradient(colors: [.green, .mint, .yellow, .orange, .red, .purple, .purple, .brown, .brown, .brown, .brown])
|
||||
|
||||
var body: some View {
|
||||
let iaqEnum = Iaq.getIaq(for: iaq)
|
||||
switch displayMode {
|
||||
case .pill:
|
||||
ZStack (alignment: .leading) {
|
||||
RoundedRectangle(cornerRadius: 10)
|
||||
.fill(iaqEnum.color)
|
||||
.frame(width: 125, height: 30)
|
||||
Label("IAQ \(iaq)", systemImage: iaq < 100 ? "aqi.low" : ((iaq > 100 && iaq < 201) ? "aqi.medium" : "aqi.high"))
|
||||
.padding(.leading, 4)
|
||||
}
|
||||
case .dot:
|
||||
VStack {
|
||||
HStack {
|
||||
Text("\(iaq)")
|
||||
Circle()
|
||||
.fill(iaqEnum.color)
|
||||
.frame(width: 10, height: 10)
|
||||
}
|
||||
}
|
||||
case .text:
|
||||
Text(iaqEnum.description)
|
||||
.font(.caption)
|
||||
case .gauge:
|
||||
Gauge(value: Double(iaq), in: 0...500) {
|
||||
|
||||
Text("IAQ")
|
||||
.foregroundColor(iaqEnum.color)
|
||||
} currentValueLabel: {
|
||||
Text("\(Int(iaq))")
|
||||
}
|
||||
.tint(gradient)
|
||||
.gaugeStyle(.accessoryCircular)
|
||||
case .gradient:
|
||||
HStack {
|
||||
Gauge(value: Double(iaq), in: 0...500) {
|
||||
Text("IAQ")
|
||||
.foregroundColor(iaqEnum.color)
|
||||
} currentValueLabel: {
|
||||
Text("IAQ ")+Text("\(Int(iaq))")
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
.tint(gradient)
|
||||
.gaugeStyle(.accessoryLinear)
|
||||
Text(iaqEnum.description)
|
||||
.font(.caption)
|
||||
}
|
||||
.padding([.leading, .trailing])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct IndoorAirQuality_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
VStack {
|
||||
Text(".pill")
|
||||
.font(.title2)
|
||||
HStack {
|
||||
IndoorAirQuality(iaq: 6)
|
||||
IndoorAirQuality(iaq: 51)
|
||||
}
|
||||
HStack {
|
||||
IndoorAirQuality(iaq: 101)
|
||||
IndoorAirQuality(iaq: 201)
|
||||
}
|
||||
HStack {
|
||||
IndoorAirQuality(iaq: 350)
|
||||
IndoorAirQuality(iaq: 351)
|
||||
}
|
||||
Text(".dot")
|
||||
.font(.title2)
|
||||
HStack {
|
||||
IndoorAirQuality(iaq: 6, displayMode: .dot)
|
||||
IndoorAirQuality(iaq: 51, displayMode: .dot)
|
||||
IndoorAirQuality(iaq: 101, displayMode: .dot)
|
||||
IndoorAirQuality(iaq: 201, displayMode: .dot)
|
||||
IndoorAirQuality(iaq: 350, displayMode: .dot)
|
||||
IndoorAirQuality(iaq: 351, displayMode: .dot)
|
||||
}
|
||||
Text(".text")
|
||||
.font(.title2)
|
||||
HStack {
|
||||
IndoorAirQuality(iaq: 6, displayMode: .text)
|
||||
IndoorAirQuality(iaq: 51, displayMode: .text)
|
||||
IndoorAirQuality(iaq: 101, displayMode: .text)
|
||||
}
|
||||
HStack {
|
||||
IndoorAirQuality(iaq: 201, displayMode: .text)
|
||||
IndoorAirQuality(iaq: 350, displayMode: .text)
|
||||
IndoorAirQuality(iaq: 351, displayMode: .text)
|
||||
}
|
||||
Text(".gauge")
|
||||
.font(.title2)
|
||||
HStack (alignment: .top) {
|
||||
IndoorAirQuality(iaq: 6, displayMode: .gauge)
|
||||
IndoorAirQuality(iaq: 51, displayMode: .gauge)
|
||||
IndoorAirQuality(iaq: 101, displayMode: .gauge)
|
||||
IndoorAirQuality(iaq: 151, displayMode: .gauge)
|
||||
}
|
||||
HStack (alignment: .top) {
|
||||
IndoorAirQuality(iaq: 201, displayMode: .gauge)
|
||||
IndoorAirQuality(iaq: 251, displayMode: .gauge)
|
||||
IndoorAirQuality(iaq: 301, displayMode: .gauge)
|
||||
IndoorAirQuality(iaq: 351, displayMode: .gauge)
|
||||
}
|
||||
HStack (alignment: .top) {
|
||||
IndoorAirQuality(iaq: 401, displayMode: .gauge)
|
||||
IndoorAirQuality(iaq: 500, displayMode: .gauge)
|
||||
}
|
||||
Text(".gradient")
|
||||
.font(.title2)
|
||||
IndoorAirQuality(iaq: 6, displayMode: .gradient)
|
||||
IndoorAirQuality(iaq: 51, displayMode: .gradient)
|
||||
IndoorAirQuality(iaq: 101, displayMode: .gradient)
|
||||
IndoorAirQuality(iaq: 201, displayMode: .gradient)
|
||||
IndoorAirQuality(iaq: 351, displayMode: .gradient)
|
||||
IndoorAirQuality(iaq: 401, displayMode: .gradient)
|
||||
IndoorAirQuality(iaq: 500, displayMode: .gradient)
|
||||
|
||||
}.previewLayout(.fixed(width: 300, height: 800))
|
||||
}
|
||||
}
|
||||
|
|
@ -109,6 +109,24 @@ struct ChannelList: View {
|
|||
Label("Delete Messages", systemImage: "trash")
|
||||
}
|
||||
}
|
||||
Button {
|
||||
channel.mute = !channel.mute
|
||||
|
||||
do {
|
||||
let adminMessageId = bleManager.saveChannel(channel: channel.protoBuf, fromUser: node!.user!, toUser: node!.user!)
|
||||
if adminMessageId > 0 {
|
||||
context.refresh(channel, mergeChanges: true)
|
||||
}
|
||||
|
||||
try context.save()
|
||||
|
||||
} catch {
|
||||
context.rollback()
|
||||
print("💥 Save Channel Mute Error")
|
||||
}
|
||||
} label: {
|
||||
Label(channel.mute ? "Show Alerts" : "Hide Alerts", systemImage: channel.mute ? "bell" : "bell.slash")
|
||||
}
|
||||
}
|
||||
.confirmationDialog(
|
||||
"This conversation will be deleted.",
|
||||
|
|
|
|||
|
|
@ -43,16 +43,24 @@ struct ChannelMessageList: View {
|
|||
.padding(.trailing)
|
||||
}
|
||||
}
|
||||
HStack(alignment: .top) {
|
||||
HStack(alignment: .bottom) {
|
||||
if currentUser { Spacer(minLength: 50) }
|
||||
if !currentUser {
|
||||
CircleText(text: message.fromUser?.shortName ?? "?", color: Color(UIColor(hex: UInt32(message.fromUser?.num ?? 0))), circleSize: 44)
|
||||
.padding(.all, 5)
|
||||
.offset(y: -5)
|
||||
.offset(y: -7)
|
||||
}
|
||||
|
||||
VStack(alignment: currentUser ? .trailing : .leading) {
|
||||
let isDetectionSensorMessage = message.portNum == Int32(PortNum.detectionSensorApp.rawValue)
|
||||
|
||||
if !currentUser && message.fromUser != nil {
|
||||
Text("\(message.fromUser?.longName ?? "unknown".localized ) (\(message.fromUser?.userId ?? "?"))")
|
||||
.font(.caption)
|
||||
.foregroundColor(.gray)
|
||||
.offset(y: 8)
|
||||
}
|
||||
|
||||
HStack {
|
||||
MessageText(
|
||||
message: message,
|
||||
|
|
@ -67,7 +75,7 @@ struct ChannelMessageList: View {
|
|||
RetryButton(message: message, destination: .channel(channel))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TapbackResponses(message: message) {
|
||||
appState.unreadChannelMessages = myInfo.unreadMessages
|
||||
UIApplication.shared.applicationIconBadgeNumber = appState.unreadChannelMessages + appState.unreadDirectMessages
|
||||
|
|
|
|||
|
|
@ -32,7 +32,8 @@ struct NodeInfoItem: View {
|
|||
.frame(width: 75, height: 75)
|
||||
.cornerRadius(5)
|
||||
Text(String(node.user!.hwModel ?? "unset".localized))
|
||||
.font(.caption2).fixedSize()
|
||||
.font(.caption2)
|
||||
.frame(maxWidth: 125)
|
||||
}
|
||||
}
|
||||
if node.snr != 0 && !node.viaMqtt {
|
||||
|
|
|
|||
|
|
@ -308,18 +308,52 @@ func firstMissingChannelIndex(_ indexes: [Int]) -> Int {
|
|||
|
||||
enum PositionPrecision: Int, CaseIterable, Identifiable {
|
||||
|
||||
case two = 2
|
||||
case three = 3
|
||||
case four = 4
|
||||
case five = 5
|
||||
case six = 6
|
||||
case seven = 7
|
||||
case eight = 8
|
||||
case nine = 9
|
||||
case ten = 10
|
||||
case eleven = 11
|
||||
case twelve = 12
|
||||
case thirteen = 13
|
||||
case fourteen = 14
|
||||
case fifteen = 15
|
||||
case sixteen = 16
|
||||
case seventeen = 17
|
||||
case eightteen = 18
|
||||
case nineteen = 19
|
||||
case twenty = 20
|
||||
case twentyone = 21
|
||||
case twentytwo = 22
|
||||
case twentythree = 23
|
||||
case twentyfour = 24
|
||||
|
||||
var id: Int { self.rawValue }
|
||||
|
||||
var precisionMeters: Double {
|
||||
switch self {
|
||||
|
||||
case .two:
|
||||
return 5976446.981252
|
||||
case .three:
|
||||
return 2988223.4850600003
|
||||
case .four:
|
||||
return 1494111.7369640006
|
||||
case .five:
|
||||
return 747055.8629159998
|
||||
case .six:
|
||||
return 373527.9258920002
|
||||
case .seven:
|
||||
return 186763.95738000044
|
||||
case .eight:
|
||||
return 93381.97312400135
|
||||
case .nine:
|
||||
return 46690.98099600022
|
||||
case .ten:
|
||||
return 23345.48493200123
|
||||
case .eleven:
|
||||
return 11672.736900000944
|
||||
case .twelve:
|
||||
|
|
@ -332,25 +366,27 @@ enum PositionPrecision: Int, CaseIterable, Identifiable {
|
|||
return 729.5356200010741
|
||||
case .sixteen:
|
||||
return 364.7622440000765
|
||||
case .seventeen:
|
||||
return 182.37555600115968
|
||||
case .eightteen:
|
||||
return 91.1822120001193
|
||||
case .nineteen:
|
||||
return 45.58554000039009
|
||||
case .twenty:
|
||||
return 22.787204001316468
|
||||
case .twentyone:
|
||||
return 11.388036000988677
|
||||
case .twentytwo:
|
||||
return 5.688452000824781
|
||||
case .twentythree:
|
||||
return 2.8386600007428338
|
||||
case .twentyfour:
|
||||
return 1.413763999910884
|
||||
}
|
||||
}
|
||||
|
||||
var description: String {
|
||||
let distanceFormatter = MKDistanceFormatter()
|
||||
switch self {
|
||||
|
||||
case .eleven:
|
||||
return String.localizedStringWithFormat("position.precision %@".localized, String(distanceFormatter.string(fromDistance: precisionMeters)))
|
||||
case .twelve:
|
||||
return String.localizedStringWithFormat("position.precision %@".localized, String(distanceFormatter.string(fromDistance: precisionMeters)))
|
||||
case .thirteen:
|
||||
return String.localizedStringWithFormat("position.precision %@".localized, String(distanceFormatter.string(fromDistance: precisionMeters)))
|
||||
case .fourteen:
|
||||
return String.localizedStringWithFormat("position.precision %@".localized, String(distanceFormatter.string(fromDistance: precisionMeters)))
|
||||
case .fifteen:
|
||||
return String.localizedStringWithFormat("position.precision %@".localized, String(distanceFormatter.string(fromDistance: precisionMeters)))
|
||||
case .sixteen:
|
||||
return String.localizedStringWithFormat("position.precision %@".localized, String(distanceFormatter.string(fromDistance: precisionMeters)))
|
||||
}
|
||||
return String.localizedStringWithFormat("position.precision %@".localized, String(distanceFormatter.string(fromDistance: precisionMeters)))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -160,7 +160,7 @@ struct ChannelForm: View {
|
|||
if !preciseLocation {
|
||||
VStack(alignment: .leading) {
|
||||
Label("Approximate Location", systemImage: "location.slash.circle.fill")
|
||||
Slider(value: $positionPrecision, in: 11...16, step: 1) {
|
||||
Slider(value: $positionPrecision, in: 11...18, step: 1) {
|
||||
} minimumValueLabel: {
|
||||
Image(systemName: "minus")
|
||||
} maximumValueLabel: {
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ struct DeviceConfig: View {
|
|||
@State var rebroadcastMode = 0
|
||||
@State var nodeInfoBroadcastSecs = 10800
|
||||
@State var doubleTapAsButtonPress = false
|
||||
@State var ledHeartbeatEnabled = true
|
||||
@State var isManaged = false
|
||||
@State var tzdef = ""
|
||||
|
||||
|
|
@ -58,6 +59,12 @@ struct DeviceConfig: View {
|
|||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
|
||||
Toggle(isOn: $isManaged) {
|
||||
Label("Managed Device", systemImage: "gearshape.arrow.triangle.2.circlepath")
|
||||
Text("Enabling Managed mode will restrict access to all radio configurations, such as short/long names, regions, channels, modules, etc. and will only be accessible through the Admin channel. To avoid being locked out, make sure the Admin channel is working properly before enabling it.")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
|
||||
Picker("Node Info Broadcast Interval", selection: $nodeInfoBroadcastSecs ) {
|
||||
ForEach(UpdateIntervals.allCases) { ui in
|
||||
if ui.rawValue >= 3600 {
|
||||
|
|
@ -66,15 +73,18 @@ struct DeviceConfig: View {
|
|||
}
|
||||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
}
|
||||
Section(header: Text("Hardware")) {
|
||||
|
||||
Toggle(isOn: $doubleTapAsButtonPress) {
|
||||
Label("Double Tap as Button", systemImage: "hand.tap")
|
||||
Text("Treat double tap on supported accelerometers as a user button press.")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
|
||||
Toggle(isOn: $isManaged) {
|
||||
Label("Managed Device", systemImage: "gearshape.arrow.triangle.2.circlepath")
|
||||
Text("Enabling Managed mode will restrict access to all radio configurations, such as short/long names, regions, channels, modules, etc. and will only be accessible through the Admin channel. To avoid being locked out, make sure the Admin channel is working properly before enabling it.")
|
||||
Toggle(isOn: $ledHeartbeatEnabled) {
|
||||
Label("LED Heartbeat", systemImage: "waveform.path.ecg")
|
||||
Text("Controls the blinking LED on the device. For most devices this will control one of the up to 4 LEDS, the charger and GPS LEDs are not controllable.")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
}
|
||||
|
|
@ -202,6 +212,7 @@ struct DeviceConfig: View {
|
|||
dc.doubleTapAsButtonPress = doubleTapAsButtonPress
|
||||
dc.isManaged = isManaged
|
||||
dc.tzdef = tzdef
|
||||
dc.ledHeartbeatDisabled = !ledHeartbeatEnabled
|
||||
if isManaged {
|
||||
serialEnabled = false
|
||||
debugLogEnabled = false
|
||||
|
|
@ -300,7 +311,9 @@ struct DeviceConfig: View {
|
|||
nodeInfoBroadcastSecs = 3600
|
||||
}
|
||||
self.doubleTapAsButtonPress = node?.deviceConfig?.doubleTapAsButtonPress ?? false
|
||||
self.ledHeartbeatEnabled = node?.deviceConfig?.ledHeartbeatEnabled ?? true
|
||||
self.isManaged = node?.deviceConfig?.isManaged ?? false
|
||||
self.tzdef = node?.deviceConfig?.tzdef ?? ""
|
||||
if self.tzdef.isEmpty {
|
||||
self.tzdef = TimeZone.current.posixDescription
|
||||
self.hasChanges = true
|
||||
|
|
|
|||
|
|
@ -398,21 +398,20 @@ struct MQTTConfig: View {
|
|||
if !stateTopic.isEmpty {
|
||||
nearbyTopics.append(stateTopic)
|
||||
}
|
||||
let countyTopic = defaultTopic + "/" + (placemark.subAdministrativeArea?.lowercased().replacingOccurrences(of: " ", with: "") ?? "")
|
||||
let countyTopic = defaultTopic + "/" + (placemark.administrativeArea ?? "") + "/" + (placemark.subAdministrativeArea?.lowercased().replacingOccurrences(of: " ", with: "") ?? "")
|
||||
if !countyTopic.isEmpty {
|
||||
nearbyTopics.append(countyTopic)
|
||||
}
|
||||
let cityTopic = defaultTopic + "/" + (placemark.locality?.lowercased().replacingOccurrences(of: " ", with: "") ?? "")
|
||||
let cityTopic = defaultTopic + "/" + (placemark.administrativeArea ?? "") + "/" + (placemark.locality?.lowercased().replacingOccurrences(of: " ", with: "") ?? "")
|
||||
if !cityTopic.isEmpty {
|
||||
nearbyTopics.append(cityTopic)
|
||||
}
|
||||
let neightborhoodTopic = defaultTopic + "/" + (placemark.subLocality?.lowercased()
|
||||
let neightborhoodTopic = defaultTopic + "/" + (placemark.administrativeArea ?? "") + "/" + (placemark.subLocality?.lowercased()
|
||||
.replacingOccurrences(of: " ", with: "")
|
||||
.replacingOccurrences(of: "'", with: "") ?? "")
|
||||
if !neightborhoodTopic.isEmpty {
|
||||
nearbyTopics.append(neightborhoodTopic)
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -146,8 +146,12 @@ struct PositionConfig: View {
|
|||
.pickerStyle(SegmentedPickerStyle())
|
||||
.padding(.top, 5)
|
||||
.padding(.bottom, 5)
|
||||
|
||||
if gpsMode == 1 {
|
||||
|
||||
|
||||
Text("Positions will be provided by your device GPS, if you select disabled or not present you can set a fixed position.")
|
||||
.foregroundColor(.gray)
|
||||
.font(.callout)
|
||||
VStack(alignment: .leading) {
|
||||
Picker("Update Interval", selection: $gpsUpdateInterval) {
|
||||
ForEach(GpsUpdateIntervals.allCases) { ui in
|
||||
|
|
@ -163,7 +167,11 @@ struct PositionConfig: View {
|
|||
VStack(alignment: .leading) {
|
||||
Toggle(isOn: $fixedPosition) {
|
||||
Label("Fixed Position", systemImage: "location.square.fill")
|
||||
Text("If enabled your current phone location and time will be sent to the device and will broadcast over the mesh on the position interval.")
|
||||
if !(node?.positionConfig?.fixedPosition ?? false) {
|
||||
Text("Your current location will be set as the fixed position and broadcast over the mesh on the position interval.")
|
||||
} else {
|
||||
|
||||
}
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
}
|
||||
|
|
@ -280,6 +288,9 @@ struct PositionConfig: View {
|
|||
print("Set Position Failed")
|
||||
}
|
||||
print("Remove a fixed position here")
|
||||
let mutablePositions = node?.positions?.mutableCopy() as? NSMutableOrderedSet
|
||||
mutablePositions?.removeAllObjects()
|
||||
node?.positions = mutablePositions
|
||||
node?.positionConfig?.fixedPosition = false
|
||||
do {
|
||||
try context.save()
|
||||
|
|
@ -295,7 +306,6 @@ struct PositionConfig: View {
|
|||
if !bleManager.setFixedPosition(fromUser: node!.user!, channel: 0) {
|
||||
print("Set Position Failed")
|
||||
}
|
||||
print("Set a fixed position")
|
||||
node?.positionConfig?.fixedPosition = true
|
||||
do {
|
||||
try context.save()
|
||||
|
|
@ -374,14 +384,10 @@ struct PositionConfig: View {
|
|||
}
|
||||
}
|
||||
.onChange(of: fixedPosition) { newFixed in
|
||||
print("Changing Fixed Position Value")
|
||||
if supportedVersion {
|
||||
if node != nil && node!.positionConfig != nil {
|
||||
print("We have a node and position config")
|
||||
print("We have turned on fixed position \(!node!.positionConfig!.fixedPosition && newFixed)")
|
||||
/// Fixed Position is off to start
|
||||
if !node!.positionConfig!.fixedPosition && newFixed {
|
||||
print("fire alert")
|
||||
showingSetFixedAlert = true
|
||||
} else if node!.positionConfig!.fixedPosition && !newFixed {
|
||||
/// Fixed Position is on to start
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ struct Firmware: View {
|
|||
@Environment(\.managedObjectContext) var context
|
||||
@EnvironmentObject var bleManager: BLEManager
|
||||
var node: NodeInfoEntity?
|
||||
@State var minimumVersion = "2.3.5"
|
||||
@State var minimumVersion = "2.3.8"
|
||||
@State var version = ""
|
||||
@State private var currentDevice: DeviceHardware?
|
||||
@State private var latestStable: FirmwareRelease?
|
||||
|
|
@ -20,7 +20,7 @@ struct Firmware: View {
|
|||
|
||||
var body: some View {
|
||||
|
||||
let supportedVersion = bleManager.connectedVersion == "0.0.0" || self.minimumVersion.compare(bleManager.connectedVersion, options: .numeric) == .orderedAscending || minimumVersion.compare(bleManager.connectedVersion, options: .numeric) == .orderedSame
|
||||
let supportedVersion = self.minimumVersion.compare(bleManager.connectedVersion, options: .numeric) == .orderedAscending || minimumVersion.compare(bleManager.connectedVersion, options: .numeric) == .orderedSame
|
||||
ScrollView {
|
||||
VStack(alignment: .leading) {
|
||||
let deviceString = currentDevice?.hwModelSlug.replacingOccurrences(of: "_", with: "")
|
||||
|
|
@ -57,12 +57,12 @@ struct Firmware: View {
|
|||
.font(.title3)
|
||||
.padding(.bottom)
|
||||
} else {
|
||||
Text("Your Firmware is out of date")
|
||||
Text("Newer firmware is available")
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.foregroundStyle(.red)
|
||||
.font(.title2)
|
||||
.padding(.bottom)
|
||||
Text("Current Firmware Version: \(bleManager.connectedVersion), Minimium Firmware Version: \(minimumVersion)")
|
||||
Text("Current Firmware Version: \(bleManager.connectedVersion), Latest Firmware Version: \(minimumVersion)")
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.font(.title3)
|
||||
.padding(.bottom)
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ struct UserConfig: View {
|
|||
.onChange(of: longName, perform: { _ in
|
||||
let totalBytes = longName.utf8.count
|
||||
// Only mess with the value if it is too big
|
||||
if totalBytes > (isLicensed ? 6 : 36) {
|
||||
if totalBytes > (isLicensed ? 8 : 36) {
|
||||
longName = String(longName.dropLast())
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -6,6 +6,16 @@
|
|||
<string>Root</string>
|
||||
<key>PreferenceSpecifiers</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>Type</key>
|
||||
<string>PSTitleValueSpecifier</string>
|
||||
<key>DefaultValue</key>
|
||||
<string></string>
|
||||
<key>Title</key>
|
||||
<string>Will share your phone GPS location with your node.</string>
|
||||
<key>Key</key>
|
||||
<string>shareLocationTitle</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>Type</key>
|
||||
<string>PSToggleSwitchSpecifier</string>
|
||||
|
|
@ -20,7 +30,7 @@
|
|||
<key>Type</key>
|
||||
<string>PSMultiValueSpecifier</string>
|
||||
<key>Title</key>
|
||||
<string>Share Location Interval</string>
|
||||
<string>Interval</string>
|
||||
<key>Key</key>
|
||||
<string>provideLocationInterval</string>
|
||||
<key>Values</key>
|
||||
|
|
@ -78,9 +88,9 @@
|
|||
<key>Type</key>
|
||||
<string>PSToggleSwitchSpecifier</string>
|
||||
<key>Title</key>
|
||||
<string>Low Battery</string>
|
||||
<string>Channel Messages</string>
|
||||
<key>Key</key>
|
||||
<string>lowBatteryNotifications</string>
|
||||
<string>channelMessageNotifications</string>
|
||||
<key>DefaultValue</key>
|
||||
<true/>
|
||||
</dict>
|
||||
|
|
@ -94,6 +104,16 @@
|
|||
<key>DefaultValue</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>Type</key>
|
||||
<string>PSToggleSwitchSpecifier</string>
|
||||
<key>Title</key>
|
||||
<string>Low Battery</string>
|
||||
<key>Key</key>
|
||||
<string>lowBatteryNotifications</string>
|
||||
<key>DefaultValue</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit 86640f20db7b9b5be42949d18e8d96ad10d47a68
|
||||
Subproject commit dd7d64cc038a6365c119ec7508762cc45f405948
|
||||
|
|
@ -7,19 +7,19 @@
|
|||
*/
|
||||
"about"="关于";
|
||||
"about.meshtastic"="关于 Meshtastic";
|
||||
"activity"="Activity";
|
||||
"activity"="活动";
|
||||
"admin"="管理员";
|
||||
"admin.log"="管理员消息日志";
|
||||
"ago"="ago";
|
||||
"ago"="之前";
|
||||
"airtime"="广播时间";
|
||||
"always.on"="常亮";
|
||||
"ambient.lighting"="Ambient Lighting";
|
||||
"ambient.lighting.config"="Ambient Lighting Config";
|
||||
"ambient.lighting"="氛围灯";
|
||||
"ambient.lighting.config"="氛围灯配置";
|
||||
"appsettings"="通用设置";
|
||||
"appsettings.provide.location"="提供定位到 Mesh 网络";
|
||||
"appsettings.smartposition"="Smart Position";
|
||||
"appsettings.smartposition"="智能定位";
|
||||
"are.you.sure"="是否确认?";
|
||||
"ascii.capable"="ASCII Capable";
|
||||
"ascii.capable"="ASCII 兼容";
|
||||
"available.radios"="可以连接的电台";
|
||||
"automatic.detection"="自动识别";
|
||||
"battery.level"="电池电量";
|
||||
|
|
@ -52,28 +52,28 @@
|
|||
"clear.app.data"="清除 App 数据";
|
||||
"clear.log"="清除日志";
|
||||
"close"="关闭";
|
||||
"config.power.settings"="Power";
|
||||
"config.power.title"="Power Config";
|
||||
"config.power.section.battery"="Battery";
|
||||
"config.power.section.sleep"="Sleep";
|
||||
"config.power.adc.override"="ADC Override";
|
||||
"config.power.adc.multiplier"="Multiplier";
|
||||
"config.power.ls.secs"="Light Sleep Interval";
|
||||
"config.power.min.wake.secs"="Minimum Wake Interval";
|
||||
"config.power.saving"="Power Saving";
|
||||
"config.power.saving.description"="Will sleep everything as much as possible, for the tracker and sensor role this will also include the lora radio. Don't use this setting if you want to use your device with the phone apps or are using a device without a user button.";
|
||||
"config.power.shutdown.on.power.loss"="Shutdown on Power Loss";
|
||||
"config.power.settings"="电源";
|
||||
"config.power.title"="电源配置";
|
||||
"config.power.section.battery"="电池";
|
||||
"config.power.section.sleep"="休眠";
|
||||
"config.power.adc.override"="ADC 修改";
|
||||
"config.power.adc.multiplier"="ADC 放大";
|
||||
"config.power.ls.secs"="轻度睡眠间隔";
|
||||
"config.power.min.wake.secs"="最小唤醒间隔";
|
||||
"config.power.saving"="省电模式";
|
||||
"config.power.saving.description"="尽可能将所有部件都置于休眠状态,对于跟踪器和传感器功能,这还包括 LoRa 无线电。如果您要使用手机应用程序或者使用没有用户按钮的设备,请不要使用这个设置。";
|
||||
"config.power.shutdown.on.power.loss"="断电时关机";
|
||||
"config.power.shutdown.after.secs"="After";
|
||||
"config.power.wait.bluetooth.secs"="Bluetooth Off After";
|
||||
"config.ringtone"="RTTTL Ringtone";
|
||||
"config.ringtone.title"="Ringtone Config";
|
||||
"config.ringtone.label"="Ringtone Transfer Language";
|
||||
"config.ringtone.description"="Ringtone Transfer Language(RTTTL) Ringtone String used by supported buzzers in external notifications.";
|
||||
"config.module.paxcounter.settings"="PAX Counter";
|
||||
"config.module.paxcounter.title"="PAX Counter Config";
|
||||
"config.module.paxcounter.enabled.description"="When enabled the PAX Counter module counts the number of people passing by using WiFi and Bluetooth. Both WiFI and Bluetooth must be disabled for PAX counter to work.";
|
||||
"config.module.paxcounter.updateinterval"="Update Interval";
|
||||
"config.module.paxcounter.updateinterval.description"="How often we can send a message to the mesh when people are detected.";
|
||||
"config.power.wait.bluetooth.secs"="蓝牙关闭 After";
|
||||
"config.ringtone"="RTTTL 铃声";
|
||||
"config.ringtone.title"="铃声配置";
|
||||
"config.ringtone.label"="铃声传输语言";
|
||||
"config.ringtone.description"="支持外部通知中使用的铃声传输语言 (RTTTL) 铃声字符串。";
|
||||
"config.module.paxcounter.settings"="PAX 计数器";
|
||||
"config.module.paxcounter.title"="PAX 计数器配置";
|
||||
"config.module.paxcounter.enabled.description"="启用 PAX 计数器模块时,通过使用 WiFi 和蓝牙来计算经过的人数。为了使 PAX 计数器正常工作,必须将 WiFi 和蓝牙都禁用。";
|
||||
"config.module.paxcounter.updateinterval"="更新间隔";
|
||||
"config.module.paxcounter.updateinterval.description"="检测到人员时,我们可以隔多久发送一条消息到 Mesh";
|
||||
"config.save.confirm"="电台将会在配置保存后重启。";
|
||||
"connected.radio"="已连接的电台";
|
||||
"communicating"="与电台进行通讯中...";
|
||||
|
|
@ -85,24 +85,24 @@
|
|||
"current"="当前";
|
||||
"default"="默认";
|
||||
"delete"="删除";
|
||||
"detection.sensor"="Detection Sensor";
|
||||
"detection.sensor"="检测传感器";
|
||||
"device"="电台";
|
||||
"device.config"="电台配置";
|
||||
"device.configuration"="Device Configuration";
|
||||
"device.configuration"="设备配置";
|
||||
"device.metrics.delete"="删除所有电台指标?";
|
||||
"device.metrics.log"="电台指标日志";
|
||||
"device.role.client"="标准模式 - App 可以连接到电台进行收发操作,并且会自动转发 Mesh 网络中其他节点的消息。";
|
||||
"device.role.clientmute"="静默模式 - 与标准模式类似,App 可以连接到电台进行收发操作,但不会转发 Mesh 网络中其他节点的消息。";
|
||||
"device.role.clienthidden"=" Used for nodes that \"only speak when spoken to\" Turns all of the routine broadcasts but allows for ad-hoc communication. Still rebroadcasts, but with local only rebroadcast mode (known meshes only). Can be used for private operation or to dramatically reduce airtime / power consumption.";
|
||||
"device.role.lostandfound"="Used to automatically send a text message to the mesh with the current position of the device on a frequent interval: \"I'm lost! Position: lat / long\"";
|
||||
"device.role.clienthidden"="用于\"只有在被请求时才发言\"的节点。关闭所有常规广播,但允许临时通信。仍然会进行转发,但采用本地转发模式(仅限已知的网络)。可用于私密操作或大幅减少空中时间/功耗。";
|
||||
"device.role.lostandfound"="用于定期自动向 Mesh 发送包含设备当前位置的文本消息的功能:\"I'm lost! Position: lat / long\"";
|
||||
"device.role.router"="纯路由模式 - 自动转发 Mesh 网络中其他节点的消息,中继模式下屏幕会熄灭,Wi-Fi 和蓝牙将会进入睡眠模式,App 将无法连接到电台进行收发操作。";
|
||||
"device.role.routerclient"="路由客户端模式 - 优先转发 Mesh 网络中其他节点的消息,App 也可以连接到电台进行收发操作。";
|
||||
"device.role.repeater"="中继模式 - Mesh 网络数据包将优先通过此节点路由。此模式可消除不必要的开销,如 NodeInfo、DeviceTelemetry 和任何其他 Mesh 数据包,从而使设备不显示为 Mesh 网络的一部分。有关此角色的其他特定设置,请参阅转播模式。";
|
||||
"device.role.repeater"="中继模式 - Mesh 网络数据包将优先通过此节点路由。此模式可消除不必要的开销,如节点信息、设备遥测和任何其他 Mesh 数据包,从而使设备不显示为 Mesh 网络的一部分。有关此角色的其他特定设置,请参阅转播模式。";
|
||||
"device.role.tracker"="定位模式 - 用于作为 GPS 跟踪器。从该设备发送的定位数据包优先级较高,每两分钟广播一次。智能位置广播默认为关闭。";
|
||||
"device.role.lostandfound"="Broadcasts location as message to default channel regularly for to assist with device recovery.";
|
||||
"device.role.sensor"="Broadcasts telemetry packets as priority.";
|
||||
"device.role.tak"="Optimized for ATAK system communication, reduces routine broadcasts.";
|
||||
"device.role.taktracker"="Enables automatic TAK PLI broadcasts and reduces routine broadcasts.";
|
||||
"device.role.lostandfound"="定期向默认通道广播位置信息,以帮助寻回设备。";
|
||||
"device.role.sensor"="将遥测数据包优先广播。";
|
||||
"device.role.tak"="针对 ATAK 系统通信进行优化,减少常规广播。";
|
||||
"device.role.taktracker"="启用自动 TAK PLI(Position Location Information)广播,并减少常规广播。";
|
||||
"direct.messages"="直频消息";
|
||||
"dismiss.keyboard"="隐藏键盘";
|
||||
"display"="屏幕(电台屏幕)";
|
||||
|
|
@ -119,8 +119,8 @@
|
|||
"finish"="Finish";
|
||||
"firmware.version"="固件版本";
|
||||
"firmware.version.unsupported"="检测到不支持的固件版本,无法连接到电台。";
|
||||
"gas"="Gas";
|
||||
"gas.resistance"="Gas Resistance";
|
||||
"gas"="气体";
|
||||
"gas.resistance"="气体阻抗";
|
||||
"generate.qr.code"="生成二维码";
|
||||
"gpsformat.dec"="十进制";
|
||||
"gpsformat.dms"="度分秒";
|
||||
|
|
@ -178,12 +178,12 @@
|
|||
"map.centering"="居中";
|
||||
"map.tiles.delete"="删除已缓存的地图区块";
|
||||
"map.recentering"="自动重新居中";
|
||||
"map.use.legacy"="Use Legacy Mesh Map";
|
||||
"map.use.legacy"="使用传统网状地图";
|
||||
"map.type"="地图类型";
|
||||
"map.usertrackingmode"="用户跟随模式";
|
||||
"map.usertrackingmode.none"="无";
|
||||
"map.usertrackingmode.follow"="跟随";
|
||||
"map.usertrackingmode.followwithheading"="Follow with heading";
|
||||
"map.usertrackingmode.followwithheading"="跟随航向";
|
||||
"mesh.live.activity"="Mesh 实时活动";
|
||||
"mesh.log"="Mesh 日志";
|
||||
"mesh.log.ambientlighting.config %@"="Ambient Lighting module config received: %@";
|
||||
|
|
@ -231,17 +231,17 @@
|
|||
"mode"="模式";
|
||||
"module.configuration"="模块配置";
|
||||
"mqtt"="MQTT";
|
||||
"mqtt.connect"="Connect to MQTT";
|
||||
"mqtt.connect"="连接至 MQTT";
|
||||
"mqtt.config"="MQTT 配置";
|
||||
"mqtt.clientproxy"="MQTT 客户端代理";
|
||||
"mqtt.disconnect"="Disconnect from MQTT";
|
||||
"mqtt.disconnect"="断开 MQTT 连接";
|
||||
"mqtt.username"="用户名称";
|
||||
"name"="名称";
|
||||
"network"="网络";
|
||||
"network.config"="网络配置";
|
||||
"nodes"="节点";
|
||||
"nodes %@"="节点 (%@)";
|
||||
"nodelist.filter.distance %@"="up to %@ away";
|
||||
"nodelist.filter.distance %@"="最远距离 %@";
|
||||
"no.nodes"="未找到 Meshtastic 节点";
|
||||
"not.connected"="未连接到电台";
|
||||
"numbers.punctuation"="数字和标点符号";
|
||||
|
|
@ -250,7 +250,7 @@
|
|||
"on.boot"="仅在启动时";
|
||||
"options"="选项";
|
||||
"password"="密码";
|
||||
"pause"="Pause";
|
||||
"pause"="暂停";
|
||||
"phone.gps"="手机 GPS";
|
||||
"phone.gps.interval.description"="电台通过手机获取定位的时间间隔,但是向 Mesh 网络中刷新定位的时间间隔由电台控制。";
|
||||
"position"="定位";
|
||||
|
|
@ -265,11 +265,11 @@
|
|||
"reboot.node"="重启节点?";
|
||||
"received.ack"="收到确认";
|
||||
"received.ack.real"="收件人确认";
|
||||
"resume"="Resume";
|
||||
"resume"="恢复";
|
||||
"ringtone"="铃声";
|
||||
"ringtone.config"="铃声设置";
|
||||
"route.recorder"="Route Recorder";
|
||||
"routes"="Routes";
|
||||
"route.recorder"="路径记录器";
|
||||
"routes"="路径";
|
||||
"routing.acknowledged"="确认";
|
||||
"routing.noroute"="找不到目标";
|
||||
"routing.gotnak"="收到否认";
|
||||
|
|
@ -303,7 +303,7 @@
|
|||
"set.region"="设置 LoRa 区域";
|
||||
"standard"="标准";
|
||||
"standard.muted"="标准静音";
|
||||
"start"="Start";
|
||||
"start"="开始";
|
||||
"ssid"="SSID";
|
||||
"storeforward"="储存 & 转发";
|
||||
"storeforward.config"="储存 & 转发设置";
|
||||
|
|
@ -323,10 +323,10 @@
|
|||
"timestamp"="时间戳";
|
||||
"tip.bluetooth.connect.title"="连接到 LoRa 电台";
|
||||
"tip.bluetooth.connect.message"="显示当前通过蓝牙连接的 Lora 电台的信息。您可以向左滑动断开电台,长按查看统计信息或开始实时活动。";
|
||||
"tip.channel.admin.title"="Admin Channel";
|
||||
"tip.channel.admin.message"="Admin channel detected: Select a node from the drop down to manage connected or remote devices.";
|
||||
"tip.channels.create.title"="Manage Channels";
|
||||
"tip.channels.create.message"="Most data on your mesh is sent over the primary channel. You can set up secondary channels to create additional messaging groups secured by their own key. [Channel config tips](https://meshtastic.org/docs/configuration/radio/channels/)";
|
||||
"tip.channel.admin.title"="admin 频道";
|
||||
"tip.channel.admin.message"="检测到 admin 频道:请从下拉菜单中选择一个节点,来管理已连接或远程设备。";
|
||||
"tip.channels.create.title"="管理频道";
|
||||
"tip.channels.create.message"="大多数 Mesh 上的数据都通过主要频道发送。您可以设置次要频道,以创建额外的消息组,并通过其自己的密钥进行安全保护。 [频道配置提示](https://meshtastic.org/docs/configuration/radio/channels/)";
|
||||
"tip.channels.share.title"="共享 Meshtastic 频道";
|
||||
"tip.channels.share.message"="在 Meshtastic 网络中最多有 8 个频道。第一个频道是主频道,大多数活动都发生在这里,也是必需的。如果您不共享主频道,您的第一个共享频道就会成为其他网络的主频道。它会在其主频道和您的辅助频道上对话。名称为 admin 的频道可远程控制节点。其他频道用于私人群组,每个群组都有自己的密钥。";
|
||||
"tip.messages.title"="消息";
|
||||
|
|
@ -341,4 +341,4 @@
|
|||
"user.details"="用户信息";
|
||||
"voltage"="电压";
|
||||
"waiting"="等待中...";
|
||||
"appsettings.newNodeNotifications"="New Node Notifications";
|
||||
"appsettings.newNodeNotifications"="新节点通知";
|
||||
|
|
|
|||