mirror of
https://github.com/meshtastic/Meshtastic-Apple.git
synced 2026-04-20 22:13:56 +00:00
commit
6ee3a4ec5f
32 changed files with 2000 additions and 937 deletions
|
|
@ -27,16 +27,6 @@
|
|||
},
|
||||
"%@" : {
|
||||
|
||||
},
|
||||
"%@ - %@" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "%1$@ - %2$@"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"%@ - %@ - %@" : {
|
||||
"localizations" : {
|
||||
|
|
@ -47,6 +37,28 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"%@ - %d Hops Towards %d Hops Back" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "%1$@ - %2$d Hops Towards %3$d Hops Back"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"%@ - 1 Hop" : {
|
||||
|
||||
},
|
||||
"%@ - Direct" : {
|
||||
|
||||
},
|
||||
"%@ - No Response" : {
|
||||
|
||||
},
|
||||
"%@ - Not Sent" : {
|
||||
|
||||
},
|
||||
"%@ (%@)" : {
|
||||
"localizations" : {
|
||||
|
|
@ -469,9 +481,6 @@
|
|||
},
|
||||
"Admin & Direct Message Keys" : {
|
||||
|
||||
},
|
||||
"Admin Key" : {
|
||||
|
||||
},
|
||||
"admin.log" : {
|
||||
"extractionState" : "manual",
|
||||
|
|
@ -1534,13 +1543,13 @@
|
|||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "尝试连接%@失败,你可能需要在系统设置的蓝牙选项中忽略该电台。"
|
||||
"value" : "尝试连接%d失败,你可能需要在系统设置的蓝牙选项中忽略该电台。"
|
||||
}
|
||||
},
|
||||
"zh-Hant-TW" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "嘗試連接%@失敗,你可能需要在系统設定的藍芽選項中忽略該電台。"
|
||||
"value" : "嘗試連接%d失敗,你可能需要在系统設定的藍芽選項中忽略該電台。"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5198,9 +5207,6 @@
|
|||
},
|
||||
"Detection sensor messages are received as text messages. If you enable notifications you will recieve a notification for each detection message received and a corresponding unread message badge." : {
|
||||
|
||||
},
|
||||
"Detection trigger High" : {
|
||||
|
||||
},
|
||||
"detection.sensor" : {
|
||||
"localizations" : {
|
||||
|
|
@ -5877,55 +5883,55 @@
|
|||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"state" : "needs_review",
|
||||
"value" : "Repeater - Mesh packets will prefer to be routed over this node. This role eliminates unnecessary overhead such as NodeInfo, DeviceTelemetry, and any other mesh packet, resulting in the device not appearing as part of the network. Please see Rebroadcast Mode for additional settings specific to this role."
|
||||
}
|
||||
},
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Infrastructure node for extending network coverage by relaying messages with minimal overhead. Not visible in Nodes list."
|
||||
"value" : "Infrastructure node on a tower or mountain top only. Not to be used for roofs or mobile nodes. Relays messages with minimal overhead. Not visible in Nodes list."
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"state" : "needs_review",
|
||||
"value" : "Noeud d'infrastructure qui étend la couverture du réseau en relayant les messages avec un minimum de surcharge. Invisible dans la liste des noeuds."
|
||||
}
|
||||
},
|
||||
"he" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"state" : "needs_review",
|
||||
"value" : "מכשיר תשתית להרחבת המש על ידי העברת הודעות עם דאטה נוסף מינימלי."
|
||||
}
|
||||
},
|
||||
"pl" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"state" : "needs_review",
|
||||
"value" : "Przekaźnik - Pakiety siatki będą preferować trasowanie przez ten węzeł. Ta rola eliminuje niepotrzebny nadmiar, taki jak NodeInfo, DeviceTelemetry i inne pakiety siatki, skutkując tym, że urządzenie nie będzie widoczne jako część sieci. Proszę zobaczyć tryb Rebroadcast dla dodatkowych ustawień specyficznych dla tej roli."
|
||||
}
|
||||
},
|
||||
"pt-PT" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"state" : "needs_review",
|
||||
"value" : "Nó de infraestrutura para ampliar a cobertura da rede transmitindo mensagens com sobrecarga mínima. Não visível na lista de Nós."
|
||||
}
|
||||
},
|
||||
"se" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"state" : "needs_review",
|
||||
"value" : "Infrastrukturnod för att utöka nätverkstäckningen genom att vidarebefordra meddelanden med minimal overhead. Syns inte i Noder-listan."
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"state" : "needs_review",
|
||||
"value" : "中继模式 - Mesh 网络数据包将优先通过此节点路由。此模式可消除不必要的开销,如节点信息、设备遥测和任何其他 Mesh 数据包,从而使设备不显示为 Mesh 网络的一部分。有关此角色的其他特定设置,请参阅转播模式。"
|
||||
}
|
||||
},
|
||||
"zh-Hant-TW" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"state" : "needs_review",
|
||||
"value" : "中繼模式 - Mesh 網路數據包將優先通過此中繼點路由。此模式可消除不必要的開銷,如 NodeInfo、DeviceTelemetry 和任何其他 Mesh 數據包,從而使設備不顯示為 Mesh 網路的一部分。有關此角色的其他特定設置,請參閱轉播模式。"
|
||||
}
|
||||
}
|
||||
|
|
@ -5936,55 +5942,55 @@
|
|||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"state" : "needs_review",
|
||||
"value" : "Router - Mesh Pakete werden bevorzugt über diesen Node gerouted. Dieser Node wird nicht von einer Client App benutzt. WLAN, Bluetooth und Display sind aus."
|
||||
}
|
||||
},
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Infrastructure node for extending network coverage by relaying messages. Visible in Nodes list."
|
||||
"value" : "Infrastructure node on a tower or mountain top only. Not to be used for roofs or mobile nodes. Needs exceptional coverage. Visible in Nodes list."
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"state" : "needs_review",
|
||||
"value" : "Noeud d'infrastructure qui étend la couverture du réseau en relayant les messages. Visible dans la liste des noeuds."
|
||||
}
|
||||
},
|
||||
"he" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"state" : "needs_review",
|
||||
"value" : "מכשיר תשתית להרחבת המש על ידי העברת הודעות. מופיע ברשימת מכשירים."
|
||||
}
|
||||
},
|
||||
"pl" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"state" : "needs_review",
|
||||
"value" : "Router - Pakiety siatki będą preferować trasowanie przez ten węzeł. Zakłada, że urządzenie będzie działać samodzielnie, umieszczone w miejscu z przewagą zasięgu. UWAGA: Radia BLE/Wi-Fi i ekran OLED zostaną uśpione."
|
||||
}
|
||||
},
|
||||
"pt-PT" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"state" : "needs_review",
|
||||
"value" : "Nó de infraestrutura para ampliar a cobertura da rede transmitindo mensagens. Visível na lista de Nós."
|
||||
}
|
||||
},
|
||||
"se" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"state" : "needs_review",
|
||||
"value" : "Infrastrukturnod för att utöka nätverkstäckningen genom att vidarebefordra meddelanden. Synlig i Noder-listan."
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"state" : "needs_review",
|
||||
"value" : "纯路由模式 - 自动转发 Mesh 网络中其他节点的消息,中继模式下屏幕会熄灭,Wi-Fi 和蓝牙将会进入睡眠模式,App 将无法连接到电台进行收发操作。"
|
||||
}
|
||||
},
|
||||
"zh-Hant-TW" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"state" : "needs_review",
|
||||
"value" : "纯路由模式 - 自動轉發 Mesh 網路中其他中繼點的消息,中繼模式下螢幕會熄滅,Wi-Fi 和藍芽將會進入睡眠模式,App 將無法連接到電台進行收發操作。"
|
||||
}
|
||||
}
|
||||
|
|
@ -6002,7 +6008,7 @@
|
|||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Combination of both ROUTER and CLIENT. Not for mobile devices. Deprecated"
|
||||
"value" : "Deprecated role use client."
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
|
|
@ -10884,9 +10890,6 @@
|
|||
},
|
||||
"Location:" : {
|
||||
|
||||
},
|
||||
"Location: %@" : {
|
||||
|
||||
},
|
||||
"Locked" : {
|
||||
|
||||
|
|
@ -16683,6 +16686,9 @@
|
|||
},
|
||||
"Primary" : {
|
||||
|
||||
},
|
||||
"Primary Admin Key" : {
|
||||
|
||||
},
|
||||
"Primary GPIO" : {
|
||||
|
||||
|
|
@ -18913,6 +18919,9 @@
|
|||
},
|
||||
"Secondary" : {
|
||||
|
||||
},
|
||||
"Secondary Admin Key" : {
|
||||
|
||||
},
|
||||
"Security" : {
|
||||
|
||||
|
|
@ -21031,6 +21040,9 @@
|
|||
},
|
||||
"Ten Minutes" : {
|
||||
|
||||
},
|
||||
"Tertiary Admin Key" : {
|
||||
|
||||
},
|
||||
"The amount of time to wait before we consider your packet as done." : {
|
||||
|
||||
|
|
@ -21062,7 +21074,7 @@
|
|||
"The most recent public key for this node does not match the previously recorded key. You can delete the node and let it exchange keys again, but this also may indicate a more serious security problem. Contact the user through another trusted channel to determine if the key change was due to a factory reset or other intentional action." : {
|
||||
|
||||
},
|
||||
"The public key authorized to send admin messages to this node." : {
|
||||
"The primary public key authorized to send admin messages to this node." : {
|
||||
|
||||
},
|
||||
"The public key does not match the recorded key. You may delete the node and let it exchange keys again, but this may indicate a more serious security problem. Contact the user through another trusted channel, to determine if the key change was due to a factory reset or other intentional action." : {
|
||||
|
|
@ -21073,9 +21085,15 @@
|
|||
},
|
||||
"The root topic to use for MQTT." : {
|
||||
|
||||
},
|
||||
"The secondary public key authorized to send admin messages to this node." : {
|
||||
|
||||
},
|
||||
"The state of the LED (on/off)" : {
|
||||
|
||||
},
|
||||
"The tertiarypublic key authorized to send admin messages to this node." : {
|
||||
|
||||
},
|
||||
"There has been no response to a request for device metadata over the admin channel for this node." : {
|
||||
|
||||
|
|
@ -21866,6 +21884,9 @@
|
|||
},
|
||||
"Treat double tap on supported accelerometers as a user button press." : {
|
||||
|
||||
},
|
||||
"TriggerType" : {
|
||||
|
||||
},
|
||||
"Triple Click Ad Hoc Ping" : {
|
||||
|
||||
|
|
@ -22607,9 +22628,6 @@
|
|||
},
|
||||
"When using in GPIO mode, keep the output on for this long. " : {
|
||||
|
||||
},
|
||||
"Whether or not the GPIO pin state detection is triggered on HIGH (1) or LOW (0)" : {
|
||||
|
||||
},
|
||||
"WiFi Options" : {
|
||||
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@
|
|||
D9C983A22B79D1A600BDBE6A /* RequestPositionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9C983A12B79D1A600BDBE6A /* RequestPositionButton.swift */; };
|
||||
DD007BAE2AA4E91200F5FA12 /* MyInfoEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD007BAD2AA4E91200F5FA12 /* MyInfoEntityExtension.swift */; };
|
||||
DD007BB02AA5981000F5FA12 /* NodeInfoEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD007BAF2AA5981000F5FA12 /* NodeInfoEntityExtension.swift */; };
|
||||
DD0BE3102CB9FDC4000BA445 /* DetectionSensorEnums.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD0BE30F2CB9FDC4000BA445 /* DetectionSensorEnums.swift */; };
|
||||
DD0D3D222A55CEB10066DB71 /* CocoaMQTT in Frameworks */ = {isa = PBXBuildFile; productRef = DD0D3D212A55CEB10066DB71 /* CocoaMQTT */; };
|
||||
DD0E21012B8A6F1300F2D100 /* DeviceHardware.json in Resources */ = {isa = PBXBuildFile; fileRef = DD0E21002B8A6BC500F2D100 /* DeviceHardware.json */; };
|
||||
DD13AA492AB73BF400BA0C98 /* PositionPopover.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD13AA482AB73BF400BA0C98 /* PositionPopover.swift */; };
|
||||
|
|
@ -299,6 +300,8 @@
|
|||
DD007BAD2AA4E91200F5FA12 /* MyInfoEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyInfoEntityExtension.swift; sourceTree = "<group>"; };
|
||||
DD007BAF2AA5981000F5FA12 /* NodeInfoEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeInfoEntityExtension.swift; sourceTree = "<group>"; };
|
||||
DD05296F2B77F454008E44CD /* MeshtasticDataModelV 26.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 26.xcdatamodel"; sourceTree = "<group>"; };
|
||||
DD0BE30C2CB785D8000BA445 /* MeshtasticDataModelV 46.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 46.xcdatamodel"; sourceTree = "<group>"; };
|
||||
DD0BE30F2CB9FDC4000BA445 /* DetectionSensorEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetectionSensorEnums.swift; sourceTree = "<group>"; };
|
||||
DD0E20FF2B892E1300F2D100 /* MeshtasticDataModelV 28.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 28.xcdatamodel"; sourceTree = "<group>"; };
|
||||
DD0E21002B8A6BC500F2D100 /* DeviceHardware.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = DeviceHardware.json; sourceTree = "<group>"; };
|
||||
DD0E9C222A30CE3A00580CBB /* MeshtasticDataModelV14.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV14.xcdatamodel; sourceTree = "<group>"; };
|
||||
|
|
@ -796,6 +799,7 @@
|
|||
DDB6ABD828B0A4BA00384BA1 /* BluetoothModes.swift */,
|
||||
DD1925B628CDA5A400720036 /* CannedMessagesConfigEnums.swift */,
|
||||
DDA1C48D28DB49D3009933EC /* ChannelRoles.swift */,
|
||||
DD0BE30F2CB9FDC4000BA445 /* DetectionSensorEnums.swift */,
|
||||
DDB6ABDF28B13AC700384BA1 /* DeviceEnums.swift */,
|
||||
DDB6ABE328B13FFF00384BA1 /* DisplayEnums.swift */,
|
||||
DD5D0A9B2931B9F200F7EA61 /* EthernetModes.swift */,
|
||||
|
|
@ -1394,6 +1398,7 @@
|
|||
DD2553572855B02500E55709 /* LoRaConfig.swift in Sources */,
|
||||
DDB6ABD928B0A4BA00384BA1 /* BluetoothModes.swift in Sources */,
|
||||
DD1BD0EE2C603C91008C0C70 /* CustomFormatters.swift in Sources */,
|
||||
DD0BE3102CB9FDC4000BA445 /* DetectionSensorEnums.swift in Sources */,
|
||||
DDD9E4E4284B208E003777C5 /* UserEntityExtension.swift in Sources */,
|
||||
DD2553592855B52700E55709 /* PositionConfig.swift in Sources */,
|
||||
DD97E96828EFE9A00056DDA4 /* About.swift in Sources */,
|
||||
|
|
@ -1699,7 +1704,7 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.5.8;
|
||||
MARKETING_VERSION = 2.5.9;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
|
|
@ -1733,7 +1738,7 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.5.8;
|
||||
MARKETING_VERSION = 2.5.9;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
|
|
@ -1765,7 +1770,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.5.8;
|
||||
MARKETING_VERSION = 2.5.9;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
|
|
@ -1798,7 +1803,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.5.8;
|
||||
MARKETING_VERSION = 2.5.9;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
|
|
@ -1910,6 +1915,7 @@
|
|||
DD3CC6BA28E366DF00FA9159 /* Meshtastic.xcdatamodeld */ = {
|
||||
isa = XCVersionGroup;
|
||||
children = (
|
||||
DD0BE30C2CB785D8000BA445 /* MeshtasticDataModelV 46.xcdatamodel */,
|
||||
DD6D5A342CA13BA600ED3032 /* MeshtasticDataModelV 45.xcdatamodel */,
|
||||
DD7CF8DA2C93663C008BD10E /* MeshtasticDataModelV 44.xcdatamodel */,
|
||||
DD7E235F2C7AA3E50078ACDF /* MeshtasticDataModelV 43.xcdatamodel */,
|
||||
|
|
@ -1956,7 +1962,7 @@
|
|||
DD5D0A9A2931AD6B00F7EA61 /* MeshtasticDataModelV2.xcdatamodel */,
|
||||
DD3CC6BB28E366DF00FA9159 /* MeshtasticDataModel.xcdatamodel */,
|
||||
);
|
||||
currentVersion = DD6D5A342CA13BA600ED3032 /* MeshtasticDataModelV 45.xcdatamodel */;
|
||||
currentVersion = DD0BE30C2CB785D8000BA445 /* MeshtasticDataModelV 46.xcdatamodel */;
|
||||
name = Meshtastic.xcdatamodeld;
|
||||
path = Meshtastic/Meshtastic.xcdatamodeld;
|
||||
sourceTree = "<group>";
|
||||
|
|
|
|||
53
Meshtastic/Enums/DetectionSensorEnums.swift
Normal file
53
Meshtastic/Enums/DetectionSensorEnums.swift
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
//
|
||||
// DetectionSensorEnums.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Copyright(c) Garth Vander Houwen 10/11/24.
|
||||
//
|
||||
import MeshtasticProtobufs
|
||||
|
||||
enum TriggerTypes: Int, CaseIterable, Identifiable {
|
||||
|
||||
case logicLow = 0
|
||||
case logicHigh = 1
|
||||
case fallingEdge = 2
|
||||
case risingEdge = 3
|
||||
case eitherEdgeActiveLow = 4
|
||||
case eitherEdgeActiveHigh = 5
|
||||
|
||||
var id: Int { self.rawValue }
|
||||
|
||||
var name: String {
|
||||
switch self {
|
||||
case .logicLow:
|
||||
return "Low"
|
||||
case .logicHigh:
|
||||
return "High"
|
||||
case .fallingEdge:
|
||||
return "Falling Edge"
|
||||
case .risingEdge:
|
||||
return "Rising Edge"
|
||||
case .eitherEdgeActiveLow:
|
||||
return "Either Edge Low"
|
||||
case .eitherEdgeActiveHigh:
|
||||
return "Either Edge Hight"
|
||||
}
|
||||
}
|
||||
func protoEnumValue() -> ModuleConfig.DetectionSensorConfig.TriggerType {
|
||||
|
||||
switch self {
|
||||
case .logicLow:
|
||||
return ModuleConfig.DetectionSensorConfig.TriggerType.logicLow
|
||||
case .logicHigh:
|
||||
return ModuleConfig.DetectionSensorConfig.TriggerType.logicHigh
|
||||
case .fallingEdge:
|
||||
return ModuleConfig.DetectionSensorConfig.TriggerType.fallingEdge
|
||||
case .risingEdge:
|
||||
return ModuleConfig.DetectionSensorConfig.TriggerType.risingEdge
|
||||
case .eitherEdgeActiveLow:
|
||||
return ModuleConfig.DetectionSensorConfig.TriggerType.eitherEdgeActiveLow
|
||||
case .eitherEdgeActiveHigh:
|
||||
return ModuleConfig.DetectionSensorConfig.TriggerType.eitherEdgeActiveHigh
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -25,9 +25,12 @@ enum RegionCodes: Int, CaseIterable, Identifiable {
|
|||
case th = 12
|
||||
case ua433 = 14
|
||||
case ua868 = 15
|
||||
case my_433 = 16
|
||||
case my_919 = 17
|
||||
case sg_923 = 18
|
||||
case my433 = 16
|
||||
case my919 = 17
|
||||
case sg923 = 18
|
||||
case ph433 = 19
|
||||
case ph868 = 20
|
||||
case ph915 = 21
|
||||
case lora24 = 13
|
||||
var topic: String {
|
||||
switch self {
|
||||
|
|
@ -61,12 +64,18 @@ enum RegionCodes: Int, CaseIterable, Identifiable {
|
|||
"UA_433"
|
||||
case .ua868:
|
||||
"UA_868"
|
||||
case .my_433:
|
||||
case .my433:
|
||||
"MY_433"
|
||||
case .my_919:
|
||||
case .my919:
|
||||
"MY_919"
|
||||
case .sg_923:
|
||||
case .sg923:
|
||||
"SG_923"
|
||||
case .ph433:
|
||||
"ph_433"
|
||||
case .ph868:
|
||||
"ph_868"
|
||||
case .ph915:
|
||||
"ph_915"
|
||||
case .lora24:
|
||||
"LORA_24"
|
||||
} }
|
||||
|
|
@ -105,12 +114,18 @@ enum RegionCodes: Int, CaseIterable, Identifiable {
|
|||
return "Ukraine 868mhz"
|
||||
case .lora24:
|
||||
return "2.4 GHZ"
|
||||
case .my_433:
|
||||
case .my433:
|
||||
return "Malaysia 433mhz"
|
||||
case .my_919:
|
||||
case .my919:
|
||||
return "Malaysia 919mhz"
|
||||
case .sg_923:
|
||||
case .sg923:
|
||||
return "Singapore 923mhz"
|
||||
case .ph433:
|
||||
return "Philippines 433mhz"
|
||||
case .ph868:
|
||||
return "Philippines 868mhz"
|
||||
case .ph915:
|
||||
return "Philippines 915mhz"
|
||||
}
|
||||
}
|
||||
var dutyCycle: Int {
|
||||
|
|
@ -147,11 +162,17 @@ enum RegionCodes: Int, CaseIterable, Identifiable {
|
|||
return 10
|
||||
case .lora24:
|
||||
return 100
|
||||
case .my_433:
|
||||
case .my433:
|
||||
return 100
|
||||
case .my_919:
|
||||
case .my919:
|
||||
return 100
|
||||
case .sg_923:
|
||||
case .sg923:
|
||||
return 100
|
||||
case .ph433:
|
||||
return 100
|
||||
case .ph868:
|
||||
return 100
|
||||
case .ph915:
|
||||
return 100
|
||||
}
|
||||
}
|
||||
|
|
@ -190,12 +211,18 @@ enum RegionCodes: Int, CaseIterable, Identifiable {
|
|||
return Config.LoRaConfig.RegionCode.ua868
|
||||
case .lora24:
|
||||
return Config.LoRaConfig.RegionCode.lora24
|
||||
case .my_433:
|
||||
case .my433:
|
||||
return Config.LoRaConfig.RegionCode.my433
|
||||
case .my_919:
|
||||
case .my919:
|
||||
return Config.LoRaConfig.RegionCode.my919
|
||||
case .sg_923:
|
||||
case .sg923:
|
||||
return Config.LoRaConfig.RegionCode.sg923
|
||||
case .ph433:
|
||||
return Config.LoRaConfig.RegionCode.ph433
|
||||
case .ph868:
|
||||
return Config.LoRaConfig.RegionCode.ph868
|
||||
case .ph915:
|
||||
return Config.LoRaConfig.RegionCode.ph915
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,7 +40,8 @@ extension NodeInfoEntity {
|
|||
}
|
||||
|
||||
var hasTraceRoutes: Bool {
|
||||
return traceRoutes?.count ?? 0 > 0
|
||||
let routes = traceRoutes?.filter { ($0 as AnyObject).response }
|
||||
return routes?.count ?? 0 > 0
|
||||
}
|
||||
|
||||
var hasPax: Bool {
|
||||
|
|
|
|||
|
|
@ -840,24 +840,31 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
let logString = String.localizedStringWithFormat("mesh.log.traceroute.received.direct %@".localized, String(snr))
|
||||
MeshLogger.log("🪧 \(logString)")
|
||||
} else {
|
||||
guard let connectedNode = getNodeInfo(id: Int64(connectedPeripheral.num), context: context) else {
|
||||
return
|
||||
}
|
||||
var hopNodes: [TraceRouteHopEntity] = []
|
||||
let connectedHop = TraceRouteHopEntity(context: context)
|
||||
connectedHop.name = traceRoute?.node?.user?.longName ?? "unknown".localized
|
||||
connectedHop.time = Date()
|
||||
connectedHop.num = connectedPeripheral.num
|
||||
connectedHop.name = connectedNode.user?.longName ?? "???"
|
||||
connectedHop.snr = Float(routingMessage.snrBack.last ?? 0 / 4)
|
||||
if let mostRecent = traceRoute?.node?.positions?.lastObject as? PositionEntity, mostRecent.time! >= Calendar.current.date(byAdding: .hour, value: -24, to: Date())! {
|
||||
connectedHop.altitude = mostRecent.altitude
|
||||
connectedHop.latitudeI = mostRecent.latitudeI
|
||||
connectedHop.longitudeI = mostRecent.longitudeI
|
||||
traceRoute?.hasPositions = true
|
||||
}
|
||||
var routeString = "\(connectedNode.user?.longName ?? "???") --> "
|
||||
hopNodes.append(connectedHop)
|
||||
var routeString = "You --> "
|
||||
traceRoute?.hopsTowards = Int32(routingMessage.route.count)
|
||||
for (index, node) in routingMessage.route.enumerated() {
|
||||
var hopNode = getNodeInfo(id: Int64(node), context: context)
|
||||
if hopNode == nil && hopNode?.num ?? 0 > 0 && node != 4294967295 {
|
||||
hopNode = createNodeInfo(num: Int64(node), context: context)
|
||||
}
|
||||
let traceRouteHop = TraceRouteHopEntity(context: context)
|
||||
traceRouteHop.time = Date()
|
||||
if routingMessage.snrTowards.count >= index + 1 {
|
||||
traceRouteHop.snr = Float(routingMessage.snrTowards[index] / 4)
|
||||
}
|
||||
|
|
@ -874,11 +881,26 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
if decodedInfo.packet.rxTime > 0 {
|
||||
hopNode?.lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(decodedInfo.packet.rxTime)))
|
||||
}
|
||||
hopNodes.append(traceRouteHop)
|
||||
}
|
||||
hopNodes.append(traceRouteHop)
|
||||
routeString += "\(hopNode?.user?.longName ?? (node == 4294967295 ? "Repeater" : String(hopNode?.num.toHex() ?? "unknown".localized))) \(hopNode?.viaMqtt ?? false ? "MQTT" : "") (\(traceRouteHop.snr > 0 ? hopNode?.snr ?? 0.0 : 0.0)dB) --> "
|
||||
}
|
||||
var routeBackString = traceRoute?.node?.user?.longName ?? "unknown".localized
|
||||
let destinationHop = TraceRouteHopEntity(context: context)
|
||||
destinationHop.name = traceRoute?.node?.user?.longName ?? "unknown".localized
|
||||
destinationHop.time = Date()
|
||||
destinationHop.snr = Float(routingMessage.snrTowards.last ?? 0 / 4)
|
||||
destinationHop.num = traceRoute?.node?.num ?? 0
|
||||
if let mostRecent = traceRoute?.node?.positions?.lastObject as? PositionEntity, mostRecent.time! >= Calendar.current.date(byAdding: .hour, value: -24, to: Date())! {
|
||||
destinationHop.altitude = mostRecent.altitude
|
||||
destinationHop.latitudeI = mostRecent.latitudeI
|
||||
destinationHop.longitudeI = mostRecent.longitudeI
|
||||
traceRoute?.hasPositions = true
|
||||
}
|
||||
hopNodes.append(destinationHop)
|
||||
/// Add the destination node to the end of the route towards string and the beginning of teh route back string
|
||||
routeString += "\(traceRoute?.node?.user?.longName ?? "unknown".localized) \((traceRoute?.node?.num ?? 0).toHex()) \(traceRoute?.node?.snr ?? 0 > 0 ? traceRoute?.node?.snr ?? 0 : 0.0)dB)"
|
||||
var routeBackString = "\(traceRoute?.node?.user?.longName ?? "unknown".localized) \((traceRoute?.node?.num ?? 0).toHex()) \(traceRoute?.node?.snr ?? 0 > 0 ? traceRoute?.node?.snr ?? 0 : 0.0)dB) --> "
|
||||
traceRoute?.hopsBack = Int32(routingMessage.routeBack.count)
|
||||
for (index, node) in routingMessage.routeBack.enumerated() {
|
||||
var hopNode = getNodeInfo(id: Int64(node), context: context)
|
||||
if hopNode == nil && hopNode?.num ?? 0 > 0 && node != 4294967295 {
|
||||
|
|
@ -903,20 +925,22 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
if decodedInfo.packet.rxTime > 0 {
|
||||
hopNode?.lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(decodedInfo.packet.rxTime)))
|
||||
}
|
||||
hopNodes.append(traceRouteHop)
|
||||
}
|
||||
hopNodes.append(traceRouteHop)
|
||||
routeBackString += "\(hopNode?.user?.longName ?? (node == 4294967295 ? "Repeater" : String(hopNode?.num.toHex() ?? "unknown".localized))) \(hopNode?.viaMqtt ?? false ? "MQTT" : "") (\(traceRouteHop.snr > 0 ? hopNode?.snr ?? 0.0 : 0.0)dB) --> "
|
||||
}
|
||||
routeBackString += "\(connectedNode.user?.longName ?? String(connectedNode.num.toHex())) \(connectedNode.snr > 0 ? connectedNode.snr : 0.0)dB)"
|
||||
traceRoute?.routeText = routeString
|
||||
traceRoute?.routeBackText = routeBackString
|
||||
traceRoute?.hops = NSOrderedSet(array: hopNodes)
|
||||
traceRoute?.time = Date()
|
||||
do {
|
||||
try context.save()
|
||||
Logger.data.info("💾 Saved Trace Route")
|
||||
} catch {
|
||||
context.rollback()
|
||||
let nsError = error as NSError
|
||||
Logger.data.error("Error Updating Core Data TraceRouteHOp: \(nsError, privacy: .public)")
|
||||
Logger.data.error("Error Updating Core Data TraceRouteHop: \(nsError, privacy: .public)")
|
||||
}
|
||||
let logString = String.localizedStringWithFormat("mesh.log.traceroute.received.route %@".localized, routeString)
|
||||
MeshLogger.log("🪧 \(logString)")
|
||||
|
|
|
|||
|
|
@ -726,6 +726,9 @@ func telemetryPacket(packet: MeshPacket, connectedNode: Int64, context: NSManage
|
|||
telemetry.numPacketsTx = Int32(truncatingIfNeeded: telemetryMessage.localStats.numPacketsTx)
|
||||
telemetry.numPacketsRx = Int32(truncatingIfNeeded: telemetryMessage.localStats.numPacketsRx)
|
||||
telemetry.numPacketsRxBad = Int32(truncatingIfNeeded: telemetryMessage.localStats.numPacketsRxBad)
|
||||
telemetry.numRxDupe = Int32(truncatingIfNeeded: telemetryMessage.localStats.numRxDupe)
|
||||
telemetry.numTxRelay = Int32(truncatingIfNeeded: telemetryMessage.localStats.numTxRelay)
|
||||
telemetry.numTxRelayCanceled = Int32(truncatingIfNeeded: telemetryMessage.localStats.numTxRelayCanceled)
|
||||
telemetry.numOnlineNodes = Int32(truncatingIfNeeded: telemetryMessage.localStats.numOnlineNodes)
|
||||
telemetry.numTotalNodes = Int32(truncatingIfNeeded: telemetryMessage.localStats.numTotalNodes)
|
||||
telemetry.metricsType = 4
|
||||
|
|
@ -780,6 +783,9 @@ func telemetryPacket(packet: MeshPacket, connectedNode: Int64, context: NSManage
|
|||
sentPackets: UInt32(telemetry.numPacketsTx),
|
||||
receivedPackets: UInt32(telemetry.numPacketsRx),
|
||||
badReceivedPackets: UInt32(telemetry.numPacketsRxBad),
|
||||
dupeReceivedPackets: UInt32(telemetry.numRxDupe),
|
||||
packetsSentRelay: UInt32(telemetry.numTxRelay),
|
||||
packetsCanceledRelay: UInt32(telemetry.numTxRelayCanceled),
|
||||
nodesOnline: UInt32(telemetry.numOnlineNodes),
|
||||
totalNodes: UInt32(telemetry.numTotalNodes),
|
||||
timerRange: date)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,6 @@
|
|||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>_XCCurrentVersionName</key>
|
||||
<string>MeshtasticDataModelV 45.xcdatamodel</string>
|
||||
<string>MeshtasticDataModelV 46.xcdatamodel</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,484 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="23231" systemVersion="24B5055e" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
||||
<entity name="AmbientLightingConfigEntity" representedClassName="AmbientLightingConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="blue" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="current" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="green" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="ledState" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="red" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="ambientLightingConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="ambientLightingConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="BluetoothConfigEntity" representedClassName="BluetoothConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="deviceLoggingEnabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="fixedPin" optional="YES" attributeType="Integer 32" defaultValueString="123456" usesScalarValueType="YES"/>
|
||||
<attribute name="mode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="bluetoothConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="bluetoothConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="CannedMessageConfigEntity" representedClassName="CannedMessageConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerEventCcw" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerEventCw" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerEventPress" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerPinA" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerPinB" optional="YES" attributeType="Integer 32" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerPinPress" optional="YES" attributeType="Integer 32" usesScalarValueType="YES"/>
|
||||
<attribute name="messages" optional="YES" attributeType="String" minValueString="0" maxValueString="198"/>
|
||||
<attribute name="rotary1Enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="sendBell" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="updown1Enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<relationship name="cannedMessagesConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="cannedMessageConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="ChannelEntity" representedClassName="ChannelEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="downlinkEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="id" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="index" attributeType="Integer 32" minValueString="0" maxValueString="13" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="mute" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="name" optional="YES" attributeType="String"/>
|
||||
<attribute name="positionPrecision" optional="YES" attributeType="Integer 32" defaultValueString="32" usesScalarValueType="YES"/>
|
||||
<attribute name="psk" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="role" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="uplinkEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<relationship name="myInfoChannel" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MyInfoEntity" inverseName="channels" inverseEntity="MyInfoEntity"/>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="index"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="DetectionSensorConfigEntity" representedClassName="DetectionSensorConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="minimumBroadcastSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="monitorPin" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="name" optional="YES" attributeType="String"/>
|
||||
<attribute name="sendBell" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="stateBroadcastSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="triggerType" optional="YES" attributeType="Integer 32" usesScalarValueType="YES"/>
|
||||
<attribute name="usePullup" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<relationship name="detectionSensorConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="detectionSensorConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="DeviceConfigEntity" representedClassName="DeviceConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="buttonGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="buzzerGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="debugLogEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="disableTripleClick" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="doubleTapAsButtonPress" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="isManaged" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="ledHeartbeatEnabled" optional="YES" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<attribute name="nodeInfoBroadcastSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="rebroadcastMode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="role" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="serialEnabled" optional="YES" attributeType="Boolean" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="tripleClickAsAdHocPing" optional="YES" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<attribute name="tzdef" optional="YES" attributeType="String"/>
|
||||
<relationship name="deviceConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="deviceConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="DeviceMetadataEntity" representedClassName="DeviceMetadataEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="canShutdown" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="deviceStateVersion" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="firmwareVersion" optional="YES" attributeType="String"/>
|
||||
<attribute name="hasBluetooth" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="hasEthernet" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="hasWifi" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="hwModel" optional="YES" attributeType="String"/>
|
||||
<attribute name="positionFlags" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="role" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="time" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<relationship name="metadataNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="metadata" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="DisplayConfigEntity" representedClassName="DisplayConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="compassNorthTop" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="displayMode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="flipScreen" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="gpsFormat" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="headingBold" optional="YES" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<attribute name="oledType" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="screenCarouselInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="screenOnSeconds" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="units" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="wakeOnTapOrMotion" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<relationship name="displayConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="displayConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="ExternalNotificationConfigEntity" representedClassName="ExternalNotificationConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="active" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="alertBell" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="alertBellBuzzer" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="alertBellVibra" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="alertMessage" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="alertMessageBuzzer" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="alertMessageVibra" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="nagTimeout" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="output" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="outputBuzzer" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="outputMilliseconds" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="outputVibra" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="useI2SAsBuzzer" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="usePWM" optional="YES" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<relationship name="externalNotificationConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="externalNotificationConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="LocationEntity" representedClassName="LocationEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="altitude" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="heading" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="id" optional="YES" attributeType="Integer 32" usesScalarValueType="YES"/>
|
||||
<attribute name="latitudeI" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="longitudeI" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="speed" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="routeLocation" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="RouteEntity" inverseName="locations" inverseEntity="RouteEntity"/>
|
||||
</entity>
|
||||
<entity name="LoRaConfigEntity" representedClassName="LoRaConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="bandwidth" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="channelNum" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="codingRate" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="frequencyOffset" optional="YES" attributeType="Float" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="hopLimit" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="ignoreMqtt" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="modemPreset" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="okToMqtt" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="overrideDutyCycle" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="overrideFrequency" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="regionCode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="spreadFactor" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="sx126xRxBoostedGain" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="txEnabled" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<attribute name="txPower" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="usePreset" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<relationship name="loRaConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="loRaConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="MessageEntity" representedClassName="MessageEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="ackError" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="ackSNR" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="ackTimestamp" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="admin" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="adminDescription" optional="YES" attributeType="String"/>
|
||||
<attribute name="channel" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="isEmoji" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="messageId" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="messagePayload" optional="YES" attributeType="String" defaultValueString=""/>
|
||||
<attribute name="messagePayloadMarkdown" optional="YES" attributeType="String"/>
|
||||
<attribute name="messageTimestamp" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="pkiEncrypted" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="portNum" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="publicKey" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="read" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="realACK" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="receivedACK" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="replyID" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="rssi" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<relationship name="fromUser" optional="YES" maxCount="1" deletionRule="Nullify" ordered="YES" destinationEntity="UserEntity" inverseName="sentMessages" inverseEntity="UserEntity"/>
|
||||
<relationship name="toUser" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="UserEntity" inverseName="receivedMessages" inverseEntity="UserEntity"/>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="messageId"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="MQTTConfigEntity" representedClassName="MQTTConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="address" optional="YES" attributeType="String"/>
|
||||
<attribute name="enabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="encryptionEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="jsonEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="mapPositionPrecision" optional="YES" attributeType="Integer 32" defaultValueString="13" usesScalarValueType="YES"/>
|
||||
<attribute name="mapPublishIntervalSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="mapReportingEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="password" optional="YES" attributeType="String" maxValueString="30"/>
|
||||
<attribute name="proxyToClientEnabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="root" optional="YES" attributeType="String" defaultValueString="msh"/>
|
||||
<attribute name="tlsEnabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="username" optional="YES" attributeType="String" maxValueString="30"/>
|
||||
<relationship name="mqttConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="mqttConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="MyInfoEntity" representedClassName="MyInfoEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="adminIndex" optional="YES" attributeType="Integer 32" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="bleName" optional="YES" attributeType="String"/>
|
||||
<attribute name="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"/>
|
||||
<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="firstHeard" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="hopsAway" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="id" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="lastHeard" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="num" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="peripheralId" optional="YES" attributeType="String"/>
|
||||
<attribute name="rssi" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="sessionExpiration" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="sessionPasskey" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="viaMqtt" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<relationship name="ambientLightingConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="AmbientLightingConfigEntity" inverseName="ambientLightingConfigNode" inverseEntity="AmbientLightingConfigEntity"/>
|
||||
<relationship name="bluetoothConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="BluetoothConfigEntity" inverseName="bluetoothConfigNode" inverseEntity="BluetoothConfigEntity"/>
|
||||
<relationship name="cannedMessageConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="CannedMessageConfigEntity" inverseName="cannedMessagesConfigNode" inverseEntity="CannedMessageConfigEntity"/>
|
||||
<relationship name="detectionSensorConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="DetectionSensorConfigEntity" inverseName="detectionSensorConfigNode" inverseEntity="DetectionSensorConfigEntity"/>
|
||||
<relationship name="deviceConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="DeviceConfigEntity" inverseName="deviceConfigNode" inverseEntity="DeviceConfigEntity"/>
|
||||
<relationship name="displayConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="DisplayConfigEntity" inverseName="displayConfigNode" inverseEntity="DisplayConfigEntity"/>
|
||||
<relationship name="externalNotificationConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="ExternalNotificationConfigEntity" inverseName="externalNotificationConfigNode" inverseEntity="ExternalNotificationConfigEntity"/>
|
||||
<relationship name="loRaConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="LoRaConfigEntity" inverseName="loRaConfigNode" inverseEntity="LoRaConfigEntity"/>
|
||||
<relationship name="metadata" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="DeviceMetadataEntity" inverseName="metadataNode" inverseEntity="DeviceMetadataEntity"/>
|
||||
<relationship name="mqttConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MQTTConfigEntity" inverseName="mqttConfigNode" inverseEntity="MQTTConfigEntity"/>
|
||||
<relationship name="myInfo" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MyInfoEntity" inverseName="myInfoNode" inverseEntity="MyInfoEntity"/>
|
||||
<relationship name="networkConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NetworkConfigEntity" inverseName="networkConfigNode" inverseEntity="NetworkConfigEntity"/>
|
||||
<relationship name="pax" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="PaxCounterEntity" inverseName="paxNode" inverseEntity="PaxCounterEntity"/>
|
||||
<relationship name="paxCounterConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="PaxCounterConfigEntity" inverseName="paxCounterConfigNode" inverseEntity="PaxCounterConfigEntity"/>
|
||||
<relationship name="positionConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="PositionConfigEntity" inverseName="positionConfigNode" inverseEntity="PositionConfigEntity"/>
|
||||
<relationship name="positions" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="PositionEntity" inverseName="nodePosition" inverseEntity="PositionEntity"/>
|
||||
<relationship name="powerConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="PowerConfigEntity" inverseName="powerConfigNode" inverseEntity="PowerConfigEntity"/>
|
||||
<relationship name="rangeTestConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="RangeTestConfigEntity" inverseName="rangeTestConfigNode" inverseEntity="RangeTestConfigEntity"/>
|
||||
<relationship name="rtttlConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="RTTTLConfigEntity" inverseName="rtttlConfigNode" inverseEntity="RTTTLConfigEntity"/>
|
||||
<relationship name="securityConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="SecurityConfigEntity" inverseName="securityConfigNode" inverseEntity="SecurityConfigEntity"/>
|
||||
<relationship name="serialConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="SerialConfigEntity" inverseName="serialConfigNode" inverseEntity="SerialConfigEntity"/>
|
||||
<relationship name="storeForwardConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="StoreForwardConfigEntity" inverseName="storeForwardConfigNode" inverseEntity="StoreForwardConfigEntity"/>
|
||||
<relationship name="telemetries" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="TelemetryEntity" inverseName="nodeTelemetry" inverseEntity="TelemetryEntity"/>
|
||||
<relationship name="telemetryConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="TelemetryConfigEntity" inverseName="telemetryConfigNode" inverseEntity="TelemetryConfigEntity"/>
|
||||
<relationship name="traceRoutes" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="TraceRouteEntity" inverseName="node" inverseEntity="TraceRouteEntity"/>
|
||||
<relationship name="user" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="UserEntity" inverseName="userNode" inverseEntity="UserEntity"/>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="num"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="PaxCounterConfigEntity" representedClassName="PaxCounterConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="bleThreshold" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="updateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="wifiThreshold" optional="YES" attributeType="Integer 32" defaultValueString="-80" usesScalarValueType="YES"/>
|
||||
<relationship name="paxCounterConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="paxCounterConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="PaxCounterEntity" representedClassName="PaxCounterEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="ble" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="time" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="uptime" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="wifi" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="paxNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="pax" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="PositionConfigEntity" representedClassName="PositionConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="broadcastSmartMinimumDistance" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="broadcastSmartMinimumIntervalSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="deviceGpsEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="fixedPosition" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="gpsAttemptTime" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="gpsEnGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="gpsMode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="gpsUpdateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="positionBroadcastSeconds" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="positionFlags" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="rxGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="smartPositionEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="txGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="positionConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="positionConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="PositionEntity" representedClassName="PositionEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="altitude" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="heading" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="latest" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="latitudeI" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="longitudeI" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="precisionBits" optional="YES" attributeType="Integer 32" defaultValueString="32" usesScalarValueType="YES"/>
|
||||
<attribute name="rssi" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="satsInView" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="seqNo" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="speed" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="time" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<relationship name="nodePosition" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="positions" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="PowerConfigEntity" representedClassName="PowerConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="adcMultiplierOverride" optional="YES" attributeType="Float" usesScalarValueType="YES"/>
|
||||
<attribute name="deviceBatteryInaAddress" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="isPowerSaving" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="lsSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="minWakeSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="onBatteryShutdownAfterSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="waitBluetoothSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="powerConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="powerConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="RangeTestConfigEntity" representedClassName="RangeTestConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="save" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="sender" optional="YES" attributeType="Integer 32" usesScalarValueType="YES"/>
|
||||
<relationship name="rangeTestConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="rangeTestConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="RouteEntity" representedClassName="RouteEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="color" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="date" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="distance" optional="YES" attributeType="Double" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="elevationGain" optional="YES" attributeType="Double" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="endDate" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="id" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="name" optional="YES" attributeType="String"/>
|
||||
<attribute name="notes" optional="YES" attributeType="String"/>
|
||||
<relationship name="locations" optional="YES" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="LocationEntity" inverseName="routeLocation" inverseEntity="LocationEntity"/>
|
||||
</entity>
|
||||
<entity name="RTTTLConfigEntity" representedClassName="RTTTLConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="ringtone" optional="YES" attributeType="String" maxValueString="228" defaultValueString=""/>
|
||||
<relationship name="rtttlConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="rtttlConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="SecurityConfigEntity" representedClassName="SecurityConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="adminChannelEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="adminKey" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="adminKey2" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="adminKey3" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="bluetoothLoggingEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="debugLogApiEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="isManaged" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="privateKey" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="publicKey" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="serialEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<relationship name="securityConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="securityConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="SerialConfigEntity" representedClassName="SerialConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="baudRate" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="echo" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="mode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="overrideConsoleSerialPort" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="rxd" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="timeout" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="txd" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="serialConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="serialConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="StoreForwardConfigEntity" representedClassName="StoreForwardConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="enabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="heartbeat" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<attribute name="historyReturnMax" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="historyReturnWindow" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="isRouter" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="lastHeartbeat" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="lastRequest" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="records" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="storeForwardConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="storeForwardConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="TelemetryConfigEntity" representedClassName="TelemetryConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="deviceUpdateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="environmentDisplayFahrenheit" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="environmentMeasurementEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="environmentScreenEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="environmentUpdateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="powerMeasurementEnabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="powerScreenEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="powerUpdateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="telemetryConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="telemetryConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="TelemetryEntity" representedClassName="TelemetryEntity" syncable="YES" 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="numOnlineNodes" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="numPacketsRx" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="numPacketsRxBad" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="numPacketsTx" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="numRxDupe" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="numTotalNodes" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="numTxRelay" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="numTxRelayCanceled" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="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"/>
|
||||
<attribute name="weight" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="windDirection" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="windGust" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="windLull" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="windSpeed" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<relationship name="nodeTelemetry" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="telemetries" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="TraceRouteEntity" representedClassName="TraceRouteEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="hasPositions" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="hopsBack" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="hopsTowards" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="id" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="response" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="routeBackText" optional="YES" attributeType="String"/>
|
||||
<attribute name="routeText" optional="YES" attributeType="String"/>
|
||||
<attribute name="sent" optional="YES" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="time" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<relationship name="hops" optional="YES" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="TraceRouteHopEntity" inverseName="traceRoute" inverseEntity="TraceRouteHopEntity"/>
|
||||
<relationship name="node" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="traceRoutes" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="TraceRouteHopEntity" representedClassName="TraceRouteHopEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="altitude" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="back" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="latitudeI" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="longitudeI" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="name" optional="YES" attributeType="String"/>
|
||||
<attribute name="num" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="time" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<relationship name="traceRoute" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="TraceRouteEntity" inverseName="hops" inverseEntity="TraceRouteEntity"/>
|
||||
</entity>
|
||||
<entity name="UserEntity" representedClassName="UserEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="hwDisplayName" optional="YES" attributeType="String"/>
|
||||
<attribute name="hwModel" attributeType="String"/>
|
||||
<attribute name="hwModelId" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="isLicensed" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="keyMatch" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<attribute name="lastMessage" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="longName" attributeType="String"/>
|
||||
<attribute name="mute" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="newPublicKey" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="num" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="numString" optional="YES" attributeType="String"/>
|
||||
<attribute name="pkiEncrypted" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="publicKey" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="role" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="shortName" attributeType="String"/>
|
||||
<attribute name="userId" attributeType="String"/>
|
||||
<relationship name="receivedMessages" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="MessageEntity" inverseName="toUser" inverseEntity="MessageEntity"/>
|
||||
<relationship name="sentMessages" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="MessageEntity" inverseName="fromUser" inverseEntity="MessageEntity"/>
|
||||
<relationship name="userNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="user" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="WaypointEntity" representedClassName="WaypointEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="created" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="expire" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="icon" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="id" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="lastUpdated" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="latitudeI" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="locked" attributeType="Integer 64" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="longDescription" optional="YES" attributeType="String" maxValueString="100"/>
|
||||
<attribute name="longitudeI" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="name" attributeType="String" minValueString="1" maxValueString="30"/>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="id"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
</model>
|
||||
|
|
@ -989,7 +989,7 @@ func upsertDetectionSensorModuleConfigPacket(config: ModuleConfig.DetectionSenso
|
|||
newConfig.sendBell = config.sendBell
|
||||
newConfig.name = config.name
|
||||
newConfig.monitorPin = Int32(config.monitorPin)
|
||||
newConfig.detectionTriggeredHigh = config.detectionTriggeredHigh
|
||||
newConfig.triggerType = Int32(config.detectionTriggerType.rawValue)
|
||||
newConfig.usePullup = config.usePullup
|
||||
newConfig.minimumBroadcastSecs = Int32(truncatingIfNeeded: config.minimumBroadcastSecs)
|
||||
newConfig.stateBroadcastSecs = Int32(truncatingIfNeeded: config.stateBroadcastSecs)
|
||||
|
|
@ -1000,7 +1000,7 @@ func upsertDetectionSensorModuleConfigPacket(config: ModuleConfig.DetectionSenso
|
|||
fetchedNode[0].detectionSensorConfig?.name = config.name
|
||||
fetchedNode[0].detectionSensorConfig?.monitorPin = Int32(config.monitorPin)
|
||||
fetchedNode[0].detectionSensorConfig?.usePullup = config.usePullup
|
||||
fetchedNode[0].detectionSensorConfig?.detectionTriggeredHigh = config.detectionTriggeredHigh
|
||||
fetchedNode[0].detectionSensorConfig?.triggerType = Int32(config.detectionTriggerType.rawValue)
|
||||
fetchedNode[0].detectionSensorConfig?.minimumBroadcastSecs = Int32(truncatingIfNeeded: config.minimumBroadcastSecs)
|
||||
fetchedNode[0].detectionSensorConfig?.stateBroadcastSecs = Int32(truncatingIfNeeded: config.stateBroadcastSecs)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -127,6 +127,14 @@
|
|||
"activelySupported": true,
|
||||
"displayName": "LILYGO T-LoRa T3-S3"
|
||||
},
|
||||
{
|
||||
"hwModel": 16,
|
||||
"hwModelSlug": "TLORA_T3_S3",
|
||||
"platformioTarget": "tlora-t3s3-epaper",
|
||||
"architecture": "esp32-s3",
|
||||
"activelySupported": true,
|
||||
"displayName": "LILYGO T-LoRa T3-S3 E-Paper"
|
||||
},
|
||||
{
|
||||
"hwModel": 17,
|
||||
"hwModelSlug": "NANO_G1_EXPLORER",
|
||||
|
|
@ -414,5 +422,13 @@
|
|||
"architecture": "nrf52840",
|
||||
"activelySupported": true,
|
||||
"displayName": "Seeed Card Tracker T1000-E"
|
||||
},
|
||||
{
|
||||
"hwModel": 72,
|
||||
"hwModelSlug": "Seeed_XIAO_S3",
|
||||
"platformioTarget": "seeed-xiao-s3",
|
||||
"architecture": "esp32-s3",
|
||||
"activelySupported": true,
|
||||
"displayName": "Seeed XIAO S3"
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -341,6 +341,9 @@ struct Connect: View {
|
|||
sentPackets: UInt32(mostRecent?.numPacketsTx ?? 0),
|
||||
receivedPackets: UInt32(mostRecent?.numPacketsRx ?? 0),
|
||||
badReceivedPackets: UInt32(mostRecent?.numPacketsRxBad ?? 0),
|
||||
dupeReceivedPackets: UInt32(mostRecent?.numRxDupe ?? 0),
|
||||
packetsSentRelay: UInt32(mostRecent?.numTxRelay ?? 0),
|
||||
packetsCanceledRelay: UInt32(mostRecent?.numTxRelayCanceled ?? 0),
|
||||
nodesOnline: UInt32(mostRecent?.numOnlineNodes ?? 0),
|
||||
totalNodes: UInt32(mostRecent?.numTotalNodes ?? 0),
|
||||
timerRange: Date.now...future)
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ struct SecureInput: View {
|
|||
private var idiom: UIUserInterfaceIdiom { UIDevice.current.userInterfaceIdiom }
|
||||
@Binding private var text: String
|
||||
@Binding private var isValid: Bool
|
||||
@State private var isSecure: Bool = true
|
||||
@State var isSecure: Bool = true
|
||||
private var title: String
|
||||
|
||||
init(_ title: String, text: Binding<String>, isValid: Binding<Bool>) {
|
||||
|
|
|
|||
|
|
@ -56,13 +56,13 @@ struct TraceRoute: Layout {
|
|||
|
||||
subview.place(at: point, anchor: .center, proposal: .unspecified)
|
||||
|
||||
DispatchQueue.main.async {
|
||||
// DispatchQueue.main.async {
|
||||
if index % 2 == 0 {
|
||||
subview[Rotation.self]?.wrappedValue = .zero
|
||||
} else {
|
||||
subview[Rotation.self]?.wrappedValue = .radians(angle)
|
||||
}
|
||||
}
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,64 +1,64 @@
|
|||
//
|
||||
// MapButtons.swift
|
||||
// Meshtastic
|
||||
//// MapButtons.swift
|
||||
//// Meshtastic
|
||||
////
|
||||
//// Copyright © Garth Vander Houwen 4/23/23.
|
||||
////
|
||||
//
|
||||
// Copyright © Garth Vander Houwen 4/23/23.
|
||||
//import SwiftUI
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct MapButtons: View {
|
||||
let buttonWidth: CGFloat = 22
|
||||
let width: CGFloat = 45
|
||||
@Binding var tracking: UserTrackingModes
|
||||
@Binding var isPresentingInfoSheet: Bool
|
||||
var body: some View {
|
||||
VStack {
|
||||
let impactLight = UIImpactFeedbackGenerator(style: .light)
|
||||
Button(action: {
|
||||
self.isPresentingInfoSheet.toggle()
|
||||
}) {
|
||||
Image(systemName: isPresentingInfoSheet ? "info.circle.fill" : "info.circle")
|
||||
.resizable()
|
||||
.frame(width: buttonWidth, height: buttonWidth, alignment: .center)
|
||||
.offset(y: -2)
|
||||
}
|
||||
Divider()
|
||||
Button(action: {
|
||||
switch self.tracking {
|
||||
case .none:
|
||||
self.tracking = .follow
|
||||
case .follow:
|
||||
self.tracking = .followWithHeading
|
||||
case .followWithHeading:
|
||||
self.tracking = .none
|
||||
}
|
||||
impactLight.impactOccurred()
|
||||
}) {
|
||||
Image(systemName: tracking.icon)
|
||||
.frame(width: buttonWidth, height: buttonWidth, alignment: .center)
|
||||
.offset(y: 3)
|
||||
}
|
||||
}
|
||||
.frame(width: width, height: width*2, alignment: .center)
|
||||
.background(Color(UIColor.systemBackground))
|
||||
.cornerRadius(8)
|
||||
.shadow(radius: 1)
|
||||
.offset(x: 3, y: 25)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Previews
|
||||
// struct MapControl_Previews: PreviewProvider {
|
||||
// @State static var tracking: UserTrackingModes = .none
|
||||
// @State static var isPresentingInfoSheet = false
|
||||
// static var previews: some View {
|
||||
// Group {
|
||||
// MapButtons(tracking: $tracking, isPresentingInfoSheet: $isPresentingInfoSheet)
|
||||
// .environment(\.colorScheme, .light)
|
||||
// MapButtons(tracking: $tracking, isPresentingInfoSheet: $isPresentingInfoSheet)
|
||||
// .environment(\.colorScheme, .dark)
|
||||
//struct MapButtons: View {
|
||||
// let buttonWidth: CGFloat = 22
|
||||
// let width: CGFloat = 45
|
||||
// @Binding var tracking: UserTrackingModes
|
||||
// @Binding var isPresentingInfoSheet: Bool
|
||||
// var body: some View {
|
||||
// VStack {
|
||||
// let impactLight = UIImpactFeedbackGenerator(style: .light)
|
||||
// Button(action: {
|
||||
// self.isPresentingInfoSheet.toggle()
|
||||
// }) {
|
||||
// Image(systemName: isPresentingInfoSheet ? "info.circle.fill" : "info.circle")
|
||||
// .resizable()
|
||||
// .frame(width: buttonWidth, height: buttonWidth, alignment: .center)
|
||||
// .offset(y: -2)
|
||||
// }
|
||||
// Divider()
|
||||
// Button(action: {
|
||||
// switch self.tracking {
|
||||
// case .none:
|
||||
// self.tracking = .follow
|
||||
// case .follow:
|
||||
// self.tracking = .followWithHeading
|
||||
// case .followWithHeading:
|
||||
// self.tracking = .none
|
||||
// }
|
||||
// impactLight.impactOccurred()
|
||||
// }) {
|
||||
// Image(systemName: tracking.icon)
|
||||
// .frame(width: buttonWidth, height: buttonWidth, alignment: .center)
|
||||
// .offset(y: 3)
|
||||
// }
|
||||
// }
|
||||
// .previewLayout(.fixed(width: 60, height: 100))
|
||||
// .frame(width: width, height: width*2, alignment: .center)
|
||||
// .background(Color(UIColor.systemBackground))
|
||||
// .cornerRadius(8)
|
||||
// .shadow(radius: 1)
|
||||
// .offset(x: 3, y: 25)
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//// MARK: Previews
|
||||
//// struct MapControl_Previews: PreviewProvider {
|
||||
//// @State static var tracking: UserTrackingModes = .none
|
||||
//// @State static var isPresentingInfoSheet = false
|
||||
//// static var previews: some View {
|
||||
//// Group {
|
||||
//// MapButtons(tracking: $tracking, isPresentingInfoSheet: $isPresentingInfoSheet)
|
||||
//// .environment(\.colorScheme, .light)
|
||||
//// MapButtons(tracking: $tracking, isPresentingInfoSheet: $isPresentingInfoSheet)
|
||||
//// .environment(\.colorScheme, .dark)
|
||||
//// }
|
||||
//// .previewLayout(.fixed(width: 60, height: 100))
|
||||
//// }
|
||||
//// }
|
||||
|
|
|
|||
|
|
@ -1,434 +1,434 @@
|
|||
////
|
||||
//// MapViewSwitUI.swift
|
||||
//// Meshtastic
|
||||
////
|
||||
//// Copyright(c) Josh Pirihi & Garth Vander Houwen 1/16/22.
|
||||
//
|
||||
// MapViewSwitUI.swift
|
||||
// Meshtastic
|
||||
//import Foundation
|
||||
//import SwiftUI
|
||||
//import MapKit
|
||||
//import OSLog
|
||||
//
|
||||
// Copyright(c) Josh Pirihi & Garth Vander Houwen 1/16/22.
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
import MapKit
|
||||
import OSLog
|
||||
|
||||
struct PolygonInfo: Codable {
|
||||
let stroke: String?
|
||||
let strokeWidth, strokeOpacity: Int?
|
||||
let fill: String?
|
||||
let fillOpacity: Double?
|
||||
let title, subtitle: String?
|
||||
}
|
||||
|
||||
func degreesToRadians(_ number: Double) -> Double {
|
||||
return number * .pi / 180
|
||||
}
|
||||
var currentMapLayer: MapLayer?
|
||||
|
||||
struct MapViewSwiftUI: UIViewRepresentable {
|
||||
var onLongPress: (_ waypointCoordinate: CLLocationCoordinate2D) -> Void
|
||||
var onWaypointEdit: (_ waypointId: Int ) -> Void
|
||||
let mapView = MKMapView()
|
||||
// Parameters
|
||||
let selectedMapLayer: MapLayer
|
||||
let selectedWeatherLayer: MapOverlayServer = UserDefaults.mapOverlayServer
|
||||
let positions: [PositionEntity]
|
||||
let waypoints: [WaypointEntity]
|
||||
let userTrackingMode: MKUserTrackingMode
|
||||
let showNodeHistory: Bool
|
||||
let showRouteLines: Bool
|
||||
let mapViewType: MKMapType = MKMapType.standard
|
||||
// Offline Map Tiles
|
||||
@AppStorage("lastUpdatedLocalMapFile") private var lastUpdatedLocalMapFile = 0
|
||||
@State private var loadedLastUpdatedLocalMapFile = 0
|
||||
var customMapOverlay: CustomMapOverlay?
|
||||
@State private var presentCustomMapOverlayHash: CustomMapOverlay?
|
||||
// MARK: Private methods
|
||||
private func configureMap(mapView: MKMapView) {
|
||||
// Map View Parameters
|
||||
mapView.mapType = mapViewType
|
||||
mapView.addAnnotations(waypoints)
|
||||
// Do the initial map centering
|
||||
let latest = positions
|
||||
.filter { $0.latest == true }
|
||||
.sorted { $0.nodePosition?.num ?? 0 > $1.nodePosition?.num ?? -1 }
|
||||
let span = MKCoordinateSpan(latitudeDelta: 0.003, longitudeDelta: 0.003)
|
||||
let center = (latest.count > 0 && userTrackingMode == MKUserTrackingMode.none) ? latest[0].coordinate : LocationHelper.currentLocation
|
||||
let region = MKCoordinateRegion(center: center, span: span)
|
||||
mapView.addAnnotations(showNodeHistory ? positions : latest)
|
||||
mapView.setRegion(region, animated: true)
|
||||
// Set user (phone gps) tracking options
|
||||
mapView.setUserTrackingMode(userTrackingMode, animated: true)
|
||||
if userTrackingMode == MKUserTrackingMode.none {
|
||||
if latest.count == 1 {
|
||||
mapView.fit(annotations: showNodeHistory ? positions: latest, andShow: false)
|
||||
} else {
|
||||
mapView.fitAllAnnotations()
|
||||
}
|
||||
mapView.showsUserLocation = false
|
||||
} else {
|
||||
mapView.showsUserLocation = true
|
||||
}
|
||||
// Other MKMapView Settings
|
||||
mapView.preferredConfiguration.elevationStyle = .realistic// .flat
|
||||
mapView.pointOfInterestFilter = MKPointOfInterestFilter.excludingAll
|
||||
mapView.isPitchEnabled = true
|
||||
mapView.isRotateEnabled = true
|
||||
mapView.isScrollEnabled = true
|
||||
mapView.isZoomEnabled = true
|
||||
mapView.showsBuildings = true
|
||||
mapView.showsScale = true
|
||||
mapView.showsTraffic = true
|
||||
|
||||
mapView.showsCompass = false
|
||||
let compass = MKCompassButton(mapView: mapView)
|
||||
compass.translatesAutoresizingMaskIntoConstraints = false
|
||||
#if targetEnvironment(macCatalyst)
|
||||
// Show the default always visible compass and the mac only controls
|
||||
compass.compassVisibility = .visible
|
||||
mapView.addSubview(compass)
|
||||
mapView.showsZoomControls = true
|
||||
mapView.showsPitchControl = true
|
||||
compass.trailingAnchor.constraint(equalTo: mapView.trailingAnchor, constant: -115).isActive = true
|
||||
compass.bottomAnchor.constraint(equalTo: mapView.bottomAnchor, constant: -5).isActive = true
|
||||
#else
|
||||
compass.compassVisibility = .adaptive
|
||||
mapView.addSubview(compass)
|
||||
compass.trailingAnchor.constraint(equalTo: mapView.trailingAnchor, constant: -5).isActive = true
|
||||
compass.topAnchor.constraint(equalTo: mapView.topAnchor, constant: 145).isActive = true
|
||||
#endif
|
||||
}
|
||||
private func setMapBaseLayer(mapView: MKMapView) {
|
||||
// Avoid refreshing UI if selectedLayer has not changed
|
||||
guard currentMapLayer != selectedMapLayer else { return }
|
||||
currentMapLayer = selectedMapLayer
|
||||
for overlay in mapView.overlays where overlay is MKTileOverlay {
|
||||
mapView.removeOverlay(overlay)
|
||||
}
|
||||
switch selectedMapLayer {
|
||||
case .offline:
|
||||
mapView.mapType = .standard
|
||||
let overlay = TileOverlay()
|
||||
overlay.canReplaceMapContent = false
|
||||
overlay.minimumZ = UserDefaults.mapTileServer.zoomRange.startIndex
|
||||
overlay.maximumZ = UserDefaults.mapTileServer.zoomRange.endIndex
|
||||
mapView.addOverlay(overlay, level: UserDefaults.mapTilesAboveLabels ? .aboveLabels : .aboveRoads)
|
||||
case .satellite:
|
||||
mapView.mapType = .satellite
|
||||
case .hybrid:
|
||||
mapView.mapType = .hybrid
|
||||
default:
|
||||
mapView.mapType = .standard
|
||||
}
|
||||
}
|
||||
private func setMapOverlays(mapView: MKMapView) {
|
||||
// Weather radar
|
||||
if UserDefaults.enableOverlayServer {
|
||||
let locale = Locale.current
|
||||
if locale.region?.identifier ?? "no locale" == "US" {
|
||||
let overlay = MKTileOverlay(urlTemplate: selectedWeatherLayer.tileUrl)
|
||||
overlay.canReplaceMapContent = false
|
||||
overlay.minimumZ = selectedWeatherLayer.zoomRange.startIndex
|
||||
overlay.maximumZ = selectedWeatherLayer.zoomRange.endIndex
|
||||
mapView.addOverlay(overlay, level: .aboveLabels)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func makeUIView(context: Context) -> MKMapView {
|
||||
currentMapLayer = nil
|
||||
mapView.delegate = context.coordinator
|
||||
self.configureMap(mapView: mapView)
|
||||
return mapView
|
||||
}
|
||||
func updateUIView(_ mapView: MKMapView, context: Context) {
|
||||
// Set selected map base layer
|
||||
setMapBaseLayer(mapView: mapView)
|
||||
// Set map tile server and weather overlay layers
|
||||
setMapOverlays(mapView: mapView)
|
||||
let latest = positions
|
||||
.filter { $0.latest == true }
|
||||
.sorted { $0.nodePosition?.num ?? 0 > $1.nodePosition?.num ?? -1 }
|
||||
// Node Route Lines
|
||||
if showRouteLines {
|
||||
// Remove all existing PolyLine Overlays
|
||||
for overlay in mapView.overlays where overlay is MKPolyline {
|
||||
mapView.removeOverlay(overlay)
|
||||
}
|
||||
var lineIndex = 0
|
||||
for position in latest {
|
||||
let nodePositions = positions.filter { $0.nodeCoordinate != nil && $0.nodePosition?.num ?? 0 == position.nodePosition?.num ?? -1 }
|
||||
let lineCoords = nodePositions.compactMap({(position) -> CLLocationCoordinate2D in
|
||||
return position.nodeCoordinate ?? LocationHelper.DefaultLocation
|
||||
})
|
||||
let polyline = MKPolyline(coordinates: lineCoords, count: nodePositions.count)
|
||||
polyline.title = "\(String(position.nodePosition?.num ?? 0))"
|
||||
mapView.addOverlay(polyline, level: .aboveLabels)
|
||||
lineIndex += 1
|
||||
// There are 18 colors for lines, start over if we are at index 17
|
||||
if lineIndex > 17 {
|
||||
lineIndex = 0
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Remove all existing PolyLine Overlays
|
||||
for overlay in mapView.overlays where overlay is MKPolyline {
|
||||
mapView.removeOverlay(overlay)
|
||||
}
|
||||
}
|
||||
let annotationCount = waypoints.count + (showNodeHistory ? positions.count : latest.count)
|
||||
if annotationCount != mapView.annotations.count {
|
||||
Logger.services.info("Annotation Count: \(annotationCount) Map Annotations: \(mapView.annotations.count)")
|
||||
mapView.removeAnnotations(mapView.annotations)
|
||||
mapView.addAnnotations(waypoints)
|
||||
}
|
||||
mapView.addAnnotations(showNodeHistory ? positions : latest)
|
||||
if userTrackingMode == MKUserTrackingMode.none {
|
||||
mapView.showsUserLocation = false
|
||||
if UserDefaults.enableMapRecentering {
|
||||
if latest.count == 1 {
|
||||
mapView.fit(annotations: showNodeHistory ? positions : latest, andShow: true)
|
||||
} else {
|
||||
mapView.fitAllAnnotations()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
mapView.showsUserLocation = true
|
||||
}
|
||||
mapView.setUserTrackingMode(userTrackingMode, animated: true)
|
||||
}
|
||||
func makeCoordinator() -> MapCoordinator {
|
||||
return Coordinator(self)
|
||||
}
|
||||
final class MapCoordinator: NSObject, MKMapViewDelegate, UIGestureRecognizerDelegate {
|
||||
var parent: MapViewSwiftUI
|
||||
var longPressRecognizer = UILongPressGestureRecognizer()
|
||||
init(_ parent: MapViewSwiftUI) {
|
||||
self.parent = parent
|
||||
super.init()
|
||||
self.longPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(longPressHandler))
|
||||
self.longPressRecognizer.minimumPressDuration = 0.5
|
||||
self.longPressRecognizer.cancelsTouchesInView = true
|
||||
self.longPressRecognizer.delegate = self
|
||||
self.parent.mapView.addGestureRecognizer(longPressRecognizer)
|
||||
}
|
||||
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
|
||||
switch annotation {
|
||||
case let positionAnnotation as PositionEntity:
|
||||
let reuseID = String(positionAnnotation.nodePosition?.num ?? 0) + "-" + String(positionAnnotation.time?.timeIntervalSince1970 ?? 0)
|
||||
let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "node") as? MKMarkerAnnotationView ?? MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: reuseID )
|
||||
annotationView.tag = -1
|
||||
annotationView.canShowCallout = true
|
||||
if positionAnnotation.latest {
|
||||
annotationView.markerTintColor = UIColor(hex: UInt32(positionAnnotation.nodePosition?.num ?? 0)).darker()
|
||||
annotationView.displayPriority = .required
|
||||
annotationView.titleVisibility = .visible
|
||||
} else {
|
||||
annotationView.markerTintColor = UIColor(hex: UInt32(positionAnnotation.nodePosition?.num ?? 0)).lighter()
|
||||
annotationView.displayPriority = .defaultHigh
|
||||
annotationView.titleVisibility = .adaptive
|
||||
}
|
||||
annotationView.tag = -1
|
||||
annotationView.canShowCallout = true
|
||||
annotationView.titleVisibility = .adaptive
|
||||
let leftIcon = UIImageView(image: annotationView.glyphText?.image())
|
||||
leftIcon.backgroundColor = UIColor(.indigo)
|
||||
annotationView.leftCalloutAccessoryView = leftIcon
|
||||
let subtitle = UILabel()
|
||||
subtitle.text = "Long Name: \(positionAnnotation.nodePosition?.user?.longName ?? "Unknown") \n"
|
||||
subtitle.text? += "Latitude: \(String(format: "%.5f", positionAnnotation.coordinate.latitude)) \n"
|
||||
subtitle.text! += "Longitude: \(String(format: "%.5f", positionAnnotation.coordinate.longitude)) \n"
|
||||
let distanceFormatter = MKDistanceFormatter()
|
||||
subtitle.text! += "Altitude: \(distanceFormatter.string(fromDistance: Double(positionAnnotation.altitude))) \n"
|
||||
if positionAnnotation.nodePosition?.metadata != nil {
|
||||
if DeviceRoles(rawValue: Int(positionAnnotation.nodePosition!.metadata?.role ?? 0)) == DeviceRoles.client ||
|
||||
DeviceRoles(rawValue: Int(positionAnnotation.nodePosition!.metadata?.role ?? 0)) == DeviceRoles.clientMute ||
|
||||
DeviceRoles(rawValue: Int(positionAnnotation.nodePosition!.metadata?.role ?? 0)) == DeviceRoles.routerClient {
|
||||
annotationView.glyphImage = UIImage(systemName: "flipphone")
|
||||
} else if DeviceRoles(rawValue: Int(positionAnnotation.nodePosition!.metadata?.role ?? 0)) == DeviceRoles.repeater {
|
||||
annotationView.glyphImage = UIImage(systemName: "repeat")
|
||||
} else if DeviceRoles(rawValue: Int(positionAnnotation.nodePosition!.metadata?.role ?? 0)) == DeviceRoles.router {
|
||||
annotationView.glyphImage = UIImage(systemName: "wifi.router.fill")
|
||||
} else if DeviceRoles(rawValue: Int(positionAnnotation.nodePosition!.metadata?.role ?? 0)) == DeviceRoles.tracker {
|
||||
annotationView.glyphImage = UIImage(systemName: "location.viewfinder")
|
||||
} else if DeviceRoles(rawValue: Int(positionAnnotation.nodePosition!.metadata?.role ?? 0)) == DeviceRoles.sensor {
|
||||
annotationView.glyphImage = UIImage(systemName: "sensor")
|
||||
}
|
||||
let pf = PositionFlags(rawValue: Int(positionAnnotation.nodePosition?.metadata?.positionFlags ?? 3))
|
||||
if pf.contains(.Satsinview) {
|
||||
subtitle.text! += "Sats in view: \(String(positionAnnotation.satsInView)) \n"
|
||||
}
|
||||
if pf.contains(.SeqNo) {
|
||||
subtitle.text! += "Sequence: \(String(positionAnnotation.seqNo)) \n"
|
||||
}
|
||||
if pf.contains(.Heading) {
|
||||
if parent.userTrackingMode != MKUserTrackingMode.followWithHeading {
|
||||
annotationView.glyphImage = UIImage(systemName: "location.north.fill")?.rotate(radians: Float(degreesToRadians(Double(positionAnnotation.heading))))
|
||||
subtitle.text! += "Heading: \(String(positionAnnotation.heading)) \n"
|
||||
} else {
|
||||
annotationView.glyphImage = UIImage(systemName: "flipphone")
|
||||
}
|
||||
}
|
||||
if pf.contains(.Speed) {
|
||||
let formatter = MeasurementFormatter()
|
||||
formatter.locale = Locale.current
|
||||
if positionAnnotation.speed <= 1 {
|
||||
annotationView.glyphImage = UIImage(systemName: "hexagon")
|
||||
}
|
||||
subtitle.text! += "Speed: \(formatter.string(from: Measurement(value: Double(positionAnnotation.speed), unit: UnitSpeed.kilometersPerHour))) \n"
|
||||
}
|
||||
} else {
|
||||
// node metadata is nil
|
||||
annotationView.glyphImage = UIImage(systemName: "flipphone")
|
||||
}
|
||||
if LocationHelper.currentLocation.distance(from: LocationHelper.DefaultLocation) > 0.0 {
|
||||
let metersAway = positionAnnotation.coordinate.distance(from: LocationHelper.currentLocation)
|
||||
subtitle.text! += "distance".localized + ": \(distanceFormatter.string(fromDistance: Double(metersAway))) \n"
|
||||
}
|
||||
subtitle.text! += positionAnnotation.time?.formatted() ?? "Unknown \n"
|
||||
subtitle.numberOfLines = 0
|
||||
annotationView.detailCalloutAccessoryView = subtitle
|
||||
let detailsIcon = UIButton(type: .detailDisclosure)
|
||||
detailsIcon.setImage(UIImage(systemName: "trash"), for: .normal)
|
||||
annotationView.rightCalloutAccessoryView = detailsIcon
|
||||
return annotationView
|
||||
case let waypointAnnotation as WaypointEntity:
|
||||
let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "waypoint") as? MKMarkerAnnotationView ?? MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: String(waypointAnnotation.id))
|
||||
annotationView.tag = Int(waypointAnnotation.id)
|
||||
annotationView.isEnabled = true
|
||||
annotationView.canShowCallout = true
|
||||
if waypointAnnotation.icon == 0 {
|
||||
annotationView.glyphText = "📍"
|
||||
} else {
|
||||
annotationView.glyphText = String(UnicodeScalar(Int(waypointAnnotation.icon)) ?? "📍")
|
||||
}
|
||||
annotationView.markerTintColor = UIColor(.accentColor)
|
||||
annotationView.displayPriority = .required
|
||||
annotationView.titleVisibility = .adaptive
|
||||
let leftIcon = UIImageView(image: annotationView.glyphText?.image())
|
||||
leftIcon.backgroundColor = UIColor(.accentColor)
|
||||
annotationView.leftCalloutAccessoryView = leftIcon
|
||||
let subtitle = UILabel()
|
||||
if waypointAnnotation.longDescription?.count ?? 0 > 0 {
|
||||
subtitle.text = (waypointAnnotation.longDescription ?? "") + "\n"
|
||||
} else {
|
||||
subtitle.text = ""
|
||||
}
|
||||
if LocationHelper.currentLocation.distance(from: LocationHelper.DefaultLocation) > 0.0 {
|
||||
let metersAway = waypointAnnotation.coordinate.distance(from: LocationHelper.currentLocation)
|
||||
let distanceFormatter = MKDistanceFormatter()
|
||||
subtitle.text! += "distance".localized + ": \(distanceFormatter.string(fromDistance: Double(metersAway))) \n"
|
||||
}
|
||||
if waypointAnnotation.created != nil {
|
||||
subtitle.text! += "Created: \(waypointAnnotation.created?.formatted() ?? "Unknown") \n"
|
||||
}
|
||||
if waypointAnnotation.lastUpdated != nil {
|
||||
subtitle.text! += "Updated: \(waypointAnnotation.lastUpdated?.formatted() ?? "Unknown") \n"
|
||||
}
|
||||
if waypointAnnotation.expire != nil {
|
||||
subtitle.text! += "Expires: \(waypointAnnotation.expire?.formatted() ?? "Unknown") \n"
|
||||
}
|
||||
subtitle.numberOfLines = 0
|
||||
annotationView.detailCalloutAccessoryView = subtitle
|
||||
let editIcon = UIButton(type: .detailDisclosure)
|
||||
editIcon.setImage(UIImage(systemName: "square.and.pencil"), for: .normal)
|
||||
annotationView.rightCalloutAccessoryView = editIcon
|
||||
return annotationView
|
||||
default: return nil
|
||||
}
|
||||
}
|
||||
func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
|
||||
switch view.annotation {
|
||||
case _ as WaypointEntity:
|
||||
// Only Allow Edit for waypoint annotations with a id
|
||||
if view.tag > 0 {
|
||||
parent.onWaypointEdit(view.tag)
|
||||
}
|
||||
default: break
|
||||
}
|
||||
}
|
||||
@objc func longPressHandler(_ gesture: UILongPressGestureRecognizer) {
|
||||
if gesture.state != UIGestureRecognizer.State.ended {
|
||||
return
|
||||
} else if gesture.state != UIGestureRecognizer.State.began {
|
||||
// Screen Position - CGPoint
|
||||
let location = longPressRecognizer.location(in: self.parent.mapView)
|
||||
// Map Coordinate - CLLocationCoordinate2D
|
||||
let coordinate = self.parent.mapView.convert(location, toCoordinateFrom: self.parent.mapView)
|
||||
let annotation = MKPointAnnotation()
|
||||
annotation.title = "Dropped Pin"
|
||||
annotation.coordinate = coordinate
|
||||
parent.mapView.addAnnotation(annotation)
|
||||
UINotificationFeedbackGenerator().notificationOccurred(.success)
|
||||
parent.onLongPress(coordinate)
|
||||
}
|
||||
}
|
||||
public func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
|
||||
if let tileOverlay = overlay as? MKTileOverlay {
|
||||
return MKTileOverlayRenderer(tileOverlay: tileOverlay)
|
||||
} else {
|
||||
if let routePolyline = overlay as? MKPolyline {
|
||||
let titleString = routePolyline.title ?? "0"
|
||||
let renderer = MKPolylineRenderer(polyline: routePolyline)
|
||||
renderer.strokeColor = UIColor(hex: UInt32(titleString) ?? 0).lighter()
|
||||
renderer.lineWidth = 8
|
||||
return renderer
|
||||
}
|
||||
if let polygon = overlay as? MKPolygon {
|
||||
let renderer = MKPolygonRenderer(polygon: polygon)
|
||||
renderer.fillColor = UIColor.purple.withAlphaComponent(0.2)
|
||||
renderer.strokeColor = .purple.withAlphaComponent(0.7)
|
||||
return renderer
|
||||
}
|
||||
return MKOverlayRenderer(overlay: overlay)
|
||||
}
|
||||
}
|
||||
}
|
||||
/// is supposed to be located in the folder with the map name
|
||||
public struct DefaultTile: Hashable {
|
||||
let tileName: String
|
||||
let tileType: String
|
||||
public init(tileName: String, tileType: String) {
|
||||
self.tileName = tileName
|
||||
self.tileType = tileType
|
||||
}
|
||||
}
|
||||
public struct CustomMapOverlay: Equatable, Hashable {
|
||||
let mapName: String
|
||||
let tileType: String
|
||||
var canReplaceMapContent: Bool
|
||||
var minimumZoomLevel: Int?
|
||||
var maximumZoomLevel: Int?
|
||||
let defaultTile: DefaultTile?
|
||||
public init(
|
||||
mapName: String,
|
||||
tileType: String,
|
||||
canReplaceMapContent: Bool = true, // false for transparent tiles
|
||||
minimumZoomLevel: Int? = nil,
|
||||
maximumZoomLevel: Int? = nil,
|
||||
defaultTile: DefaultTile? = nil
|
||||
) {
|
||||
self.mapName = mapName
|
||||
self.tileType = tileType
|
||||
self.canReplaceMapContent = canReplaceMapContent
|
||||
self.minimumZoomLevel = minimumZoomLevel
|
||||
self.maximumZoomLevel = maximumZoomLevel
|
||||
self.defaultTile = defaultTile
|
||||
}
|
||||
public init?(
|
||||
mapName: String?,
|
||||
tileType: String,
|
||||
canReplaceMapContent: Bool = true, // false for transparent tiles
|
||||
minimumZoomLevel: Int? = nil,
|
||||
maximumZoomLevel: Int? = nil,
|
||||
defaultTile: DefaultTile? = nil
|
||||
) {
|
||||
if mapName == nil || mapName! == "" {
|
||||
return nil
|
||||
}
|
||||
self.mapName = mapName!
|
||||
self.tileType = tileType
|
||||
self.canReplaceMapContent = canReplaceMapContent
|
||||
self.minimumZoomLevel = minimumZoomLevel
|
||||
self.maximumZoomLevel = maximumZoomLevel
|
||||
self.defaultTile = defaultTile
|
||||
}
|
||||
}
|
||||
}
|
||||
//struct PolygonInfo: Codable {
|
||||
// let stroke: String?
|
||||
// let strokeWidth, strokeOpacity: Int?
|
||||
// let fill: String?
|
||||
// let fillOpacity: Double?
|
||||
// let title, subtitle: String?
|
||||
//}
|
||||
//
|
||||
//func degreesToRadians(_ number: Double) -> Double {
|
||||
// return number * .pi / 180
|
||||
//}
|
||||
//var currentMapLayer: MapLayer?
|
||||
//
|
||||
//struct MapViewSwiftUI: UIViewRepresentable {
|
||||
// var onLongPress: (_ waypointCoordinate: CLLocationCoordinate2D) -> Void
|
||||
// var onWaypointEdit: (_ waypointId: Int ) -> Void
|
||||
// let mapView = MKMapView()
|
||||
// // Parameters
|
||||
// let selectedMapLayer: MapLayer
|
||||
// let selectedWeatherLayer: MapOverlayServer = UserDefaults.mapOverlayServer
|
||||
// let positions: [PositionEntity]
|
||||
// let waypoints: [WaypointEntity]
|
||||
// let userTrackingMode: MKUserTrackingMode
|
||||
// let showNodeHistory: Bool
|
||||
// let showRouteLines: Bool
|
||||
// let mapViewType: MKMapType = MKMapType.standard
|
||||
// // Offline Map Tiles
|
||||
// @AppStorage("lastUpdatedLocalMapFile") private var lastUpdatedLocalMapFile = 0
|
||||
// @State private var loadedLastUpdatedLocalMapFile = 0
|
||||
// var customMapOverlay: CustomMapOverlay?
|
||||
// @State private var presentCustomMapOverlayHash: CustomMapOverlay?
|
||||
// // MARK: Private methods
|
||||
// private func configureMap(mapView: MKMapView) {
|
||||
// // Map View Parameters
|
||||
// mapView.mapType = mapViewType
|
||||
// mapView.addAnnotations(waypoints)
|
||||
// // Do the initial map centering
|
||||
// let latest = positions
|
||||
// .filter { $0.latest == true }
|
||||
// .sorted { $0.nodePosition?.num ?? 0 > $1.nodePosition?.num ?? -1 }
|
||||
// let span = MKCoordinateSpan(latitudeDelta: 0.003, longitudeDelta: 0.003)
|
||||
// let center = (latest.count > 0 && userTrackingMode == MKUserTrackingMode.none) ? latest[0].coordinate : LocationHelper.currentLocation
|
||||
// let region = MKCoordinateRegion(center: center, span: span)
|
||||
// mapView.addAnnotations(showNodeHistory ? positions : latest)
|
||||
// mapView.setRegion(region, animated: true)
|
||||
// // Set user (phone gps) tracking options
|
||||
// mapView.setUserTrackingMode(userTrackingMode, animated: true)
|
||||
// if userTrackingMode == MKUserTrackingMode.none {
|
||||
// if latest.count == 1 {
|
||||
// mapView.fit(annotations: showNodeHistory ? positions: latest, andShow: false)
|
||||
// } else {
|
||||
// mapView.fitAllAnnotations()
|
||||
// }
|
||||
// mapView.showsUserLocation = false
|
||||
// } else {
|
||||
// mapView.showsUserLocation = true
|
||||
// }
|
||||
// // Other MKMapView Settings
|
||||
// mapView.preferredConfiguration.elevationStyle = .realistic// .flat
|
||||
// mapView.pointOfInterestFilter = MKPointOfInterestFilter.excludingAll
|
||||
// mapView.isPitchEnabled = true
|
||||
// mapView.isRotateEnabled = true
|
||||
// mapView.isScrollEnabled = true
|
||||
// mapView.isZoomEnabled = true
|
||||
// mapView.showsBuildings = true
|
||||
// mapView.showsScale = true
|
||||
// mapView.showsTraffic = true
|
||||
//
|
||||
// mapView.showsCompass = false
|
||||
// let compass = MKCompassButton(mapView: mapView)
|
||||
// compass.translatesAutoresizingMaskIntoConstraints = false
|
||||
// #if targetEnvironment(macCatalyst)
|
||||
// // Show the default always visible compass and the mac only controls
|
||||
// compass.compassVisibility = .visible
|
||||
// mapView.addSubview(compass)
|
||||
// mapView.showsZoomControls = true
|
||||
// mapView.showsPitchControl = true
|
||||
// compass.trailingAnchor.constraint(equalTo: mapView.trailingAnchor, constant: -115).isActive = true
|
||||
// compass.bottomAnchor.constraint(equalTo: mapView.bottomAnchor, constant: -5).isActive = true
|
||||
// #else
|
||||
// compass.compassVisibility = .adaptive
|
||||
// mapView.addSubview(compass)
|
||||
// compass.trailingAnchor.constraint(equalTo: mapView.trailingAnchor, constant: -5).isActive = true
|
||||
// compass.topAnchor.constraint(equalTo: mapView.topAnchor, constant: 145).isActive = true
|
||||
// #endif
|
||||
// }
|
||||
// private func setMapBaseLayer(mapView: MKMapView) {
|
||||
// // Avoid refreshing UI if selectedLayer has not changed
|
||||
// guard currentMapLayer != selectedMapLayer else { return }
|
||||
// currentMapLayer = selectedMapLayer
|
||||
// for overlay in mapView.overlays where overlay is MKTileOverlay {
|
||||
// mapView.removeOverlay(overlay)
|
||||
// }
|
||||
// switch selectedMapLayer {
|
||||
// case .offline:
|
||||
// mapView.mapType = .standard
|
||||
// let overlay = TileOverlay()
|
||||
// overlay.canReplaceMapContent = false
|
||||
// overlay.minimumZ = UserDefaults.mapTileServer.zoomRange.startIndex
|
||||
// overlay.maximumZ = UserDefaults.mapTileServer.zoomRange.endIndex
|
||||
// mapView.addOverlay(overlay, level: UserDefaults.mapTilesAboveLabels ? .aboveLabels : .aboveRoads)
|
||||
// case .satellite:
|
||||
// mapView.mapType = .satellite
|
||||
// case .hybrid:
|
||||
// mapView.mapType = .hybrid
|
||||
// default:
|
||||
// mapView.mapType = .standard
|
||||
// }
|
||||
// }
|
||||
// private func setMapOverlays(mapView: MKMapView) {
|
||||
// // Weather radar
|
||||
// if UserDefaults.enableOverlayServer {
|
||||
// let locale = Locale.current
|
||||
// if locale.region?.identifier ?? "no locale" == "US" {
|
||||
// let overlay = MKTileOverlay(urlTemplate: selectedWeatherLayer.tileUrl)
|
||||
// overlay.canReplaceMapContent = false
|
||||
// overlay.minimumZ = selectedWeatherLayer.zoomRange.startIndex
|
||||
// overlay.maximumZ = selectedWeatherLayer.zoomRange.endIndex
|
||||
// mapView.addOverlay(overlay, level: .aboveLabels)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// func makeUIView(context: Context) -> MKMapView {
|
||||
// currentMapLayer = nil
|
||||
// mapView.delegate = context.coordinator
|
||||
// self.configureMap(mapView: mapView)
|
||||
// return mapView
|
||||
// }
|
||||
// func updateUIView(_ mapView: MKMapView, context: Context) {
|
||||
// // Set selected map base layer
|
||||
// setMapBaseLayer(mapView: mapView)
|
||||
// // Set map tile server and weather overlay layers
|
||||
// setMapOverlays(mapView: mapView)
|
||||
// let latest = positions
|
||||
// .filter { $0.latest == true }
|
||||
// .sorted { $0.nodePosition?.num ?? 0 > $1.nodePosition?.num ?? -1 }
|
||||
// // Node Route Lines
|
||||
// if showRouteLines {
|
||||
// // Remove all existing PolyLine Overlays
|
||||
// for overlay in mapView.overlays where overlay is MKPolyline {
|
||||
// mapView.removeOverlay(overlay)
|
||||
// }
|
||||
// var lineIndex = 0
|
||||
// for position in latest {
|
||||
// let nodePositions = positions.filter { $0.nodeCoordinate != nil && $0.nodePosition?.num ?? 0 == position.nodePosition?.num ?? -1 }
|
||||
// let lineCoords = nodePositions.compactMap({(position) -> CLLocationCoordinate2D in
|
||||
// return position.nodeCoordinate ?? LocationHelper.DefaultLocation
|
||||
// })
|
||||
// let polyline = MKPolyline(coordinates: lineCoords, count: nodePositions.count)
|
||||
// polyline.title = "\(String(position.nodePosition?.num ?? 0))"
|
||||
// mapView.addOverlay(polyline, level: .aboveLabels)
|
||||
// lineIndex += 1
|
||||
// // There are 18 colors for lines, start over if we are at index 17
|
||||
// if lineIndex > 17 {
|
||||
// lineIndex = 0
|
||||
// }
|
||||
// }
|
||||
// } else {
|
||||
// // Remove all existing PolyLine Overlays
|
||||
// for overlay in mapView.overlays where overlay is MKPolyline {
|
||||
// mapView.removeOverlay(overlay)
|
||||
// }
|
||||
// }
|
||||
// let annotationCount = waypoints.count + (showNodeHistory ? positions.count : latest.count)
|
||||
// if annotationCount != mapView.annotations.count {
|
||||
// Logger.services.info("Annotation Count: \(annotationCount) Map Annotations: \(mapView.annotations.count)")
|
||||
// mapView.removeAnnotations(mapView.annotations)
|
||||
// mapView.addAnnotations(waypoints)
|
||||
// }
|
||||
// mapView.addAnnotations(showNodeHistory ? positions : latest)
|
||||
// if userTrackingMode == MKUserTrackingMode.none {
|
||||
// mapView.showsUserLocation = false
|
||||
// if UserDefaults.enableMapRecentering {
|
||||
// if latest.count == 1 {
|
||||
// mapView.fit(annotations: showNodeHistory ? positions : latest, andShow: true)
|
||||
// } else {
|
||||
// mapView.fitAllAnnotations()
|
||||
// }
|
||||
// }
|
||||
// } else {
|
||||
// mapView.showsUserLocation = true
|
||||
// }
|
||||
// mapView.setUserTrackingMode(userTrackingMode, animated: true)
|
||||
// }
|
||||
// func makeCoordinator() -> MapCoordinator {
|
||||
// return Coordinator(self)
|
||||
// }
|
||||
// final class MapCoordinator: NSObject, MKMapViewDelegate, UIGestureRecognizerDelegate {
|
||||
// var parent: MapViewSwiftUI
|
||||
// var longPressRecognizer = UILongPressGestureRecognizer()
|
||||
// init(_ parent: MapViewSwiftUI) {
|
||||
// self.parent = parent
|
||||
// super.init()
|
||||
// self.longPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(longPressHandler))
|
||||
// self.longPressRecognizer.minimumPressDuration = 0.5
|
||||
// self.longPressRecognizer.cancelsTouchesInView = true
|
||||
// self.longPressRecognizer.delegate = self
|
||||
// self.parent.mapView.addGestureRecognizer(longPressRecognizer)
|
||||
// }
|
||||
// func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
|
||||
// switch annotation {
|
||||
// case let positionAnnotation as PositionEntity:
|
||||
// let reuseID = String(positionAnnotation.nodePosition?.num ?? 0) + "-" + String(positionAnnotation.time?.timeIntervalSince1970 ?? 0)
|
||||
// let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "node") as? MKMarkerAnnotationView ?? MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: reuseID )
|
||||
// annotationView.tag = -1
|
||||
// annotationView.canShowCallout = true
|
||||
// if positionAnnotation.latest {
|
||||
// annotationView.markerTintColor = UIColor(hex: UInt32(positionAnnotation.nodePosition?.num ?? 0)).darker()
|
||||
// annotationView.displayPriority = .required
|
||||
// annotationView.titleVisibility = .visible
|
||||
// } else {
|
||||
// annotationView.markerTintColor = UIColor(hex: UInt32(positionAnnotation.nodePosition?.num ?? 0)).lighter()
|
||||
// annotationView.displayPriority = .defaultHigh
|
||||
// annotationView.titleVisibility = .adaptive
|
||||
// }
|
||||
// annotationView.tag = -1
|
||||
// annotationView.canShowCallout = true
|
||||
// annotationView.titleVisibility = .adaptive
|
||||
// let leftIcon = UIImageView(image: annotationView.glyphText?.image())
|
||||
// leftIcon.backgroundColor = UIColor(.indigo)
|
||||
// annotationView.leftCalloutAccessoryView = leftIcon
|
||||
// let subtitle = UILabel()
|
||||
// subtitle.text = "Long Name: \(positionAnnotation.nodePosition?.user?.longName ?? "Unknown") \n"
|
||||
// subtitle.text? += "Latitude: \(String(format: "%.5f", positionAnnotation.coordinate.latitude)) \n"
|
||||
// subtitle.text! += "Longitude: \(String(format: "%.5f", positionAnnotation.coordinate.longitude)) \n"
|
||||
// let distanceFormatter = MKDistanceFormatter()
|
||||
// subtitle.text! += "Altitude: \(distanceFormatter.string(fromDistance: Double(positionAnnotation.altitude))) \n"
|
||||
// if positionAnnotation.nodePosition?.metadata != nil {
|
||||
// if DeviceRoles(rawValue: Int(positionAnnotation.nodePosition!.metadata?.role ?? 0)) == DeviceRoles.client ||
|
||||
// DeviceRoles(rawValue: Int(positionAnnotation.nodePosition!.metadata?.role ?? 0)) == DeviceRoles.clientMute ||
|
||||
// DeviceRoles(rawValue: Int(positionAnnotation.nodePosition!.metadata?.role ?? 0)) == DeviceRoles.routerClient {
|
||||
// annotationView.glyphImage = UIImage(systemName: "flipphone")
|
||||
// } else if DeviceRoles(rawValue: Int(positionAnnotation.nodePosition!.metadata?.role ?? 0)) == DeviceRoles.repeater {
|
||||
// annotationView.glyphImage = UIImage(systemName: "repeat")
|
||||
// } else if DeviceRoles(rawValue: Int(positionAnnotation.nodePosition!.metadata?.role ?? 0)) == DeviceRoles.router {
|
||||
// annotationView.glyphImage = UIImage(systemName: "wifi.router.fill")
|
||||
// } else if DeviceRoles(rawValue: Int(positionAnnotation.nodePosition!.metadata?.role ?? 0)) == DeviceRoles.tracker {
|
||||
// annotationView.glyphImage = UIImage(systemName: "location.viewfinder")
|
||||
// } else if DeviceRoles(rawValue: Int(positionAnnotation.nodePosition!.metadata?.role ?? 0)) == DeviceRoles.sensor {
|
||||
// annotationView.glyphImage = UIImage(systemName: "sensor")
|
||||
// }
|
||||
// let pf = PositionFlags(rawValue: Int(positionAnnotation.nodePosition?.metadata?.positionFlags ?? 3))
|
||||
// if pf.contains(.Satsinview) {
|
||||
// subtitle.text! += "Sats in view: \(String(positionAnnotation.satsInView)) \n"
|
||||
// }
|
||||
// if pf.contains(.SeqNo) {
|
||||
// subtitle.text! += "Sequence: \(String(positionAnnotation.seqNo)) \n"
|
||||
// }
|
||||
// if pf.contains(.Heading) {
|
||||
// if parent.userTrackingMode != MKUserTrackingMode.followWithHeading {
|
||||
// annotationView.glyphImage = UIImage(systemName: "location.north.fill")?.rotate(radians: Float(degreesToRadians(Double(positionAnnotation.heading))))
|
||||
// subtitle.text! += "Heading: \(String(positionAnnotation.heading)) \n"
|
||||
// } else {
|
||||
// annotationView.glyphImage = UIImage(systemName: "flipphone")
|
||||
// }
|
||||
// }
|
||||
// if pf.contains(.Speed) {
|
||||
// let formatter = MeasurementFormatter()
|
||||
// formatter.locale = Locale.current
|
||||
// if positionAnnotation.speed <= 1 {
|
||||
// annotationView.glyphImage = UIImage(systemName: "hexagon")
|
||||
// }
|
||||
// subtitle.text! += "Speed: \(formatter.string(from: Measurement(value: Double(positionAnnotation.speed), unit: UnitSpeed.kilometersPerHour))) \n"
|
||||
// }
|
||||
// } else {
|
||||
// // node metadata is nil
|
||||
// annotationView.glyphImage = UIImage(systemName: "flipphone")
|
||||
// }
|
||||
// if LocationHelper.currentLocation.distance(from: LocationHelper.DefaultLocation) > 0.0 {
|
||||
// let metersAway = positionAnnotation.coordinate.distance(from: LocationHelper.currentLocation)
|
||||
// subtitle.text! += "distance".localized + ": \(distanceFormatter.string(fromDistance: Double(metersAway))) \n"
|
||||
// }
|
||||
// subtitle.text! += positionAnnotation.time?.formatted() ?? "Unknown \n"
|
||||
// subtitle.numberOfLines = 0
|
||||
// annotationView.detailCalloutAccessoryView = subtitle
|
||||
// let detailsIcon = UIButton(type: .detailDisclosure)
|
||||
// detailsIcon.setImage(UIImage(systemName: "trash"), for: .normal)
|
||||
// annotationView.rightCalloutAccessoryView = detailsIcon
|
||||
// return annotationView
|
||||
// case let waypointAnnotation as WaypointEntity:
|
||||
// let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "waypoint") as? MKMarkerAnnotationView ?? MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: String(waypointAnnotation.id))
|
||||
// annotationView.tag = Int(waypointAnnotation.id)
|
||||
// annotationView.isEnabled = true
|
||||
// annotationView.canShowCallout = true
|
||||
// if waypointAnnotation.icon == 0 {
|
||||
// annotationView.glyphText = "📍"
|
||||
// } else {
|
||||
// annotationView.glyphText = String(UnicodeScalar(Int(waypointAnnotation.icon)) ?? "📍")
|
||||
// }
|
||||
// annotationView.markerTintColor = UIColor(.accentColor)
|
||||
// annotationView.displayPriority = .required
|
||||
// annotationView.titleVisibility = .adaptive
|
||||
// let leftIcon = UIImageView(image: annotationView.glyphText?.image())
|
||||
// leftIcon.backgroundColor = UIColor(.accentColor)
|
||||
// annotationView.leftCalloutAccessoryView = leftIcon
|
||||
// let subtitle = UILabel()
|
||||
// if waypointAnnotation.longDescription?.count ?? 0 > 0 {
|
||||
// subtitle.text = (waypointAnnotation.longDescription ?? "") + "\n"
|
||||
// } else {
|
||||
// subtitle.text = ""
|
||||
// }
|
||||
// if LocationHelper.currentLocation.distance(from: LocationHelper.DefaultLocation) > 0.0 {
|
||||
// let metersAway = waypointAnnotation.coordinate.distance(from: LocationHelper.currentLocation)
|
||||
// let distanceFormatter = MKDistanceFormatter()
|
||||
// subtitle.text! += "distance".localized + ": \(distanceFormatter.string(fromDistance: Double(metersAway))) \n"
|
||||
// }
|
||||
// if waypointAnnotation.created != nil {
|
||||
// subtitle.text! += "Created: \(waypointAnnotation.created?.formatted() ?? "Unknown") \n"
|
||||
// }
|
||||
// if waypointAnnotation.lastUpdated != nil {
|
||||
// subtitle.text! += "Updated: \(waypointAnnotation.lastUpdated?.formatted() ?? "Unknown") \n"
|
||||
// }
|
||||
// if waypointAnnotation.expire != nil {
|
||||
// subtitle.text! += "Expires: \(waypointAnnotation.expire?.formatted() ?? "Unknown") \n"
|
||||
// }
|
||||
// subtitle.numberOfLines = 0
|
||||
// annotationView.detailCalloutAccessoryView = subtitle
|
||||
// let editIcon = UIButton(type: .detailDisclosure)
|
||||
// editIcon.setImage(UIImage(systemName: "square.and.pencil"), for: .normal)
|
||||
// annotationView.rightCalloutAccessoryView = editIcon
|
||||
// return annotationView
|
||||
// default: return nil
|
||||
// }
|
||||
// }
|
||||
// func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
|
||||
// switch view.annotation {
|
||||
// case _ as WaypointEntity:
|
||||
// // Only Allow Edit for waypoint annotations with a id
|
||||
// if view.tag > 0 {
|
||||
// parent.onWaypointEdit(view.tag)
|
||||
// }
|
||||
// default: break
|
||||
// }
|
||||
// }
|
||||
// @objc func longPressHandler(_ gesture: UILongPressGestureRecognizer) {
|
||||
// if gesture.state != UIGestureRecognizer.State.ended {
|
||||
// return
|
||||
// } else if gesture.state != UIGestureRecognizer.State.began {
|
||||
// // Screen Position - CGPoint
|
||||
// let location = longPressRecognizer.location(in: self.parent.mapView)
|
||||
// // Map Coordinate - CLLocationCoordinate2D
|
||||
// let coordinate = self.parent.mapView.convert(location, toCoordinateFrom: self.parent.mapView)
|
||||
// let annotation = MKPointAnnotation()
|
||||
// annotation.title = "Dropped Pin"
|
||||
// annotation.coordinate = coordinate
|
||||
// parent.mapView.addAnnotation(annotation)
|
||||
// UINotificationFeedbackGenerator().notificationOccurred(.success)
|
||||
// parent.onLongPress(coordinate)
|
||||
// }
|
||||
// }
|
||||
// public func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
|
||||
// if let tileOverlay = overlay as? MKTileOverlay {
|
||||
// return MKTileOverlayRenderer(tileOverlay: tileOverlay)
|
||||
// } else {
|
||||
// if let routePolyline = overlay as? MKPolyline {
|
||||
// let titleString = routePolyline.title ?? "0"
|
||||
// let renderer = MKPolylineRenderer(polyline: routePolyline)
|
||||
// renderer.strokeColor = UIColor(hex: UInt32(titleString) ?? 0).lighter()
|
||||
// renderer.lineWidth = 8
|
||||
// return renderer
|
||||
// }
|
||||
// if let polygon = overlay as? MKPolygon {
|
||||
// let renderer = MKPolygonRenderer(polygon: polygon)
|
||||
// renderer.fillColor = UIColor.purple.withAlphaComponent(0.2)
|
||||
// renderer.strokeColor = .purple.withAlphaComponent(0.7)
|
||||
// return renderer
|
||||
// }
|
||||
// return MKOverlayRenderer(overlay: overlay)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// /// is supposed to be located in the folder with the map name
|
||||
// public struct DefaultTile: Hashable {
|
||||
// let tileName: String
|
||||
// let tileType: String
|
||||
// public init(tileName: String, tileType: String) {
|
||||
// self.tileName = tileName
|
||||
// self.tileType = tileType
|
||||
// }
|
||||
// }
|
||||
// public struct CustomMapOverlay: Equatable, Hashable {
|
||||
// let mapName: String
|
||||
// let tileType: String
|
||||
// var canReplaceMapContent: Bool
|
||||
// var minimumZoomLevel: Int?
|
||||
// var maximumZoomLevel: Int?
|
||||
// let defaultTile: DefaultTile?
|
||||
// public init(
|
||||
// mapName: String,
|
||||
// tileType: String,
|
||||
// canReplaceMapContent: Bool = true, // false for transparent tiles
|
||||
// minimumZoomLevel: Int? = nil,
|
||||
// maximumZoomLevel: Int? = nil,
|
||||
// defaultTile: DefaultTile? = nil
|
||||
// ) {
|
||||
// self.mapName = mapName
|
||||
// self.tileType = tileType
|
||||
// self.canReplaceMapContent = canReplaceMapContent
|
||||
// self.minimumZoomLevel = minimumZoomLevel
|
||||
// self.maximumZoomLevel = maximumZoomLevel
|
||||
// self.defaultTile = defaultTile
|
||||
// }
|
||||
// public init?(
|
||||
// mapName: String?,
|
||||
// tileType: String,
|
||||
// canReplaceMapContent: Bool = true, // false for transparent tiles
|
||||
// minimumZoomLevel: Int? = nil,
|
||||
// maximumZoomLevel: Int? = nil,
|
||||
// defaultTile: DefaultTile? = nil
|
||||
// ) {
|
||||
// if mapName == nil || mapName! == "" {
|
||||
// return nil
|
||||
// }
|
||||
// self.mapName = mapName!
|
||||
// self.tileType = tileType
|
||||
// self.canReplaceMapContent = canReplaceMapContent
|
||||
// self.minimumZoomLevel = minimumZoomLevel
|
||||
// self.maximumZoomLevel = maximumZoomLevel
|
||||
// self.defaultTile = defaultTile
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
|
|
|||
|
|
@ -1,266 +1,266 @@
|
|||
////
|
||||
//// WaypointFormView.swift
|
||||
//// Meshtastic
|
||||
////
|
||||
//// Copyright Garth Vander Houwen 1/10/23.
|
||||
////
|
||||
//
|
||||
// WaypointFormView.swift
|
||||
// Meshtastic
|
||||
//import CoreLocation
|
||||
//import MeshtasticProtobufs
|
||||
//import OSLog
|
||||
//import SwiftUI
|
||||
//
|
||||
// Copyright Garth Vander Houwen 1/10/23.
|
||||
//struct WaypointFormMapKit: View {
|
||||
//
|
||||
|
||||
import CoreLocation
|
||||
import MeshtasticProtobufs
|
||||
import OSLog
|
||||
import SwiftUI
|
||||
|
||||
struct WaypointFormMapKit: View {
|
||||
|
||||
@EnvironmentObject var bleManager: BLEManager
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@State var coordinate: WaypointCoordinate
|
||||
@FocusState private var iconIsFocused: Bool
|
||||
@State private var name: String = ""
|
||||
@State private var description: String = ""
|
||||
@State private var icon: String = "📍"
|
||||
@State private var latitude: Double = 0
|
||||
@State private var longitude: Double = 0
|
||||
@State private var expires: Bool = false
|
||||
@State private var expire: Date = Date.now.addingTimeInterval(60 * 480) // 1 minute * 480 = 8 Hours
|
||||
@State private var locked: Bool = false
|
||||
@State private var lockedTo: Int64 = 0
|
||||
|
||||
var body: some View {
|
||||
|
||||
Form {
|
||||
let distance = CLLocation(latitude: LocationHelper.currentLocation.latitude, longitude: LocationHelper.currentLocation.longitude).distance(from: CLLocation(latitude: coordinate.coordinate?.latitude ?? 0, longitude: coordinate.coordinate?.longitude ?? 0))
|
||||
Section(header: Text((coordinate.waypointId > 0) ? "Editing Waypoint" : "Create Waypoint")) {
|
||||
HStack {
|
||||
Text("Location: \(String(format: "%.5f", latitude) + "," + String(format: "%.5f", longitude))")
|
||||
.textSelection(.enabled)
|
||||
.foregroundColor(Color.gray)
|
||||
.font(.caption2)
|
||||
if coordinate.coordinate?.latitude ?? 0 != 0 && coordinate.coordinate?.longitude ?? 0 != 0 {
|
||||
DistanceText(meters: distance)
|
||||
.foregroundColor(Color.gray)
|
||||
.font(.caption2)
|
||||
}
|
||||
}
|
||||
HStack {
|
||||
Text("Name")
|
||||
Spacer()
|
||||
TextField(
|
||||
"Name",
|
||||
text: $name,
|
||||
axis: .vertical
|
||||
)
|
||||
.foregroundColor(Color.gray)
|
||||
.onChange(of: name) {
|
||||
var totalBytes = name.utf8.count
|
||||
// Only mess with the value if it is too big
|
||||
while totalBytes > 30 {
|
||||
name = String(name.dropLast())
|
||||
totalBytes = name.utf8.count
|
||||
}
|
||||
if totalBytes > 30 {
|
||||
name = String(name.dropLast())
|
||||
}
|
||||
}
|
||||
}
|
||||
HStack {
|
||||
Text("Description")
|
||||
Spacer()
|
||||
TextField(
|
||||
"Description",
|
||||
text: $description,
|
||||
axis: .vertical
|
||||
)
|
||||
.foregroundColor(Color.gray)
|
||||
.onChange(of: description) {
|
||||
var totalBytes = description.utf8.count
|
||||
// Only mess with the value if it is too big
|
||||
while totalBytes > 100 {
|
||||
description = String(description.dropLast())
|
||||
totalBytes = description.utf8.count
|
||||
}
|
||||
}
|
||||
}
|
||||
HStack {
|
||||
Text("Icon")
|
||||
Spacer()
|
||||
EmojiOnlyTextField(text: $icon, placeholder: "Select an emoji")
|
||||
.font(.title)
|
||||
.focused($iconIsFocused)
|
||||
.onChange(of: icon) { _, value in
|
||||
|
||||
// If you have anything other than emojis in your string make it empty
|
||||
if !value.onlyEmojis() {
|
||||
icon = ""
|
||||
}
|
||||
// If a second emoji is entered delete the first one
|
||||
if value.count >= 1 {
|
||||
|
||||
if value.count > 1 {
|
||||
let index = value.index(value.startIndex, offsetBy: 1)
|
||||
icon = String(value[index])
|
||||
}
|
||||
iconIsFocused = false
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Toggle(isOn: $expires) {
|
||||
Label("Expires", systemImage: "clock.badge.xmark")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
if expires {
|
||||
DatePicker("Expire", selection: $expire, in: Date.now...)
|
||||
.datePickerStyle(.compact)
|
||||
.font(.callout)
|
||||
}
|
||||
Toggle(isOn: $locked) {
|
||||
Label("Locked", systemImage: "lock")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
}
|
||||
}
|
||||
HStack {
|
||||
Button {
|
||||
|
||||
var newWaypoint = Waypoint()
|
||||
// Loading a waypoint from edit
|
||||
if coordinate.waypointId > 0 {
|
||||
newWaypoint.id = UInt32(coordinate.waypointId)
|
||||
let waypoint = getWaypoint(id: Int64(coordinate.waypointId), context: bleManager.context)
|
||||
newWaypoint.latitudeI = waypoint.latitudeI
|
||||
newWaypoint.longitudeI = waypoint.longitudeI
|
||||
} else {
|
||||
// New waypoint
|
||||
newWaypoint.id = UInt32.random(in: UInt32(UInt8.max)..<UInt32.max)
|
||||
newWaypoint.latitudeI = Int32(Double(coordinate.coordinate?.latitude ?? 0) * 1e7)
|
||||
newWaypoint.longitudeI = Int32(Double(coordinate.coordinate?.longitude ?? 0) * 1e7)
|
||||
}
|
||||
newWaypoint.name = name.count > 0 ? name : "Dropped Pin"
|
||||
newWaypoint.description_p = description
|
||||
// Unicode scalar value for the icon emoji string
|
||||
let unicodeScalers = icon.unicodeScalars
|
||||
// First element as an UInt32
|
||||
let unicode = unicodeScalers[unicodeScalers.startIndex].value
|
||||
newWaypoint.icon = unicode
|
||||
if locked {
|
||||
if lockedTo == 0 {
|
||||
newWaypoint.lockedTo = UInt32(bleManager.connectedPeripheral!.num)
|
||||
} else {
|
||||
newWaypoint.lockedTo = UInt32(lockedTo)
|
||||
}
|
||||
}
|
||||
if expires {
|
||||
newWaypoint.expire = UInt32(expire.timeIntervalSince1970)
|
||||
} else {
|
||||
newWaypoint.expire = 0
|
||||
}
|
||||
if bleManager.sendWaypoint(waypoint: newWaypoint) {
|
||||
dismiss()
|
||||
} else {
|
||||
dismiss()
|
||||
Logger.mesh.error("Send waypoint failed")
|
||||
}
|
||||
} label: {
|
||||
Label("Send", systemImage: "arrow.up")
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
.buttonBorderShape(.capsule)
|
||||
.controlSize(.regular)
|
||||
.disabled(bleManager.connectedPeripheral == nil)
|
||||
.padding(.bottom)
|
||||
|
||||
Button(role: .cancel) {
|
||||
dismiss()
|
||||
} label: {
|
||||
Label("cancel", systemImage: "x.circle")
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
.buttonBorderShape(.capsule)
|
||||
.controlSize(.regular)
|
||||
.padding(.bottom)
|
||||
|
||||
if coordinate.waypointId > 0 {
|
||||
|
||||
Menu {
|
||||
Button("For me", action: {
|
||||
let waypoint = getWaypoint(id: Int64(coordinate.waypointId), context: bleManager.context)
|
||||
bleManager.context.delete(waypoint)
|
||||
do {
|
||||
try bleManager.context.save()
|
||||
} catch {
|
||||
bleManager.context.rollback()
|
||||
}
|
||||
dismiss() })
|
||||
Button("For everyone", action: {
|
||||
var newWaypoint = Waypoint()
|
||||
|
||||
if coordinate.waypointId > 0 {
|
||||
newWaypoint.id = UInt32(coordinate.waypointId)
|
||||
}
|
||||
newWaypoint.name = name.count > 0 ? name : "Dropped Pin"
|
||||
newWaypoint.description_p = description
|
||||
newWaypoint.latitudeI = Int32(coordinate.coordinate?.latitude ?? 0 * 1e7)
|
||||
newWaypoint.longitudeI = Int32(coordinate.coordinate?.longitude ?? 0 * 1e7)
|
||||
// Unicode scalar value for the icon emoji string
|
||||
let unicodeScalers = icon.unicodeScalars
|
||||
// First element as an UInt32
|
||||
let unicode = unicodeScalers[unicodeScalers.startIndex].value
|
||||
newWaypoint.icon = unicode
|
||||
if locked {
|
||||
if lockedTo == 0 {
|
||||
newWaypoint.lockedTo = UInt32(bleManager.connectedPeripheral!.num)
|
||||
} else {
|
||||
newWaypoint.lockedTo = UInt32(lockedTo)
|
||||
}
|
||||
}
|
||||
newWaypoint.expire = 1
|
||||
if bleManager.sendWaypoint(waypoint: newWaypoint) {
|
||||
dismiss()
|
||||
} else {
|
||||
dismiss()
|
||||
Logger.mesh.error("Send waypoint failed")
|
||||
}
|
||||
})
|
||||
}
|
||||
label: {
|
||||
Label("delete", systemImage: "trash")
|
||||
.foregroundColor(.red)
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
.buttonBorderShape(.capsule)
|
||||
.controlSize(.regular)
|
||||
.padding(.bottom)
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
if coordinate.waypointId > 0 {
|
||||
let waypoint = getWaypoint(id: Int64(coordinate.waypointId), context: bleManager.context)
|
||||
name = waypoint.name ?? "Dropped Pin"
|
||||
description = waypoint.longDescription ?? ""
|
||||
icon = String(UnicodeScalar(Int(waypoint.icon)) ?? "📍")
|
||||
latitude = Double(waypoint.latitudeI) / 1e7
|
||||
longitude = Double(waypoint.longitudeI) / 1e7
|
||||
if waypoint.expire != nil {
|
||||
expires = true
|
||||
expire = waypoint.expire ?? Date()
|
||||
} else {
|
||||
expires = false
|
||||
}
|
||||
if waypoint.locked > 0 {
|
||||
locked = true
|
||||
lockedTo = waypoint.locked
|
||||
}
|
||||
} else {
|
||||
name = ""
|
||||
description = ""
|
||||
locked = false
|
||||
expires = false
|
||||
expire = Date.now.addingTimeInterval(60 * 480)
|
||||
icon = "📍"
|
||||
latitude = coordinate.coordinate?.latitude ?? 0
|
||||
longitude = coordinate.coordinate?.longitude ?? 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// @EnvironmentObject var bleManager: BLEManager
|
||||
// @Environment(\.dismiss) private var dismiss
|
||||
// @State var coordinate: WaypointCoordinate
|
||||
// @FocusState private var iconIsFocused: Bool
|
||||
// @State private var name: String = ""
|
||||
// @State private var description: String = ""
|
||||
// @State private var icon: String = "📍"
|
||||
// @State private var latitude: Double = 0
|
||||
// @State private var longitude: Double = 0
|
||||
// @State private var expires: Bool = false
|
||||
// @State private var expire: Date = Date.now.addingTimeInterval(60 * 480) // 1 minute * 480 = 8 Hours
|
||||
// @State private var locked: Bool = false
|
||||
// @State private var lockedTo: Int64 = 0
|
||||
//
|
||||
// var body: some View {
|
||||
//
|
||||
// Form {
|
||||
// let distance = CLLocation(latitude: LocationHelper.currentLocation.latitude, longitude: LocationHelper.currentLocation.longitude).distance(from: CLLocation(latitude: coordinate.coordinate?.latitude ?? 0, longitude: coordinate.coordinate?.longitude ?? 0))
|
||||
// Section(header: Text((coordinate.waypointId > 0) ? "Editing Waypoint" : "Create Waypoint")) {
|
||||
// HStack {
|
||||
// Text("Location: \(String(format: "%.5f", latitude) + "," + String(format: "%.5f", longitude))")
|
||||
// .textSelection(.enabled)
|
||||
// .foregroundColor(Color.gray)
|
||||
// .font(.caption2)
|
||||
// if coordinate.coordinate?.latitude ?? 0 != 0 && coordinate.coordinate?.longitude ?? 0 != 0 {
|
||||
// DistanceText(meters: distance)
|
||||
// .foregroundColor(Color.gray)
|
||||
// .font(.caption2)
|
||||
// }
|
||||
// }
|
||||
// HStack {
|
||||
// Text("Name")
|
||||
// Spacer()
|
||||
// TextField(
|
||||
// "Name",
|
||||
// text: $name,
|
||||
// axis: .vertical
|
||||
// )
|
||||
// .foregroundColor(Color.gray)
|
||||
// .onChange(of: name) {
|
||||
// var totalBytes = name.utf8.count
|
||||
// // Only mess with the value if it is too big
|
||||
// while totalBytes > 30 {
|
||||
// name = String(name.dropLast())
|
||||
// totalBytes = name.utf8.count
|
||||
// }
|
||||
// if totalBytes > 30 {
|
||||
// name = String(name.dropLast())
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// HStack {
|
||||
// Text("Description")
|
||||
// Spacer()
|
||||
// TextField(
|
||||
// "Description",
|
||||
// text: $description,
|
||||
// axis: .vertical
|
||||
// )
|
||||
// .foregroundColor(Color.gray)
|
||||
// .onChange(of: description) {
|
||||
// var totalBytes = description.utf8.count
|
||||
// // Only mess with the value if it is too big
|
||||
// while totalBytes > 100 {
|
||||
// description = String(description.dropLast())
|
||||
// totalBytes = description.utf8.count
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// HStack {
|
||||
// Text("Icon")
|
||||
// Spacer()
|
||||
// EmojiOnlyTextField(text: $icon, placeholder: "Select an emoji")
|
||||
// .font(.title)
|
||||
// .focused($iconIsFocused)
|
||||
// .onChange(of: icon) { _, value in
|
||||
//
|
||||
// // If you have anything other than emojis in your string make it empty
|
||||
// if !value.onlyEmojis() {
|
||||
// icon = ""
|
||||
// }
|
||||
// // If a second emoji is entered delete the first one
|
||||
// if value.count >= 1 {
|
||||
//
|
||||
// if value.count > 1 {
|
||||
// let index = value.index(value.startIndex, offsetBy: 1)
|
||||
// icon = String(value[index])
|
||||
// }
|
||||
// iconIsFocused = false
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// }
|
||||
// Toggle(isOn: $expires) {
|
||||
// Label("Expires", systemImage: "clock.badge.xmark")
|
||||
// }
|
||||
// .toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
// if expires {
|
||||
// DatePicker("Expire", selection: $expire, in: Date.now...)
|
||||
// .datePickerStyle(.compact)
|
||||
// .font(.callout)
|
||||
// }
|
||||
// Toggle(isOn: $locked) {
|
||||
// Label("Locked", systemImage: "lock")
|
||||
// }
|
||||
// .toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
// }
|
||||
// }
|
||||
// HStack {
|
||||
// Button {
|
||||
//
|
||||
// var newWaypoint = Waypoint()
|
||||
// // Loading a waypoint from edit
|
||||
// if coordinate.waypointId > 0 {
|
||||
// newWaypoint.id = UInt32(coordinate.waypointId)
|
||||
// let waypoint = getWaypoint(id: Int64(coordinate.waypointId), context: bleManager.context)
|
||||
// newWaypoint.latitudeI = waypoint.latitudeI
|
||||
// newWaypoint.longitudeI = waypoint.longitudeI
|
||||
// } else {
|
||||
// // New waypoint
|
||||
// newWaypoint.id = UInt32.random(in: UInt32(UInt8.max)..<UInt32.max)
|
||||
// newWaypoint.latitudeI = Int32(Double(coordinate.coordinate?.latitude ?? 0) * 1e7)
|
||||
// newWaypoint.longitudeI = Int32(Double(coordinate.coordinate?.longitude ?? 0) * 1e7)
|
||||
// }
|
||||
// newWaypoint.name = name.count > 0 ? name : "Dropped Pin"
|
||||
// newWaypoint.description_p = description
|
||||
// // Unicode scalar value for the icon emoji string
|
||||
// let unicodeScalers = icon.unicodeScalars
|
||||
// // First element as an UInt32
|
||||
// let unicode = unicodeScalers[unicodeScalers.startIndex].value
|
||||
// newWaypoint.icon = unicode
|
||||
// if locked {
|
||||
// if lockedTo == 0 {
|
||||
// newWaypoint.lockedTo = UInt32(bleManager.connectedPeripheral!.num)
|
||||
// } else {
|
||||
// newWaypoint.lockedTo = UInt32(lockedTo)
|
||||
// }
|
||||
// }
|
||||
// if expires {
|
||||
// newWaypoint.expire = UInt32(expire.timeIntervalSince1970)
|
||||
// } else {
|
||||
// newWaypoint.expire = 0
|
||||
// }
|
||||
// if bleManager.sendWaypoint(waypoint: newWaypoint) {
|
||||
// dismiss()
|
||||
// } else {
|
||||
// dismiss()
|
||||
// Logger.mesh.error("Send waypoint failed")
|
||||
// }
|
||||
// } label: {
|
||||
// Label("Send", systemImage: "arrow.up")
|
||||
// }
|
||||
// .buttonStyle(.bordered)
|
||||
// .buttonBorderShape(.capsule)
|
||||
// .controlSize(.regular)
|
||||
// .disabled(bleManager.connectedPeripheral == nil)
|
||||
// .padding(.bottom)
|
||||
//
|
||||
// Button(role: .cancel) {
|
||||
// dismiss()
|
||||
// } label: {
|
||||
// Label("cancel", systemImage: "x.circle")
|
||||
// }
|
||||
// .buttonStyle(.bordered)
|
||||
// .buttonBorderShape(.capsule)
|
||||
// .controlSize(.regular)
|
||||
// .padding(.bottom)
|
||||
//
|
||||
// if coordinate.waypointId > 0 {
|
||||
//
|
||||
// Menu {
|
||||
// Button("For me", action: {
|
||||
// let waypoint = getWaypoint(id: Int64(coordinate.waypointId), context: bleManager.context)
|
||||
// bleManager.context.delete(waypoint)
|
||||
// do {
|
||||
// try bleManager.context.save()
|
||||
// } catch {
|
||||
// bleManager.context.rollback()
|
||||
// }
|
||||
// dismiss() })
|
||||
// Button("For everyone", action: {
|
||||
// var newWaypoint = Waypoint()
|
||||
//
|
||||
// if coordinate.waypointId > 0 {
|
||||
// newWaypoint.id = UInt32(coordinate.waypointId)
|
||||
// }
|
||||
// newWaypoint.name = name.count > 0 ? name : "Dropped Pin"
|
||||
// newWaypoint.description_p = description
|
||||
// newWaypoint.latitudeI = Int32(coordinate.coordinate?.latitude ?? 0 * 1e7)
|
||||
// newWaypoint.longitudeI = Int32(coordinate.coordinate?.longitude ?? 0 * 1e7)
|
||||
// // Unicode scalar value for the icon emoji string
|
||||
// let unicodeScalers = icon.unicodeScalars
|
||||
// // First element as an UInt32
|
||||
// let unicode = unicodeScalers[unicodeScalers.startIndex].value
|
||||
// newWaypoint.icon = unicode
|
||||
// if locked {
|
||||
// if lockedTo == 0 {
|
||||
// newWaypoint.lockedTo = UInt32(bleManager.connectedPeripheral!.num)
|
||||
// } else {
|
||||
// newWaypoint.lockedTo = UInt32(lockedTo)
|
||||
// }
|
||||
// }
|
||||
// newWaypoint.expire = 1
|
||||
// if bleManager.sendWaypoint(waypoint: newWaypoint) {
|
||||
// dismiss()
|
||||
// } else {
|
||||
// dismiss()
|
||||
// Logger.mesh.error("Send waypoint failed")
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
// label: {
|
||||
// Label("delete", systemImage: "trash")
|
||||
// .foregroundColor(.red)
|
||||
// }
|
||||
// .buttonStyle(.bordered)
|
||||
// .buttonBorderShape(.capsule)
|
||||
// .controlSize(.regular)
|
||||
// .padding(.bottom)
|
||||
// }
|
||||
// }
|
||||
// .onAppear {
|
||||
// if coordinate.waypointId > 0 {
|
||||
// let waypoint = getWaypoint(id: Int64(coordinate.waypointId), context: bleManager.context)
|
||||
// name = waypoint.name ?? "Dropped Pin"
|
||||
// description = waypoint.longDescription ?? ""
|
||||
// icon = String(UnicodeScalar(Int(waypoint.icon)) ?? "📍")
|
||||
// latitude = Double(waypoint.latitudeI) / 1e7
|
||||
// longitude = Double(waypoint.longitudeI) / 1e7
|
||||
// if waypoint.expire != nil {
|
||||
// expires = true
|
||||
// expire = waypoint.expire ?? Date()
|
||||
// } else {
|
||||
// expires = false
|
||||
// }
|
||||
// if waypoint.locked > 0 {
|
||||
// locked = true
|
||||
// lockedTo = waypoint.locked
|
||||
// }
|
||||
// } else {
|
||||
// name = ""
|
||||
// description = ""
|
||||
// locked = false
|
||||
// expires = false
|
||||
// expire = Date.now.addingTimeInterval(60 * 480)
|
||||
// icon = "📍"
|
||||
// latitude = coordinate.coordinate?.latitude ?? 0
|
||||
// longitude = coordinate.coordinate?.longitude ?? 0
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
|
|
|||
|
|
@ -302,9 +302,13 @@ struct UserList: View {
|
|||
let loraPredicate = NSPredicate(format: "userNode.viaMqtt == NO")
|
||||
predicates.append(loraPredicate)
|
||||
} else {
|
||||
let mqttPredicate = NSPredicate(format: "userNode.viaMqtt == YES")
|
||||
let mqttPredicate = NSPredicate(format: "userNode.viaMqtt == YES AND userNode.hopsAway == 0")
|
||||
predicates.append(mqttPredicate)
|
||||
}
|
||||
} else {
|
||||
/// Only show mqtt nodes that can be contacted (zero hops) on the default key
|
||||
// let bothPredicate = NSPredicate(format: "userNode.viaMqtt == YES AND userNode.hopsAway == 0 OR userNode.viaMqtt == NO")
|
||||
// predicates.append(bothPredicate)
|
||||
}
|
||||
/// Roles
|
||||
if roleFilter && deviceRoles.count > 0 {
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ struct NodeMapSwiftUI: View {
|
|||
@Namespace var mapScope
|
||||
@State var mapStyle: MapStyle = MapStyle.hybrid(elevation: .flat, pointsOfInterest: .all, showsTraffic: true)
|
||||
@State var position = MapCameraPosition.automatic
|
||||
@State var distance = 0.0
|
||||
@State var distance = 10000.0
|
||||
@State var scene: MKLookAroundScene?
|
||||
@State var isLookingAround = false
|
||||
@State var isShowingAltitude = false
|
||||
|
|
|
|||
|
|
@ -156,39 +156,36 @@ struct NodeListItem: View {
|
|||
Image(systemName: "scroll")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.font(.callout)
|
||||
.frame(width: 30)
|
||||
Text("Logs:")
|
||||
.foregroundColor(.gray)
|
||||
.font(UIDevice.current.userInterfaceIdiom == .phone ? .callout : .caption)
|
||||
.font(UIDevice.current.userInterfaceIdiom == .phone ? .callout : .caption2)
|
||||
.allowsTightening(true)
|
||||
if node.hasDeviceMetrics {
|
||||
Image(systemName: "flipphone")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.font(.callout)
|
||||
.frame(width: 30)
|
||||
}
|
||||
if node.hasPositions {
|
||||
Image(systemName: "mappin.and.ellipse")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.font(.callout)
|
||||
.frame(width: 30)
|
||||
|
||||
}
|
||||
if node.hasEnvironmentMetrics {
|
||||
Image(systemName: "cloud.sun.rain")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.font(.callout)
|
||||
.frame(width: 30)
|
||||
|
||||
}
|
||||
if node.hasDetectionSensorMetrics {
|
||||
Image(systemName: "sensor")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.font(.callout)
|
||||
.frame(width: 30)
|
||||
}
|
||||
if node.hasTraceRoutes {
|
||||
Image(systemName: "signpost.right.and.left")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.font(.callout)
|
||||
.frame(width: 30)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,8 +37,22 @@ struct TraceRouteLog: View {
|
|||
VStack {
|
||||
List(node.traceRoutes?.reversed() as? [TraceRouteEntity] ?? [], id: \.self, selection: $selectedRoute) { route in
|
||||
Label {
|
||||
Text("\(route.time?.formatted() ?? "unknown".localized) - \(route.response ? (route.hops?.count == 0 && route.response ? "Direct" : "\(route.hops?.count ?? 0) \(route.hops?.count ?? 0 == 1 ? "Hop": "Hops")") : (route.sent ? "No Response" : "Not Sent"))")
|
||||
.font(.callout)
|
||||
if route.response && route.hopsTowards == 0 {
|
||||
Text("\(route.time?.formatted() ?? "unknown".localized) - Direct")
|
||||
.font(.caption)
|
||||
} else if route.response && route.hopsTowards == 1 {
|
||||
Text("\(route.time?.formatted() ?? "unknown".localized) - 1 Hop")
|
||||
.font(.caption)
|
||||
} else if route.response {
|
||||
Text("\(route.time?.formatted() ?? "unknown".localized) - \(route.hopsTowards) Hops Towards \(route.hopsBack) Hops Back")
|
||||
.font(.caption)
|
||||
} else if route.sent {
|
||||
Text("\(route.time?.formatted() ?? "unknown".localized) - No Response")
|
||||
.font(.caption)
|
||||
} else {
|
||||
Text("\(route.time?.formatted() ?? "unknown".localized) - Not Sent")
|
||||
.font(.caption)
|
||||
}
|
||||
} icon: {
|
||||
Image(systemName: route.response ? (route.hops?.count == 0 && route.response ? "person.line.dotted.person" : "point.3.connected.trianglepath.dotted") : "person.slash")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
|
|
@ -62,7 +76,16 @@ struct TraceRouteLog: View {
|
|||
Divider()
|
||||
ScrollView {
|
||||
if selectedRoute != nil {
|
||||
if selectedRoute?.response ?? false && selectedRoute?.hops?.count ?? 0 > 0 {
|
||||
|
||||
if selectedRoute?.response ?? false && selectedRoute?.hopsTowards ?? 0 == 0 {
|
||||
Label {
|
||||
Text("Trace route received directly by \(selectedRoute?.node?.user?.longName ?? "unknown".localized) with a SNR of \(String(format: "%.2f", selectedRoute?.node?.snr ?? 0.0)) dB")
|
||||
} icon: {
|
||||
Image(systemName: "signpost.right.and.left")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
}
|
||||
.font(.title3)
|
||||
} else if selectedRoute?.response ?? false && selectedRoute?.hopsTowards ?? 0 > 0 {
|
||||
Label {
|
||||
Text("Route: \(selectedRoute?.routeText ?? "unknown".localized)")
|
||||
} icon: {
|
||||
|
|
@ -77,14 +100,6 @@ struct TraceRouteLog: View {
|
|||
.symbolRenderingMode(.hierarchical)
|
||||
}
|
||||
.font(.title3)
|
||||
} else if selectedRoute?.response ?? false {
|
||||
Label {
|
||||
Text("Trace route received directly by \(selectedRoute?.node?.user?.longName ?? "unknown".localized) with a SNR of \(String(format: "%.2f", selectedRoute?.node?.snr ?? 0.0)) dB")
|
||||
} icon: {
|
||||
Image(systemName: "signpost.right.and.left")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
}
|
||||
.font(.title3)
|
||||
} else if !(selectedRoute?.sent ?? true) {
|
||||
Label {
|
||||
VStack {
|
||||
|
|
@ -116,7 +131,7 @@ struct TraceRouteLog: View {
|
|||
.symbolRenderingMode(.hierarchical)
|
||||
}
|
||||
}
|
||||
if selectedRoute?.hops?.count ?? 0 >= 3 {
|
||||
if false {//selectedRoute?.hops?.count ?? 0 >= 3 {
|
||||
HStack(alignment: .center) {
|
||||
GeometryReader { geometry in
|
||||
let size = ((geometry.size.width >= geometry.size.height ? geometry.size.height : geometry.size.width) / 2) - (idiom == .phone ? 45 : 85)
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ struct DetectionSensorConfig: View {
|
|||
@State var enabled = false
|
||||
@State var sendBell: Bool = false
|
||||
@State var name: String = ""
|
||||
@State var detectionTriggeredHigh: Bool = true
|
||||
@State var triggerType = 0
|
||||
@State var usePullup: Bool = false
|
||||
@State var minimumBroadcastSecs = 0
|
||||
@State var stateBroadcastSecs = 0
|
||||
|
|
@ -116,11 +116,13 @@ struct DetectionSensorConfig: View {
|
|||
}
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
|
||||
Toggle(isOn: $detectionTriggeredHigh) {
|
||||
Label("Detection trigger High", systemImage: "dial.high")
|
||||
Text("Whether or not the GPIO pin state detection is triggered on HIGH (1) or LOW (0)")
|
||||
Picker("TriggerType", selection: $triggerType) {
|
||||
ForEach(TriggerTypes.allCases) { tt in
|
||||
Text(tt.name).tag(tt.rawValue)
|
||||
}
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
.pickerStyle(DefaultPickerStyle())
|
||||
.listRowSeparator(.hidden)
|
||||
|
||||
Toggle(isOn: $usePullup) {
|
||||
Label("Uses pullup resistor", systemImage: "arrow.up.to.line")
|
||||
|
|
@ -166,7 +168,7 @@ struct DetectionSensorConfig: View {
|
|||
dsc.sendBell = self.sendBell
|
||||
dsc.name = self.name
|
||||
dsc.monitorPin = UInt32(self.monitorPin)
|
||||
dsc.detectionTriggeredHigh = self.detectionTriggeredHigh
|
||||
dsc.detectionTriggerType = TriggerTypes(rawValue: triggerType)!.protoEnumValue()
|
||||
dsc.usePullup = self.usePullup
|
||||
dsc.minimumBroadcastSecs = UInt32(self.minimumBroadcastSecs)
|
||||
dsc.stateBroadcastSecs = UInt32(self.stateBroadcastSecs)
|
||||
|
|
@ -216,8 +218,8 @@ struct DetectionSensorConfig: View {
|
|||
.onChange(of: sendBell) { _, newSendBell in
|
||||
if newSendBell != node?.detectionSensorConfig?.sendBell { hasChanges = true }
|
||||
}
|
||||
.onChange(of: detectionTriggeredHigh) { _, newDetectionTriggeredHigh in
|
||||
if newDetectionTriggeredHigh != node?.detectionSensorConfig?.detectionTriggeredHigh { hasChanges = true }
|
||||
.onChange(of: triggerType) { _, newTriggerType in
|
||||
if newTriggerType != node?.detectionSensorConfig?.triggerType ?? 0 { hasChanges = true }
|
||||
}
|
||||
.onChange(of: usePullup) { _, newUsePullup in
|
||||
if newUsePullup != node?.detectionSensorConfig?.usePullup { hasChanges = true }
|
||||
|
|
@ -244,7 +246,7 @@ struct DetectionSensorConfig: View {
|
|||
self.name = (node?.detectionSensorConfig?.name ?? "")
|
||||
self.monitorPin = Int(node?.detectionSensorConfig?.monitorPin ?? 0)
|
||||
self.usePullup = (node?.detectionSensorConfig?.usePullup ?? false)
|
||||
self.detectionTriggeredHigh = (node?.detectionSensorConfig?.detectionTriggeredHigh ?? true)
|
||||
self.triggerType = Int(node?.detectionSensorConfig?.triggerType ?? 0)
|
||||
self.minimumBroadcastSecs = Int(node?.detectionSensorConfig?.minimumBroadcastSecs ?? 45)
|
||||
self.stateBroadcastSecs = Int(node?.detectionSensorConfig?.stateBroadcastSecs ?? 0)
|
||||
|
||||
|
|
|
|||
|
|
@ -26,7 +26,11 @@ struct SecurityConfig: View {
|
|||
@State var privateKey = ""
|
||||
@State var hasValidPrivateKey: Bool = false
|
||||
@State var adminKey = ""
|
||||
@State var adminKey2 = ""
|
||||
@State var adminKey3 = ""
|
||||
@State var hasValidAdminKey: Bool = true
|
||||
@State var hasValidAdminKey2: Bool = true
|
||||
@State var hasValidAdminKey3: Bool = true
|
||||
@State var isManaged = false
|
||||
@State var serialEnabled = false
|
||||
@State var debugLogApiEnabled = false
|
||||
|
|
@ -49,8 +53,7 @@ struct SecurityConfig: View {
|
|||
Text("Sent out to other nodes on the mesh to allow them to compute a shared secret key.")
|
||||
.foregroundStyle(.secondary)
|
||||
.font(idiom == .phone ? .caption : .callout)
|
||||
}
|
||||
VStack(alignment: .leading) {
|
||||
Divider()
|
||||
Label("Private Key", systemImage: "key.fill")
|
||||
SecureInput("Private Key", text: $privateKey, isValid: $hasValidPrivateKey)
|
||||
.background(
|
||||
|
|
@ -60,11 +63,34 @@ struct SecurityConfig: View {
|
|||
Text("Used to create a shared key with a remote device.")
|
||||
.foregroundStyle(.secondary)
|
||||
.font(idiom == .phone ? .caption : .callout)
|
||||
}
|
||||
VStack(alignment: .leading) {
|
||||
Label("Admin Key", systemImage: "key.viewfinder")
|
||||
SecureInput("Admin Key", text: $adminKey, isValid: $hasValidAdminKey)
|
||||
Text("The public key authorized to send admin messages to this node.")
|
||||
Divider()
|
||||
Label("Primary Admin Key", systemImage: "key.viewfinder")
|
||||
SecureInput("Primary Admin Key", text: $adminKey, isValid: $hasValidAdminKey)
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 10.0)
|
||||
.stroke(hasValidAdminKey ? Color.clear : Color.red, lineWidth: 2.0)
|
||||
)
|
||||
Text("The primary public key authorized to send admin messages to this node.")
|
||||
.foregroundStyle(.secondary)
|
||||
.font(idiom == .phone ? .caption : .callout)
|
||||
Divider()
|
||||
Label("Secondary Admin Key", systemImage: "key.viewfinder")
|
||||
SecureInput("Secondary Admin Key", text: $adminKey2, isValid: $hasValidAdminKey2)
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 10.0)
|
||||
.stroke(hasValidAdminKey2 ? Color.clear : Color.red, lineWidth: 2.0)
|
||||
)
|
||||
Text("The secondary public key authorized to send admin messages to this node.")
|
||||
.foregroundStyle(.secondary)
|
||||
.font(idiom == .phone ? .caption : .callout)
|
||||
Divider()
|
||||
Label("Tertiary Admin Key", systemImage: "key.viewfinder")
|
||||
SecureInput("Tertiary Admin Key", text: $adminKey2, isValid: $hasValidAdminKey2)
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 10.0)
|
||||
.stroke(hasValidAdminKey3 ? Color.clear : Color.red, lineWidth: 2.0)
|
||||
)
|
||||
Text("The tertiarypublic key authorized to send admin messages to this node.")
|
||||
.foregroundStyle(.secondary)
|
||||
.font(idiom == .phone ? .caption : .callout)
|
||||
}
|
||||
|
|
@ -147,6 +173,28 @@ struct SecurityConfig: View {
|
|||
}
|
||||
hasChanges = true
|
||||
}
|
||||
.onChange(of: adminKey2) { _, key in
|
||||
let tempKey = Data(base64Encoded: key) ?? Data()
|
||||
if key.isEmpty {
|
||||
hasValidAdminKey2 = true
|
||||
} else if tempKey.count == 32 {
|
||||
hasValidAdminKey2 = true
|
||||
} else {
|
||||
hasValidAdminKey2 = false
|
||||
}
|
||||
hasChanges = true
|
||||
}
|
||||
.onChange(of: adminKey3) { _, key in
|
||||
let tempKey = Data(base64Encoded: key) ?? Data()
|
||||
if key.isEmpty {
|
||||
hasValidAdminKey3 = true
|
||||
} else if tempKey.count == 32 {
|
||||
hasValidAdminKey3 = true
|
||||
} else {
|
||||
hasValidAdminKey3 = false
|
||||
}
|
||||
hasChanges = true
|
||||
}
|
||||
.onFirstAppear {
|
||||
// Need to request a DeviceConfig from the remote node before allowing changes
|
||||
if let connectedPeripheral = bleManager.connectedPeripheral, let node {
|
||||
|
|
@ -186,7 +234,7 @@ struct SecurityConfig: View {
|
|||
var config = Config.SecurityConfig()
|
||||
config.publicKey = Data(base64Encoded: publicKey) ?? Data()
|
||||
config.privateKey = Data(base64Encoded: privateKey) ?? Data()
|
||||
config.adminKey = [Data(base64Encoded: adminKey) ?? Data()]
|
||||
config.adminKey = [Data(base64Encoded: adminKey) ?? Data(), Data(base64Encoded: adminKey2) ?? Data(), Data(base64Encoded: adminKey3) ?? Data()]
|
||||
config.isManaged = isManaged
|
||||
config.serialEnabled = serialEnabled
|
||||
config.debugLogApiEnabled = debugLogApiEnabled
|
||||
|
|
@ -211,6 +259,8 @@ struct SecurityConfig: View {
|
|||
self.publicKey = node?.securityConfig?.publicKey?.base64EncodedString() ?? ""
|
||||
self.privateKey = node?.securityConfig?.privateKey?.base64EncodedString() ?? ""
|
||||
self.adminKey = node?.securityConfig?.adminKey?.base64EncodedString() ?? ""
|
||||
self.adminKey2 = node?.securityConfig?.adminKey?.base64EncodedString() ?? ""
|
||||
self.adminKey3 = node?.securityConfig?.adminKey?.base64EncodedString() ?? ""
|
||||
self.isManaged = node?.securityConfig?.isManaged ?? false
|
||||
self.serialEnabled = node?.securityConfig?.serialEnabled ?? false
|
||||
self.debugLogApiEnabled = node?.securityConfig?.debugLogApiEnabled ?? false
|
||||
|
|
|
|||
|
|
@ -324,7 +324,7 @@ public struct TAKPacket {
|
|||
|
||||
///
|
||||
/// Generic CoT detail XML
|
||||
/// May be compressed / truncated by the sender
|
||||
/// May be compressed / truncated by the sender (EUD)
|
||||
public var detail: Data {
|
||||
get {
|
||||
if case .detail(let v)? = payloadVariant {return v}
|
||||
|
|
@ -346,7 +346,7 @@ public struct TAKPacket {
|
|||
case chat(GeoChat)
|
||||
///
|
||||
/// Generic CoT detail XML
|
||||
/// May be compressed / truncated by the sender
|
||||
/// May be compressed / truncated by the sender (EUD)
|
||||
case detail(Data)
|
||||
|
||||
#if !swift(>=4.1)
|
||||
|
|
|
|||
|
|
@ -1344,6 +1344,18 @@ public struct Config {
|
|||
///
|
||||
/// Singapore 923mhz
|
||||
case sg923 // = 18
|
||||
|
||||
///
|
||||
/// Philippines 433mhz
|
||||
case ph433 // = 19
|
||||
|
||||
///
|
||||
/// Philippines 868mhz
|
||||
case ph868 // = 20
|
||||
|
||||
///
|
||||
/// Philippines 915mhz
|
||||
case ph915 // = 21
|
||||
case UNRECOGNIZED(Int)
|
||||
|
||||
public init() {
|
||||
|
|
@ -1371,6 +1383,9 @@ public struct Config {
|
|||
case 16: self = .my433
|
||||
case 17: self = .my919
|
||||
case 18: self = .sg923
|
||||
case 19: self = .ph433
|
||||
case 20: self = .ph868
|
||||
case 21: self = .ph915
|
||||
default: self = .UNRECOGNIZED(rawValue)
|
||||
}
|
||||
}
|
||||
|
|
@ -1396,6 +1411,9 @@ public struct Config {
|
|||
case .my433: return 16
|
||||
case .my919: return 17
|
||||
case .sg923: return 18
|
||||
case .ph433: return 19
|
||||
case .ph868: return 20
|
||||
case .ph915: return 21
|
||||
case .UNRECOGNIZED(let i): return i
|
||||
}
|
||||
}
|
||||
|
|
@ -1747,6 +1765,9 @@ extension Config.LoRaConfig.RegionCode: CaseIterable {
|
|||
.my433,
|
||||
.my919,
|
||||
.sg923,
|
||||
.ph433,
|
||||
.ph868,
|
||||
.ph915,
|
||||
]
|
||||
}
|
||||
|
||||
|
|
@ -2834,6 +2855,9 @@ extension Config.LoRaConfig.RegionCode: SwiftProtobuf._ProtoNameProviding {
|
|||
16: .same(proto: "MY_433"),
|
||||
17: .same(proto: "MY_919"),
|
||||
18: .same(proto: "SG_923"),
|
||||
19: .same(proto: "PH_433"),
|
||||
20: .same(proto: "PH_868"),
|
||||
21: .same(proto: "PH_915"),
|
||||
]
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -126,6 +126,10 @@ public enum HardwareModel: SwiftProtobuf.Enum {
|
|||
/// Heltec HRU-3601: https://heltec.org/project/hru-3601/
|
||||
case heltecHru3601 // = 23
|
||||
|
||||
///
|
||||
/// Heltec Wireless Bridge
|
||||
case heltecWirelessBridge // = 24
|
||||
|
||||
///
|
||||
/// B&Q Consulting Station Edition G1: https://uniteng.com/wiki/doku.php?id=meshtastic:station
|
||||
case stationG1 // = 25
|
||||
|
|
@ -197,7 +201,7 @@ public enum HardwareModel: SwiftProtobuf.Enum {
|
|||
case drDev // = 41
|
||||
|
||||
///
|
||||
/// M5 esp32 based MCU modules with enclosure, TFT and LORA Shields. All Variants (Basic, Core, Fire, Core2, Paper) https://m5stack.com/
|
||||
/// M5 esp32 based MCU modules with enclosure, TFT and LORA Shields. All Variants (Basic, Core, Fire, Core2, CoreS3, Paper) https://m5stack.com/
|
||||
case m5Stack // = 42
|
||||
|
||||
///
|
||||
|
|
@ -355,10 +359,27 @@ public enum HardwareModel: SwiftProtobuf.Enum {
|
|||
/// ^^^ short A0 to switch to I2C address 0x3C
|
||||
case rp2040FeatherRfm95 // = 76
|
||||
|
||||
/// M5 esp32 based MCU modules with enclosure, TFT and LORA Shields. All Variants (Basic, Core, Fire, Core2, Paper) https://m5stack.com/
|
||||
/// M5 esp32 based MCU modules with enclosure, TFT and LORA Shields. All Variants (Basic, Core, Fire, Core2, CoreS3, Paper) https://m5stack.com/
|
||||
case m5StackCorebasic // = 77
|
||||
case m5StackCore2 // = 78
|
||||
|
||||
/// Pico2 with Waveshare Hat, same as Pico
|
||||
case rpiPico2 // = 79
|
||||
|
||||
/// M5 esp32 based MCU modules with enclosure, TFT and LORA Shields. All Variants (Basic, Core, Fire, Core2, CoreS3, Paper) https://m5stack.com/
|
||||
case m5StackCores3 // = 80
|
||||
|
||||
/// Seeed XIAO S3 DK
|
||||
case seeedXiaoS3 // = 81
|
||||
|
||||
///
|
||||
/// Nordic nRF52840+Semtech SX1262 LoRa BLE Combo Module. nRF52840+SX1262 MS24SF1
|
||||
case ms24Sf1 // = 82
|
||||
|
||||
///
|
||||
/// Lilygo TLora-C6 with the new ESP32-C6 MCU
|
||||
case tloraC6 // = 83
|
||||
|
||||
///
|
||||
/// ------------------------------------------------------------------------------------------------------------------------------------------
|
||||
/// Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits.
|
||||
|
|
@ -396,6 +417,7 @@ public enum HardwareModel: SwiftProtobuf.Enum {
|
|||
case 21: self = .wioWm1110
|
||||
case 22: self = .rak2560
|
||||
case 23: self = .heltecHru3601
|
||||
case 24: self = .heltecWirelessBridge
|
||||
case 25: self = .stationG1
|
||||
case 26: self = .rak11310
|
||||
case 27: self = .senseloraRp2040
|
||||
|
|
@ -450,6 +472,11 @@ public enum HardwareModel: SwiftProtobuf.Enum {
|
|||
case 76: self = .rp2040FeatherRfm95
|
||||
case 77: self = .m5StackCorebasic
|
||||
case 78: self = .m5StackCore2
|
||||
case 79: self = .rpiPico2
|
||||
case 80: self = .m5StackCores3
|
||||
case 81: self = .seeedXiaoS3
|
||||
case 82: self = .ms24Sf1
|
||||
case 83: self = .tloraC6
|
||||
case 255: self = .privateHw
|
||||
default: self = .UNRECOGNIZED(rawValue)
|
||||
}
|
||||
|
|
@ -481,6 +508,7 @@ public enum HardwareModel: SwiftProtobuf.Enum {
|
|||
case .wioWm1110: return 21
|
||||
case .rak2560: return 22
|
||||
case .heltecHru3601: return 23
|
||||
case .heltecWirelessBridge: return 24
|
||||
case .stationG1: return 25
|
||||
case .rak11310: return 26
|
||||
case .senseloraRp2040: return 27
|
||||
|
|
@ -535,6 +563,11 @@ public enum HardwareModel: SwiftProtobuf.Enum {
|
|||
case .rp2040FeatherRfm95: return 76
|
||||
case .m5StackCorebasic: return 77
|
||||
case .m5StackCore2: return 78
|
||||
case .rpiPico2: return 79
|
||||
case .m5StackCores3: return 80
|
||||
case .seeedXiaoS3: return 81
|
||||
case .ms24Sf1: return 82
|
||||
case .tloraC6: return 83
|
||||
case .privateHw: return 255
|
||||
case .UNRECOGNIZED(let i): return i
|
||||
}
|
||||
|
|
@ -571,6 +604,7 @@ extension HardwareModel: CaseIterable {
|
|||
.wioWm1110,
|
||||
.rak2560,
|
||||
.heltecHru3601,
|
||||
.heltecWirelessBridge,
|
||||
.stationG1,
|
||||
.rak11310,
|
||||
.senseloraRp2040,
|
||||
|
|
@ -625,6 +659,11 @@ extension HardwareModel: CaseIterable {
|
|||
.rp2040FeatherRfm95,
|
||||
.m5StackCorebasic,
|
||||
.m5StackCore2,
|
||||
.rpiPico2,
|
||||
.m5StackCores3,
|
||||
.seeedXiaoS3,
|
||||
.ms24Sf1,
|
||||
.tloraC6,
|
||||
.privateHw,
|
||||
]
|
||||
}
|
||||
|
|
@ -3262,6 +3301,7 @@ extension HardwareModel: SwiftProtobuf._ProtoNameProviding {
|
|||
21: .same(proto: "WIO_WM1110"),
|
||||
22: .same(proto: "RAK2560"),
|
||||
23: .same(proto: "HELTEC_HRU_3601"),
|
||||
24: .same(proto: "HELTEC_WIRELESS_BRIDGE"),
|
||||
25: .same(proto: "STATION_G1"),
|
||||
26: .same(proto: "RAK11310"),
|
||||
27: .same(proto: "SENSELORA_RP2040"),
|
||||
|
|
@ -3316,6 +3356,11 @@ extension HardwareModel: SwiftProtobuf._ProtoNameProviding {
|
|||
76: .same(proto: "RP2040_FEATHER_RFM95"),
|
||||
77: .same(proto: "M5STACK_COREBASIC"),
|
||||
78: .same(proto: "M5STACK_CORE2"),
|
||||
79: .same(proto: "RPI_PICO2"),
|
||||
80: .same(proto: "M5STACK_CORES3"),
|
||||
81: .same(proto: "SEEED_XIAO_S3"),
|
||||
82: .same(proto: "MS24SF1"),
|
||||
83: .same(proto: "TLORA_C6"),
|
||||
255: .same(proto: "PRIVATE_HW"),
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -475,13 +475,15 @@ public struct ModuleConfig {
|
|||
public var enabled: Bool = false
|
||||
|
||||
///
|
||||
/// Interval in seconds of how often we can send a message to the mesh when a state change is detected
|
||||
/// Interval in seconds of how often we can send a message to the mesh when a
|
||||
/// trigger event is detected
|
||||
public var minimumBroadcastSecs: UInt32 = 0
|
||||
|
||||
///
|
||||
/// Interval in seconds of how often we should send a message to the mesh with the current state regardless of changes
|
||||
/// When set to 0, only state changes will be broadcasted
|
||||
/// Works as a sort of status heartbeat for peace of mind
|
||||
/// Interval in seconds of how often we should send a message to the mesh
|
||||
/// with the current state regardless of trigger events When set to 0, only
|
||||
/// trigger events will be broadcasted Works as a sort of status heartbeat
|
||||
/// for peace of mind
|
||||
public var stateBroadcastSecs: UInt32 = 0
|
||||
|
||||
///
|
||||
|
|
@ -500,9 +502,8 @@ public struct ModuleConfig {
|
|||
public var monitorPin: UInt32 = 0
|
||||
|
||||
///
|
||||
/// Whether or not the GPIO pin state detection is triggered on HIGH (1)
|
||||
/// Otherwise LOW (0)
|
||||
public var detectionTriggeredHigh: Bool = false
|
||||
/// The type of trigger event to be used
|
||||
public var detectionTriggerType: ModuleConfig.DetectionSensorConfig.TriggerType = .logicLow
|
||||
|
||||
///
|
||||
/// Whether or not use INPUT_PULLUP mode for GPIO pin
|
||||
|
|
@ -511,6 +512,60 @@ public struct ModuleConfig {
|
|||
|
||||
public var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
public enum TriggerType: SwiftProtobuf.Enum {
|
||||
public typealias RawValue = Int
|
||||
|
||||
/// Event is triggered if pin is low
|
||||
case logicLow // = 0
|
||||
|
||||
/// Event is triggered if pin is high
|
||||
case logicHigh // = 1
|
||||
|
||||
/// Event is triggered when pin goes high to low
|
||||
case fallingEdge // = 2
|
||||
|
||||
/// Event is triggered when pin goes low to high
|
||||
case risingEdge // = 3
|
||||
|
||||
/// Event is triggered on every pin state change, low is considered to be
|
||||
/// "active"
|
||||
case eitherEdgeActiveLow // = 4
|
||||
|
||||
/// Event is triggered on every pin state change, high is considered to be
|
||||
/// "active"
|
||||
case eitherEdgeActiveHigh // = 5
|
||||
case UNRECOGNIZED(Int)
|
||||
|
||||
public init() {
|
||||
self = .logicLow
|
||||
}
|
||||
|
||||
public init?(rawValue: Int) {
|
||||
switch rawValue {
|
||||
case 0: self = .logicLow
|
||||
case 1: self = .logicHigh
|
||||
case 2: self = .fallingEdge
|
||||
case 3: self = .risingEdge
|
||||
case 4: self = .eitherEdgeActiveLow
|
||||
case 5: self = .eitherEdgeActiveHigh
|
||||
default: self = .UNRECOGNIZED(rawValue)
|
||||
}
|
||||
}
|
||||
|
||||
public var rawValue: Int {
|
||||
switch self {
|
||||
case .logicLow: return 0
|
||||
case .logicHigh: return 1
|
||||
case .fallingEdge: return 2
|
||||
case .risingEdge: return 3
|
||||
case .eitherEdgeActiveLow: return 4
|
||||
case .eitherEdgeActiveHigh: return 5
|
||||
case .UNRECOGNIZED(let i): return i
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public init() {}
|
||||
}
|
||||
|
||||
|
|
@ -980,20 +1035,32 @@ public struct ModuleConfig {
|
|||
public var airQualityInterval: UInt32 = 0
|
||||
|
||||
///
|
||||
/// Interval in seconds of how often we should try to send our
|
||||
/// air quality metrics to the mesh
|
||||
/// Enable/disable Power metrics
|
||||
public var powerMeasurementEnabled: Bool = false
|
||||
|
||||
///
|
||||
/// Interval in seconds of how often we should try to send our
|
||||
/// air quality metrics to the mesh
|
||||
/// power metrics to the mesh
|
||||
public var powerUpdateInterval: UInt32 = 0
|
||||
|
||||
///
|
||||
/// Interval in seconds of how often we should try to send our
|
||||
/// air quality metrics to the mesh
|
||||
/// Enable/Disable the power measurement module on-device display
|
||||
public var powerScreenEnabled: Bool = false
|
||||
|
||||
///
|
||||
/// Preferences for the (Health) Telemetry Module
|
||||
/// Enable/Disable the telemetry measurement module measurement collection
|
||||
public var healthMeasurementEnabled: Bool = false
|
||||
|
||||
///
|
||||
/// Interval in seconds of how often we should try to send our
|
||||
/// health metrics to the mesh
|
||||
public var healthUpdateInterval: UInt32 = 0
|
||||
|
||||
///
|
||||
/// Enable/Disable the health telemetry module on-device display
|
||||
public var healthScreenEnabled: Bool = false
|
||||
|
||||
public var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
public init() {}
|
||||
|
|
@ -1167,6 +1234,18 @@ public struct ModuleConfig {
|
|||
|
||||
#if swift(>=4.2)
|
||||
|
||||
extension ModuleConfig.DetectionSensorConfig.TriggerType: CaseIterable {
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
public static let allCases: [ModuleConfig.DetectionSensorConfig.TriggerType] = [
|
||||
.logicLow,
|
||||
.logicHigh,
|
||||
.fallingEdge,
|
||||
.risingEdge,
|
||||
.eitherEdgeActiveLow,
|
||||
.eitherEdgeActiveHigh,
|
||||
]
|
||||
}
|
||||
|
||||
extension ModuleConfig.AudioConfig.Audio_Baud: CaseIterable {
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
public static let allCases: [ModuleConfig.AudioConfig.Audio_Baud] = [
|
||||
|
|
@ -1266,6 +1345,7 @@ extension ModuleConfig.MapReportSettings: @unchecked Sendable {}
|
|||
extension ModuleConfig.RemoteHardwareConfig: @unchecked Sendable {}
|
||||
extension ModuleConfig.NeighborInfoConfig: @unchecked Sendable {}
|
||||
extension ModuleConfig.DetectionSensorConfig: @unchecked Sendable {}
|
||||
extension ModuleConfig.DetectionSensorConfig.TriggerType: @unchecked Sendable {}
|
||||
extension ModuleConfig.AudioConfig: @unchecked Sendable {}
|
||||
extension ModuleConfig.AudioConfig.Audio_Baud: @unchecked Sendable {}
|
||||
extension ModuleConfig.PaxcounterConfig: @unchecked Sendable {}
|
||||
|
|
@ -1787,7 +1867,7 @@ extension ModuleConfig.DetectionSensorConfig: SwiftProtobuf.Message, SwiftProtob
|
|||
4: .standard(proto: "send_bell"),
|
||||
5: .same(proto: "name"),
|
||||
6: .standard(proto: "monitor_pin"),
|
||||
7: .standard(proto: "detection_triggered_high"),
|
||||
7: .standard(proto: "detection_trigger_type"),
|
||||
8: .standard(proto: "use_pullup"),
|
||||
]
|
||||
|
||||
|
|
@ -1803,7 +1883,7 @@ extension ModuleConfig.DetectionSensorConfig: SwiftProtobuf.Message, SwiftProtob
|
|||
case 4: try { try decoder.decodeSingularBoolField(value: &self.sendBell) }()
|
||||
case 5: try { try decoder.decodeSingularStringField(value: &self.name) }()
|
||||
case 6: try { try decoder.decodeSingularUInt32Field(value: &self.monitorPin) }()
|
||||
case 7: try { try decoder.decodeSingularBoolField(value: &self.detectionTriggeredHigh) }()
|
||||
case 7: try { try decoder.decodeSingularEnumField(value: &self.detectionTriggerType) }()
|
||||
case 8: try { try decoder.decodeSingularBoolField(value: &self.usePullup) }()
|
||||
default: break
|
||||
}
|
||||
|
|
@ -1829,8 +1909,8 @@ extension ModuleConfig.DetectionSensorConfig: SwiftProtobuf.Message, SwiftProtob
|
|||
if self.monitorPin != 0 {
|
||||
try visitor.visitSingularUInt32Field(value: self.monitorPin, fieldNumber: 6)
|
||||
}
|
||||
if self.detectionTriggeredHigh != false {
|
||||
try visitor.visitSingularBoolField(value: self.detectionTriggeredHigh, fieldNumber: 7)
|
||||
if self.detectionTriggerType != .logicLow {
|
||||
try visitor.visitSingularEnumField(value: self.detectionTriggerType, fieldNumber: 7)
|
||||
}
|
||||
if self.usePullup != false {
|
||||
try visitor.visitSingularBoolField(value: self.usePullup, fieldNumber: 8)
|
||||
|
|
@ -1845,13 +1925,24 @@ extension ModuleConfig.DetectionSensorConfig: SwiftProtobuf.Message, SwiftProtob
|
|||
if lhs.sendBell != rhs.sendBell {return false}
|
||||
if lhs.name != rhs.name {return false}
|
||||
if lhs.monitorPin != rhs.monitorPin {return false}
|
||||
if lhs.detectionTriggeredHigh != rhs.detectionTriggeredHigh {return false}
|
||||
if lhs.detectionTriggerType != rhs.detectionTriggerType {return false}
|
||||
if lhs.usePullup != rhs.usePullup {return false}
|
||||
if lhs.unknownFields != rhs.unknownFields {return false}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
extension ModuleConfig.DetectionSensorConfig.TriggerType: SwiftProtobuf._ProtoNameProviding {
|
||||
public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
||||
0: .same(proto: "LOGIC_LOW"),
|
||||
1: .same(proto: "LOGIC_HIGH"),
|
||||
2: .same(proto: "FALLING_EDGE"),
|
||||
3: .same(proto: "RISING_EDGE"),
|
||||
4: .same(proto: "EITHER_EDGE_ACTIVE_LOW"),
|
||||
5: .same(proto: "EITHER_EDGE_ACTIVE_HIGH"),
|
||||
]
|
||||
}
|
||||
|
||||
extension ModuleConfig.AudioConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
||||
public static let protoMessageName: String = ModuleConfig.protoMessageName + ".AudioConfig"
|
||||
public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
||||
|
|
@ -2326,6 +2417,9 @@ extension ModuleConfig.TelemetryConfig: SwiftProtobuf.Message, SwiftProtobuf._Me
|
|||
8: .standard(proto: "power_measurement_enabled"),
|
||||
9: .standard(proto: "power_update_interval"),
|
||||
10: .standard(proto: "power_screen_enabled"),
|
||||
11: .standard(proto: "health_measurement_enabled"),
|
||||
12: .standard(proto: "health_update_interval"),
|
||||
13: .standard(proto: "health_screen_enabled"),
|
||||
]
|
||||
|
||||
public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||
|
|
@ -2344,6 +2438,9 @@ extension ModuleConfig.TelemetryConfig: SwiftProtobuf.Message, SwiftProtobuf._Me
|
|||
case 8: try { try decoder.decodeSingularBoolField(value: &self.powerMeasurementEnabled) }()
|
||||
case 9: try { try decoder.decodeSingularUInt32Field(value: &self.powerUpdateInterval) }()
|
||||
case 10: try { try decoder.decodeSingularBoolField(value: &self.powerScreenEnabled) }()
|
||||
case 11: try { try decoder.decodeSingularBoolField(value: &self.healthMeasurementEnabled) }()
|
||||
case 12: try { try decoder.decodeSingularUInt32Field(value: &self.healthUpdateInterval) }()
|
||||
case 13: try { try decoder.decodeSingularBoolField(value: &self.healthScreenEnabled) }()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
|
@ -2380,6 +2477,15 @@ extension ModuleConfig.TelemetryConfig: SwiftProtobuf.Message, SwiftProtobuf._Me
|
|||
if self.powerScreenEnabled != false {
|
||||
try visitor.visitSingularBoolField(value: self.powerScreenEnabled, fieldNumber: 10)
|
||||
}
|
||||
if self.healthMeasurementEnabled != false {
|
||||
try visitor.visitSingularBoolField(value: self.healthMeasurementEnabled, fieldNumber: 11)
|
||||
}
|
||||
if self.healthUpdateInterval != 0 {
|
||||
try visitor.visitSingularUInt32Field(value: self.healthUpdateInterval, fieldNumber: 12)
|
||||
}
|
||||
if self.healthScreenEnabled != false {
|
||||
try visitor.visitSingularBoolField(value: self.healthScreenEnabled, fieldNumber: 13)
|
||||
}
|
||||
try unknownFields.traverse(visitor: &visitor)
|
||||
}
|
||||
|
||||
|
|
@ -2394,6 +2500,9 @@ extension ModuleConfig.TelemetryConfig: SwiftProtobuf.Message, SwiftProtobuf._Me
|
|||
if lhs.powerMeasurementEnabled != rhs.powerMeasurementEnabled {return false}
|
||||
if lhs.powerUpdateInterval != rhs.powerUpdateInterval {return false}
|
||||
if lhs.powerScreenEnabled != rhs.powerScreenEnabled {return false}
|
||||
if lhs.healthMeasurementEnabled != rhs.healthMeasurementEnabled {return false}
|
||||
if lhs.healthUpdateInterval != rhs.healthUpdateInterval {return false}
|
||||
if lhs.healthScreenEnabled != rhs.healthScreenEnabled {return false}
|
||||
if lhs.unknownFields != rhs.unknownFields {return false}
|
||||
return true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -144,6 +144,14 @@ public enum TelemetrySensorType: SwiftProtobuf.Enum {
|
|||
///
|
||||
/// Custom I2C sensor implementation based on https://github.com/meshtastic/i2c-sensor
|
||||
case customSensor // = 29
|
||||
|
||||
///
|
||||
/// MAX30102 Pulse Oximeter and Heart-Rate Sensor
|
||||
case max30102 // = 30
|
||||
|
||||
///
|
||||
/// MLX90614 non-contact IR temperature sensor.
|
||||
case mlx90614 // = 31
|
||||
case UNRECOGNIZED(Int)
|
||||
|
||||
public init() {
|
||||
|
|
@ -182,6 +190,8 @@ public enum TelemetrySensorType: SwiftProtobuf.Enum {
|
|||
case 27: self = .icm20948
|
||||
case 28: self = .max17048
|
||||
case 29: self = .customSensor
|
||||
case 30: self = .max30102
|
||||
case 31: self = .mlx90614
|
||||
default: self = .UNRECOGNIZED(rawValue)
|
||||
}
|
||||
}
|
||||
|
|
@ -218,6 +228,8 @@ public enum TelemetrySensorType: SwiftProtobuf.Enum {
|
|||
case .icm20948: return 27
|
||||
case .max17048: return 28
|
||||
case .customSensor: return 29
|
||||
case .max30102: return 30
|
||||
case .mlx90614: return 31
|
||||
case .UNRECOGNIZED(let i): return i
|
||||
}
|
||||
}
|
||||
|
|
@ -259,6 +271,8 @@ extension TelemetrySensorType: CaseIterable {
|
|||
.icm20948,
|
||||
.max17048,
|
||||
.customSensor,
|
||||
.max30102,
|
||||
.mlx90614,
|
||||
]
|
||||
}
|
||||
|
||||
|
|
@ -806,7 +820,7 @@ public struct LocalStats {
|
|||
public var numPacketsTx: UInt32 = 0
|
||||
|
||||
///
|
||||
/// Number of packets received good
|
||||
/// Number of packets received (both good and bad)
|
||||
public var numPacketsRx: UInt32 = 0
|
||||
|
||||
///
|
||||
|
|
@ -821,11 +835,74 @@ public struct LocalStats {
|
|||
/// Number of nodes total
|
||||
public var numTotalNodes: UInt32 = 0
|
||||
|
||||
///
|
||||
/// Number of received packets that were duplicates (due to multiple nodes relaying).
|
||||
/// If this number is high, there are nodes in the mesh relaying packets when it's unnecessary, for example due to the ROUTER/REPEATER role.
|
||||
public var numRxDupe: UInt32 = 0
|
||||
|
||||
///
|
||||
/// Number of packets we transmitted that were a relay for others (not originating from ourselves).
|
||||
public var numTxRelay: UInt32 = 0
|
||||
|
||||
///
|
||||
/// Number of times we canceled a packet to be relayed, because someone else did it before us.
|
||||
/// This will always be zero for ROUTERs/REPEATERs. If this number is high, some other node(s) is/are relaying faster than you.
|
||||
public var numTxRelayCanceled: UInt32 = 0
|
||||
|
||||
public var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
public init() {}
|
||||
}
|
||||
|
||||
///
|
||||
/// Health telemetry metrics
|
||||
public struct HealthMetrics {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
// methods supported on all messages.
|
||||
|
||||
///
|
||||
/// Heart rate (beats per minute)
|
||||
public var heartBpm: UInt32 {
|
||||
get {return _heartBpm ?? 0}
|
||||
set {_heartBpm = newValue}
|
||||
}
|
||||
/// Returns true if `heartBpm` has been explicitly set.
|
||||
public var hasHeartBpm: Bool {return self._heartBpm != nil}
|
||||
/// Clears the value of `heartBpm`. Subsequent reads from it will return its default value.
|
||||
public mutating func clearHeartBpm() {self._heartBpm = nil}
|
||||
|
||||
///
|
||||
/// SpO2 (blood oxygen saturation) level
|
||||
public var spO2: UInt32 {
|
||||
get {return _spO2 ?? 0}
|
||||
set {_spO2 = newValue}
|
||||
}
|
||||
/// Returns true if `spO2` has been explicitly set.
|
||||
public var hasSpO2: Bool {return self._spO2 != nil}
|
||||
/// Clears the value of `spO2`. Subsequent reads from it will return its default value.
|
||||
public mutating func clearSpO2() {self._spO2 = nil}
|
||||
|
||||
///
|
||||
/// Body temperature in degrees Celsius
|
||||
public var temperature: Float {
|
||||
get {return _temperature ?? 0}
|
||||
set {_temperature = newValue}
|
||||
}
|
||||
/// Returns true if `temperature` has been explicitly set.
|
||||
public var hasTemperature: Bool {return self._temperature != nil}
|
||||
/// Clears the value of `temperature`. Subsequent reads from it will return its default value.
|
||||
public mutating func clearTemperature() {self._temperature = nil}
|
||||
|
||||
public var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
public init() {}
|
||||
|
||||
fileprivate var _heartBpm: UInt32? = nil
|
||||
fileprivate var _spO2: UInt32? = nil
|
||||
fileprivate var _temperature: Float? = nil
|
||||
}
|
||||
|
||||
///
|
||||
/// Types of Measurements the telemetry module is equipped to handle
|
||||
public struct Telemetry {
|
||||
|
|
@ -889,6 +966,16 @@ public struct Telemetry {
|
|||
set {variant = .localStats(newValue)}
|
||||
}
|
||||
|
||||
///
|
||||
/// Health telemetry metrics
|
||||
public var healthMetrics: HealthMetrics {
|
||||
get {
|
||||
if case .healthMetrics(let v)? = variant {return v}
|
||||
return HealthMetrics()
|
||||
}
|
||||
set {variant = .healthMetrics(newValue)}
|
||||
}
|
||||
|
||||
public var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
public enum OneOf_Variant: Equatable {
|
||||
|
|
@ -907,6 +994,9 @@ public struct Telemetry {
|
|||
///
|
||||
/// Local device mesh statistics
|
||||
case localStats(LocalStats)
|
||||
///
|
||||
/// Health telemetry metrics
|
||||
case healthMetrics(HealthMetrics)
|
||||
|
||||
#if !swift(>=4.1)
|
||||
public static func ==(lhs: Telemetry.OneOf_Variant, rhs: Telemetry.OneOf_Variant) -> Bool {
|
||||
|
|
@ -934,6 +1024,10 @@ public struct Telemetry {
|
|||
guard case .localStats(let l) = lhs, case .localStats(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
case (.healthMetrics, .healthMetrics): return {
|
||||
guard case .healthMetrics(let l) = lhs, case .healthMetrics(let r) = rhs else { preconditionFailure() }
|
||||
return l == r
|
||||
}()
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
|
|
@ -970,6 +1064,7 @@ extension EnvironmentMetrics: @unchecked Sendable {}
|
|||
extension PowerMetrics: @unchecked Sendable {}
|
||||
extension AirQualityMetrics: @unchecked Sendable {}
|
||||
extension LocalStats: @unchecked Sendable {}
|
||||
extension HealthMetrics: @unchecked Sendable {}
|
||||
extension Telemetry: @unchecked Sendable {}
|
||||
extension Telemetry.OneOf_Variant: @unchecked Sendable {}
|
||||
extension Nau7802Config: @unchecked Sendable {}
|
||||
|
|
@ -1011,6 +1106,8 @@ extension TelemetrySensorType: SwiftProtobuf._ProtoNameProviding {
|
|||
27: .same(proto: "ICM20948"),
|
||||
28: .same(proto: "MAX17048"),
|
||||
29: .same(proto: "CUSTOM_SENSOR"),
|
||||
30: .same(proto: "MAX30102"),
|
||||
31: .same(proto: "MLX90614"),
|
||||
]
|
||||
}
|
||||
|
||||
|
|
@ -1457,6 +1554,9 @@ extension LocalStats: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio
|
|||
6: .standard(proto: "num_packets_rx_bad"),
|
||||
7: .standard(proto: "num_online_nodes"),
|
||||
8: .standard(proto: "num_total_nodes"),
|
||||
9: .standard(proto: "num_rx_dupe"),
|
||||
10: .standard(proto: "num_tx_relay"),
|
||||
11: .standard(proto: "num_tx_relay_canceled"),
|
||||
]
|
||||
|
||||
public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||
|
|
@ -1473,6 +1573,9 @@ extension LocalStats: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio
|
|||
case 6: try { try decoder.decodeSingularUInt32Field(value: &self.numPacketsRxBad) }()
|
||||
case 7: try { try decoder.decodeSingularUInt32Field(value: &self.numOnlineNodes) }()
|
||||
case 8: try { try decoder.decodeSingularUInt32Field(value: &self.numTotalNodes) }()
|
||||
case 9: try { try decoder.decodeSingularUInt32Field(value: &self.numRxDupe) }()
|
||||
case 10: try { try decoder.decodeSingularUInt32Field(value: &self.numTxRelay) }()
|
||||
case 11: try { try decoder.decodeSingularUInt32Field(value: &self.numTxRelayCanceled) }()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
|
@ -1503,6 +1606,15 @@ extension LocalStats: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio
|
|||
if self.numTotalNodes != 0 {
|
||||
try visitor.visitSingularUInt32Field(value: self.numTotalNodes, fieldNumber: 8)
|
||||
}
|
||||
if self.numRxDupe != 0 {
|
||||
try visitor.visitSingularUInt32Field(value: self.numRxDupe, fieldNumber: 9)
|
||||
}
|
||||
if self.numTxRelay != 0 {
|
||||
try visitor.visitSingularUInt32Field(value: self.numTxRelay, fieldNumber: 10)
|
||||
}
|
||||
if self.numTxRelayCanceled != 0 {
|
||||
try visitor.visitSingularUInt32Field(value: self.numTxRelayCanceled, fieldNumber: 11)
|
||||
}
|
||||
try unknownFields.traverse(visitor: &visitor)
|
||||
}
|
||||
|
||||
|
|
@ -1515,6 +1627,57 @@ extension LocalStats: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio
|
|||
if lhs.numPacketsRxBad != rhs.numPacketsRxBad {return false}
|
||||
if lhs.numOnlineNodes != rhs.numOnlineNodes {return false}
|
||||
if lhs.numTotalNodes != rhs.numTotalNodes {return false}
|
||||
if lhs.numRxDupe != rhs.numRxDupe {return false}
|
||||
if lhs.numTxRelay != rhs.numTxRelay {return false}
|
||||
if lhs.numTxRelayCanceled != rhs.numTxRelayCanceled {return false}
|
||||
if lhs.unknownFields != rhs.unknownFields {return false}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
extension HealthMetrics: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
||||
public static let protoMessageName: String = _protobuf_package + ".HealthMetrics"
|
||||
public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
||||
1: .standard(proto: "heart_bpm"),
|
||||
2: .same(proto: "spO2"),
|
||||
3: .same(proto: "temperature"),
|
||||
]
|
||||
|
||||
public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||
while let fieldNumber = try decoder.nextFieldNumber() {
|
||||
// The use of inline closures is to circumvent an issue where the compiler
|
||||
// allocates stack space for every case branch when no optimizations are
|
||||
// enabled. https://github.com/apple/swift-protobuf/issues/1034
|
||||
switch fieldNumber {
|
||||
case 1: try { try decoder.decodeSingularUInt32Field(value: &self._heartBpm) }()
|
||||
case 2: try { try decoder.decodeSingularUInt32Field(value: &self._spO2) }()
|
||||
case 3: try { try decoder.decodeSingularFloatField(value: &self._temperature) }()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
|
||||
// The use of inline closures is to circumvent an issue where the compiler
|
||||
// allocates stack space for every if/case branch local when no optimizations
|
||||
// are enabled. https://github.com/apple/swift-protobuf/issues/1034 and
|
||||
// https://github.com/apple/swift-protobuf/issues/1182
|
||||
try { if let v = self._heartBpm {
|
||||
try visitor.visitSingularUInt32Field(value: v, fieldNumber: 1)
|
||||
} }()
|
||||
try { if let v = self._spO2 {
|
||||
try visitor.visitSingularUInt32Field(value: v, fieldNumber: 2)
|
||||
} }()
|
||||
try { if let v = self._temperature {
|
||||
try visitor.visitSingularFloatField(value: v, fieldNumber: 3)
|
||||
} }()
|
||||
try unknownFields.traverse(visitor: &visitor)
|
||||
}
|
||||
|
||||
public static func ==(lhs: HealthMetrics, rhs: HealthMetrics) -> Bool {
|
||||
if lhs._heartBpm != rhs._heartBpm {return false}
|
||||
if lhs._spO2 != rhs._spO2 {return false}
|
||||
if lhs._temperature != rhs._temperature {return false}
|
||||
if lhs.unknownFields != rhs.unknownFields {return false}
|
||||
return true
|
||||
}
|
||||
|
|
@ -1529,6 +1692,7 @@ extension Telemetry: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementation
|
|||
4: .standard(proto: "air_quality_metrics"),
|
||||
5: .standard(proto: "power_metrics"),
|
||||
6: .standard(proto: "local_stats"),
|
||||
7: .standard(proto: "health_metrics"),
|
||||
]
|
||||
|
||||
public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||
|
|
@ -1603,6 +1767,19 @@ extension Telemetry: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementation
|
|||
self.variant = .localStats(v)
|
||||
}
|
||||
}()
|
||||
case 7: try {
|
||||
var v: HealthMetrics?
|
||||
var hadOneofValue = false
|
||||
if let current = self.variant {
|
||||
hadOneofValue = true
|
||||
if case .healthMetrics(let m) = current {v = m}
|
||||
}
|
||||
try decoder.decodeSingularMessageField(value: &v)
|
||||
if let v = v {
|
||||
if hadOneofValue {try decoder.handleConflictingOneOf()}
|
||||
self.variant = .healthMetrics(v)
|
||||
}
|
||||
}()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
|
@ -1637,6 +1814,10 @@ extension Telemetry: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementation
|
|||
guard case .localStats(let v)? = self.variant else { preconditionFailure() }
|
||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 6)
|
||||
}()
|
||||
case .healthMetrics?: try {
|
||||
guard case .healthMetrics(let v)? = self.variant else { preconditionFailure() }
|
||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 7)
|
||||
}()
|
||||
case nil: break
|
||||
}
|
||||
try unknownFields.traverse(visitor: &visitor)
|
||||
|
|
|
|||
|
|
@ -68,16 +68,6 @@
|
|||
<key>DefaultValue</key>
|
||||
<false/>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>Type</key>
|
||||
<string>PSToggleSwitchSpecifier</string>
|
||||
<key>Title</key>
|
||||
<string>Use Legacy Mesh Map</string>
|
||||
<key>Key</key>
|
||||
<string>mapUseLegacy</string>
|
||||
<key>DefaultValue</key>
|
||||
<false/>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>Type</key>
|
||||
<string>PSGroupSpecifier</string>
|
||||
|
|
|
|||
|
|
@ -21,8 +21,13 @@ struct MeshActivityAttributes: ActivityAttributes {
|
|||
var sentPackets: UInt32
|
||||
var receivedPackets: UInt32
|
||||
var badReceivedPackets: UInt32
|
||||
var dupeReceivedPackets: UInt32
|
||||
var packetsSentRelay: UInt32
|
||||
var packetsCanceledRelay: UInt32
|
||||
var nodesOnline: UInt32
|
||||
var totalNodes: UInt32
|
||||
|
||||
public var numTxRelayCanceled: UInt32 = 0
|
||||
var timerRange: ClosedRange<Date>
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,9 @@ struct WidgetsLiveActivity: Widget {
|
|||
sentPackets: context.state.sentPackets,
|
||||
receivedPackets: context.state.receivedPackets,
|
||||
badReceivedPackets: context.state.badReceivedPackets,
|
||||
dupeReceivedPackets: context.state.dupeReceivedPackets,
|
||||
packetsSentRelay: context.state.packetsSentRelay,
|
||||
packetsCanceledRelay: context.state.packetsCanceledRelay,
|
||||
nodesOnline: context.state.nodesOnline,
|
||||
totalNodes: context.state.totalNodes,
|
||||
timerRange: context.state.timerRange)
|
||||
|
|
@ -28,33 +31,31 @@ struct WidgetsLiveActivity: Widget {
|
|||
} dynamicIsland: { context in
|
||||
DynamicIsland {
|
||||
DynamicIslandExpandedRegion(.leading) {
|
||||
HStack(alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/) {
|
||||
Spacer()
|
||||
Text("Mesh")
|
||||
.font(.callout)
|
||||
.fontWeight(.medium)
|
||||
.foregroundStyle(.primary)
|
||||
.padding(.bottom, 10)
|
||||
.fixedSize()
|
||||
Spacer()
|
||||
}
|
||||
if context.state.totalNodes >= 100 {
|
||||
Text("100+ online")
|
||||
.font(.caption)
|
||||
.font(.callout)
|
||||
.foregroundStyle(.secondary)
|
||||
.fixedSize()
|
||||
} else {
|
||||
Text("\(context.state.nodesOnline) of \(context.state.totalNodes) online")
|
||||
.font(.caption)
|
||||
.font(.callout)
|
||||
.foregroundStyle(.secondary)
|
||||
.fixedSize()
|
||||
}
|
||||
Text("\(String(format: "Ch. Util: %.2f", context.state.channelUtilization))%")
|
||||
.font(.caption)
|
||||
.font(.caption2)
|
||||
.foregroundStyle(.secondary)
|
||||
.fixedSize()
|
||||
Text("\(String(format: "Airtime: %.2f", context.state.airtime))%")
|
||||
.font(.caption)
|
||||
.font(.caption2)
|
||||
.foregroundStyle(.secondary)
|
||||
.fixedSize()
|
||||
Text("Sent: \(context.state.sentPackets)")
|
||||
.font(.caption2)
|
||||
.foregroundStyle(.secondary)
|
||||
.fixedSize()
|
||||
Text("Received: \(context.state.receivedPackets)")
|
||||
.font(.caption2)
|
||||
.foregroundStyle(.secondary)
|
||||
.fixedSize()
|
||||
}
|
||||
|
|
@ -63,32 +64,27 @@ struct WidgetsLiveActivity: Widget {
|
|||
.tint(Color("LightIndigo"))
|
||||
}
|
||||
DynamicIslandExpandedRegion(.trailing, priority: 1) {
|
||||
HStack(alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/) {
|
||||
Spacer()
|
||||
Text("Packets")
|
||||
.font(.callout)
|
||||
.fontWeight(.medium)
|
||||
.foregroundStyle(.primary)
|
||||
.padding(.bottom, 10)
|
||||
.fixedSize()
|
||||
Spacer()
|
||||
}
|
||||
Text("Sent: \(context.state.sentPackets)")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
.fixedSize()
|
||||
Text("Received: \(context.state.receivedPackets)")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
.fixedSize()
|
||||
Spacer()
|
||||
Text("Bad: \(context.state.badReceivedPackets)")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
.fixedSize()
|
||||
Text("Dupe: \(context.state.dupeReceivedPackets)")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
.fixedSize()
|
||||
Text("Relayed: \(context.state.packetsSentRelay)")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
.fixedSize()
|
||||
Text("Relay Cancel: \(context.state.packetsCanceledRelay)")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
.fixedSize()
|
||||
}
|
||||
DynamicIslandExpandedRegion(.bottom) {
|
||||
Text("Last Heard: \(Date().formatted())")
|
||||
.font(.caption)
|
||||
.font(.caption2)
|
||||
.fontWeight(.medium)
|
||||
.foregroundStyle(.tint)
|
||||
.fixedSize()
|
||||
|
|
@ -122,7 +118,7 @@ struct WidgetsLiveActivity: Widget {
|
|||
|
||||
struct WidgetsLiveActivity_Previews: PreviewProvider {
|
||||
static let attributes = MeshActivityAttributes(nodeNum: 123456789, name: "RAK Compact Rotary Handset Gray 8E6G")
|
||||
static let state = MeshActivityAttributes.ContentState(uptimeSeconds: 600, channelUtilization: 1.2, airtime: 3.5, sentPackets: 12587, receivedPackets: 12555, badReceivedPackets: 800, nodesOnline: 99, totalNodes: 100, timerRange: Date.now...Date(timeIntervalSinceNow: 300))
|
||||
static let state = MeshActivityAttributes.ContentState(uptimeSeconds: 600, channelUtilization: 1.2, airtime: 3.5, sentPackets: 12587, receivedPackets: 12555, badReceivedPackets: 800, dupeReceivedPackets: 100 , packetsSentRelay: 250, packetsCanceledRelay: 372, nodesOnline: 99, totalNodes: 100, timerRange: Date.now...Date(timeIntervalSinceNow: 300))
|
||||
|
||||
static var previews: some View {
|
||||
attributes
|
||||
|
|
@ -150,6 +146,9 @@ struct LiveActivityView: View {
|
|||
var sentPackets: UInt32
|
||||
var receivedPackets: UInt32
|
||||
var badReceivedPackets: UInt32
|
||||
var dupeReceivedPackets: UInt32
|
||||
var packetsSentRelay: UInt32
|
||||
var packetsCanceledRelay: UInt32
|
||||
var nodesOnline: UInt32
|
||||
var totalNodes: UInt32
|
||||
var timerRange: ClosedRange<Date>
|
||||
|
|
@ -164,7 +163,8 @@ struct LiveActivityView: View {
|
|||
.aspectRatio(contentMode: .fit)
|
||||
.frame(minWidth: 25, idealWidth: 45, maxWidth: 55)
|
||||
Spacer()
|
||||
NodeInfoView(isLuminanceReduced: _isLuminanceReduced, nodeName: nodeName, uptimeSeconds: uptimeSeconds, channelUtilization: channelUtilization, airtime: airtime, sentPackets: sentPackets, receivedPackets: receivedPackets, badReceivedPackets: badReceivedPackets, nodesOnline: nodesOnline, totalNodes: totalNodes, timerRange: timerRange)
|
||||
NodeInfoView(isLuminanceReduced: _isLuminanceReduced, nodeName: nodeName, uptimeSeconds: uptimeSeconds, channelUtilization: channelUtilization, airtime: airtime, sentPackets: sentPackets, receivedPackets: receivedPackets, badReceivedPackets: badReceivedPackets,
|
||||
dupeReceivedPackets: dupeReceivedPackets, packetsSentRelay: packetsSentRelay, packetsCanceledRelay: packetsCanceledRelay, nodesOnline: nodesOnline, totalNodes: totalNodes, timerRange: timerRange)
|
||||
Spacer()
|
||||
}
|
||||
.tint(.primary)
|
||||
|
|
@ -185,6 +185,9 @@ struct NodeInfoView: View {
|
|||
var sentPackets: UInt32
|
||||
var receivedPackets: UInt32
|
||||
var badReceivedPackets: UInt32
|
||||
var dupeReceivedPackets: UInt32
|
||||
var packetsSentRelay: UInt32
|
||||
var packetsCanceledRelay: UInt32
|
||||
var nodesOnline: UInt32
|
||||
var totalNodes: UInt32
|
||||
var timerRange: ClosedRange<Date>
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit 5709c0a05eaefccbc9cb8ed3917adbf5fd134197
|
||||
Subproject commit c9ae7fd478bffe5f954b30de6cb140821fe9ff52
|
||||
Loading…
Add table
Add a link
Reference in a new issue