Merge branch 'center-meshmap' into powersjcb/nodes-map-deep-links

This commit is contained in:
Garth Vander Houwen 2024-09-19 19:43:44 -07:00 committed by GitHub
commit 39702d45f2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
29 changed files with 979 additions and 221 deletions

View file

@ -21,21 +21,6 @@
},
": %d" : {
},
".dot" : {
},
".gauge" : {
},
".gradient" : {
},
".pill" : {
},
".text" : {
},
"(Re)define PIN_GPS_EN for your board." : {
@ -1429,8 +1414,15 @@
"Bad" : {
},
"Bad Packets: %d" : {
"Bad Packets: %d %@%%" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "Bad Packets: %1$d %2$@%%"
}
}
}
},
"Bandwidth" : {
@ -5061,9 +5053,6 @@
},
"Debug" : {
},
"Debug Log" : {
},
"Debug Logs" : {
@ -15907,6 +15896,9 @@
},
"OK" : {
},
"Ok to MQTT" : {
},
"OLED Type" : {
@ -16079,11 +16071,15 @@
"Override automatic OLED screen detection." : {
},
"Packets Received: %d" : {
},
"Packets Sent: %d" : {
"Packets: Sent: %d Received: %d" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "Packets: Sent: %1$d Received: %2$d"
}
}
}
},
"password" : {
"localizations" : {
@ -19158,6 +19154,9 @@
},
"Send" : {
},
"Send ${messageContent} to ${channelNumber}" : {
},
"Send a Group Message" : {
@ -22314,6 +22313,9 @@
}
}
}
},
"Uptime: %@" : {
},
"Use a PWM output (like the RAK Buzzer) for tunes instead of an on/off output. This will ignore the output, output duration and active settings and use the device config buzzer GPIO option instead." : {

View file

@ -376,6 +376,7 @@
DD77093C2AA1AFA3007A8BF0 /* ChannelTips.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelTips.swift; sourceTree = "<group>"; };
DD77093E2AA1B146007A8BF0 /* UIColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIColor.swift; sourceTree = "<group>"; };
DD798B062915928D005217CD /* ChannelMessageList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelMessageList.swift; sourceTree = "<group>"; };
DD7CF8DA2C93663C008BD10E /* MeshtasticDataModelV 44.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 44.xcdatamodel"; sourceTree = "<group>"; };
DD7E235F2C7AA3E50078ACDF /* MeshtasticDataModelV 43.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 43.xcdatamodel"; sourceTree = "<group>"; };
DD8169F8271F1A6100F4AB02 /* MeshLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshLogger.swift; sourceTree = "<group>"; };
DD8169FA271F1F3A00F4AB02 /* MeshLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshLog.swift; sourceTree = "<group>"; };
@ -1686,7 +1687,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 2.5.3;
MARKETING_VERSION = 2.5.5;
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTS_MACCATALYST = YES;
@ -1721,7 +1722,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 2.5.3;
MARKETING_VERSION = 2.5.5;
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTS_MACCATALYST = YES;
@ -1753,7 +1754,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.5.3;
MARKETING_VERSION = 2.5.5;
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@ -1786,7 +1787,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.5.3;
MARKETING_VERSION = 2.5.5;
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@ -1898,6 +1899,7 @@
DD3CC6BA28E366DF00FA9159 /* Meshtastic.xcdatamodeld */ = {
isa = XCVersionGroup;
children = (
DD7CF8DA2C93663C008BD10E /* MeshtasticDataModelV 44.xcdatamodel */,
DD7E235F2C7AA3E50078ACDF /* MeshtasticDataModelV 43.xcdatamodel */,
DD1BD0F12C61D3AD008C0C70 /* MeshtasticDataModelV 42.xcdatamodel */,
DD2984A82C5AEF7500B1268D /* MeshtasticDataModelV 41.xcdatamodel */,
@ -1942,7 +1944,7 @@
DD5D0A9A2931AD6B00F7EA61 /* MeshtasticDataModelV2.xcdatamodel */,
DD3CC6BB28E366DF00FA9159 /* MeshtasticDataModel.xcdatamodel */,
);
currentVersion = DD7E235F2C7AA3E50078ACDF /* MeshtasticDataModelV 43.xcdatamodel */;
currentVersion = DD7CF8DA2C93663C008BD10E /* MeshtasticDataModelV 44.xcdatamodel */;
name = Meshtastic.xcdatamodeld;
path = Meshtastic/Meshtastic.xcdatamodeld;
sourceTree = "<group>";

View file

@ -42,8 +42,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-protobuf.git",
"state" : {
"revision" : "d57a5aecf24a25b32ec4a74be2f5d0a995a47c4b",
"version" : "1.27.0"
"revision" : "edb6ed4919f7756157fe02f2552b7e3850a538e5",
"version" : "1.28.1"
}
}
],

View file

@ -5,6 +5,30 @@
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "logo-dark.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "tinted"
}
],
"filename" : "logo-tinted.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
"info" : {

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View file

@ -3302,7 +3302,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
// MARK: - CB Central Manager implmentation
extension BLEManager: CBCentralManagerDelegate {
// MARK: Bluetooth enabled/disabled
func centralManagerDidUpdateState(_ central: CBCentralManager) {
if central.state == CBManagerState.poweredOn {
@ -3312,9 +3312,9 @@ extension BLEManager: CBCentralManagerDelegate {
} else {
isSwitchedOn = false
}
var status = ""
switch central.state {
case .poweredOff:
status = "BLE is powered off"
@ -3333,10 +3333,10 @@ extension BLEManager: CBCentralManagerDelegate {
}
Logger.services.info("📜 [BLE] Bluetooth status: \(status)")
}
// Called each time a peripheral is discovered
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String: Any], rssi RSSI: NSNumber) {
if self.automaticallyReconnect && peripheral.identifier.uuidString == UserDefaults.standard.object(forKey: "preferredPeripheralId") as? String ?? "" {
self.connectTo(peripheral: peripheral)
Logger.services.info("✅ [BLE] Reconnecting to prefered peripheral: \(peripheral.name ?? "Unknown", privacy: .public)")
@ -3344,7 +3344,7 @@ extension BLEManager: CBCentralManagerDelegate {
let name = advertisementData[CBAdvertisementDataLocalNameKey] as? String
let device = Peripheral(id: peripheral.identifier.uuidString, num: 0, name: name ?? "Unknown", shortName: "?", longName: name ?? "Unknown", firmwareVersion: "Unknown", rssi: RSSI.intValue, lastUpdate: Date(), peripheral: peripheral)
let index = peripherals.map { $0.peripheral }.firstIndex(of: peripheral)
if let peripheralIndex = index {
peripherals[peripheralIndex] = device
} else {

View file

@ -5,6 +5,16 @@ import OSLog
class LocalNotificationManager {
var notifications = [Notification]()
let thumbsUpAction = UNNotificationAction(identifier: "messageNotification.thumbsUpAction", title:
"👍 \(Tapbacks.thumbsUp.description)", options: [])
let thumbsDownAction = UNNotificationAction(identifier: "messageNotification.thumbsDownAction", title:
"👎 \(Tapbacks.thumbsDown.description)", options: [])
let replyInputAction = UNTextInputNotificationAction(
identifier: "messageNotification.replyInputAction",
title: "reply".localized,
options: [])
// Step 1 Request Permissions for notifications
private func requestAuthorization() {
@ -31,6 +41,15 @@ class LocalNotificationManager {
// This function iterates over the Notification objects in the notifications array and schedules them for delivery in the future
private func scheduleNotifications() {
let messageNotificationCategory = UNNotificationCategory(
identifier: "messageNotificationCategory",
actions: [thumbsUpAction, thumbsDownAction,replyInputAction],
intentIdentifiers: [],
options: .customDismissAction
)
UNUserNotificationCenter.current().setNotificationCategories([messageNotificationCategory])
for notification in notifications {
let content = UNMutableNotificationContent()
content.subtitle = notification.subtitle
@ -45,6 +64,17 @@ class LocalNotificationManager {
if notification.path != nil {
content.userInfo["path"] = notification.path
}
if notification.messageId != nil {
content.categoryIdentifier = "messageNotificationCategory"
content.userInfo["messageId"] = notification.messageId
}
if notification.channel != nil {
content.userInfo["channel"] = notification.channel
}
if notification.userNum != nil {
content.userInfo["userNum"] = notification.userNum
}
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false)
let request = UNNotificationRequest(identifier: notification.id, content: content, trigger: trigger)
@ -76,4 +106,7 @@ struct Notification {
var content: String
var target: String?
var path: String?
var messageId: Int64?
var channel: Int32?
var userNum: Int64?
}

View file

@ -816,12 +816,10 @@ func textMessageAppPacket(
context: NSManagedObjectContext,
appState: AppState
) {
var messageText = String(bytes: packet.decoded.payload, encoding: .utf8)
let rangeRef = Reference(Int.self)
let rangeTestRegex = Regex {
"seq "
TryCapture(as: rangeRef) {
OneOrMore(.digit)
} transform: { match in
@ -829,7 +827,7 @@ func textMessageAppPacket(
}
}
let rangeTest = messageText?.contains(rangeTestRegex) ?? false && messageText?.starts(with: "seq ") ?? false
if !wantRangeTestPackets && rangeTest {
return
}
@ -842,15 +840,16 @@ func textMessageAppPacket(
}
}
}
if messageText?.count ?? 0 > 0 {
MeshLogger.log("💬 \("mesh.log.textmessage.received".localized)")
let messageUsers = UserEntity.fetchRequest()
messageUsers.predicate = NSPredicate(format: "num IN %@", [packet.to, packet.from])
do {
let fetchedUsers = try context.fetch(messageUsers)
let newMessage = MessageEntity(context: context)
newMessage.messageId = Int64(packet.id)
if packet.rxTime > 0 {
@ -864,56 +863,66 @@ func textMessageAppPacket(
newMessage.isEmoji = packet.decoded.emoji == 1
newMessage.channel = Int32(packet.channel)
newMessage.portNum = Int32(packet.decoded.portnum.rawValue)
newMessage.publicKey = packet.publicKey
newMessage.pkiEncrypted = packet.pkiEncrypted
if newMessage.toUser?.pkiEncrypted ?? false {
newMessage.pkiEncrypted = true
newMessage.publicKey = packet.publicKey
}
if packet.decoded.portnum == PortNum.detectionSensorApp {
if !UserDefaults.enableDetectionNotifications {
newMessage.read = true
}
}
if packet.decoded.replyID > 0 {
newMessage.replyID = Int64(packet.decoded.replyID)
}
if fetchedUsers.first(where: { $0.num == packet.to }) != nil && packet.to != Constants.maximumNodeNum {
if !storeForwardBroadcast {
newMessage.toUser = fetchedUsers.first(where: { $0.num == packet.to })
}
}
if fetchedUsers.first(where: { $0.num == packet.from }) != nil {
newMessage.fromUser = fetchedUsers.first(where: { $0.num == packet.from })
if !(newMessage.fromUser?.publicKey?.isEmpty ?? true) {
/// We have a key, check if it matches
if !(newMessage.fromUser?.publicKey?.isEmpty ?? true) && newMessage.toUser != nil && packet.pkiEncrypted {
// We have a key and it is a PKC encrypted DM, check if it matches
if newMessage.fromUser?.publicKey != newMessage.publicKey {
newMessage.fromUser?.keyMatch = false
newMessage.fromUser?.newPublicKey = newMessage.publicKey
Logger.data.error("🔑 Key Mismatch origninal key: \(newMessage.fromUser?.publicKey?.base64EncodedString() ?? "No Key") new key: \(newMessage.fromUser?.newPublicKey?.base64EncodedString() ?? "No Key") ")
}
} else {
/// We have no key, set it
newMessage.fromUser?.publicKey = packet.publicKey
newMessage.fromUser?.pkiEncrypted = packet.pkiEncrypted
/// We have no key, set it if it is not empty
if !packet.publicKey.isEmpty {
newMessage.fromUser?.pkiEncrypted = true
newMessage.fromUser?.publicKey = packet.publicKey
}
}
if packet.rxTime > 0 {
newMessage.fromUser?.userNode?.lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(packet.rxTime)))
} else {
newMessage.fromUser?.userNode?.lastHeard = Date()
}
}
newMessage.messagePayload = messageText
newMessage.messagePayloadMarkdown = generateMessageMarkdown(message: messageText!)
if packet.to != Constants.maximumNodeNum && newMessage.fromUser != nil {
newMessage.fromUser?.lastMessage = Date()
}
var messageSaved = false
do {
try context.save()
Logger.data.info("💾 Saved a new message for \(newMessage.messageId)")
messageSaved = true
if messageSaved {
if packet.decoded.portnum == PortNum.detectionSensorApp && !UserDefaults.enableDetectionNotifications {
return
}
@ -932,14 +941,16 @@ func textMessageAppPacket(
subtitle: "AKA \(newMessage.fromUser?.shortName ?? "?")",
content: messageText!,
target: "messages",
path: "meshtastic:///messages?userNum=\(newMessage.fromUser?.num ?? 0)&messageId=\(newMessage.messageId)"
path: "meshtastic:///messages?userNum=\(newMessage.fromUser?.num ?? 0)&messageId=\(newMessage.messageId)",
messageId: newMessage.messageId,
channel: newMessage.channel,
userNum: Int64(packet.from)
)
]
manager.schedule()
Logger.services.debug("iOS Notification Scheduled for text message from \(newMessage.fromUser?.longName ?? "unknown".localized)")
}
} else if newMessage.fromUser != nil && newMessage.toUser == nil {
let fetchMyInfoRequest = MyInfoEntity.fetchRequest()
fetchMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %lld", Int64(connectedNode))
@ -962,7 +973,11 @@ func textMessageAppPacket(
subtitle: "AKA \(newMessage.fromUser?.shortName ?? "?")",
content: messageText!,
target: "messages",
path: "meshtastic:///messages?channelId=\(newMessage.channel)&messageId=\(newMessage.messageId)")
path: "meshtastic:///messages?channelId=\(newMessage.channel)&messageId=\(newMessage.messageId)",
messageId: newMessage.messageId,
channel: newMessage.channel,
userNum: Int64(newMessage.fromUser?.userId ?? "0")
)
]
manager.schedule()
Logger.services.debug("iOS Notification Scheduled for text message from \(newMessage.fromUser?.longName ?? "unknown".localized)")
@ -970,7 +985,7 @@ func textMessageAppPacket(
}
}
} catch {
// Handle error
}
}
}
@ -985,6 +1000,7 @@ func textMessageAppPacket(
}
}
func waypointPacket (packet: MeshPacket, context: NSManagedObjectContext) {
let logString = String.localizedStringWithFormat("mesh.log.waypoint.received %@".localized, String(packet.from))

View file

@ -3,6 +3,6 @@
<plist version="1.0">
<dict>
<key>_XCCurrentVersionName</key>
<string>MeshtasticDataModelV 43.xcdatamodel</string>
<string>MeshtasticDataModelV 44.xcdatamodel</string>
</dict>
</plist>

View file

@ -0,0 +1,476 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="22758" systemVersion="23G5075b" 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="detectionTriggeredHigh" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="enabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="minimumBroadcastSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="monitorPin" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="name" optional="YES" attributeType="String"/>
<attribute name="sendBell" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="stateBroadcastSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="usePullup" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<relationship name="detectionSensorConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="detectionSensorConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="DeviceConfigEntity" representedClassName="DeviceConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="buttonGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="buzzerGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="debugLogEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="disableTripleClick" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="doubleTapAsButtonPress" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="isManaged" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="ledHeartbeatEnabled" optional="YES" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
<attribute name="nodeInfoBroadcastSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="rebroadcastMode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="role" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="serialEnabled" optional="YES" attributeType="Boolean" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="tzdef" optional="YES" attributeType="String"/>
<relationship name="deviceConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="deviceConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="DeviceMetadataEntity" representedClassName="DeviceMetadataEntity" syncable="YES" codeGenerationType="class">
<attribute name="canShutdown" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="deviceStateVersion" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="firmwareVersion" optional="YES" attributeType="String"/>
<attribute name="hasBluetooth" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="hasEthernet" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="hasWifi" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="hwModel" optional="YES" attributeType="String"/>
<attribute name="positionFlags" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="role" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="time" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<relationship name="metadataNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="metadata" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="DisplayConfigEntity" representedClassName="DisplayConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="compassNorthTop" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="displayMode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="flipScreen" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="gpsFormat" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="headingBold" optional="YES" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
<attribute name="oledType" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="screenCarouselInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="screenOnSeconds" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="units" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="wakeOnTapOrMotion" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<relationship name="displayConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="displayConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="ExternalNotificationConfigEntity" representedClassName="ExternalNotificationConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="active" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="alertBell" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="alertBellBuzzer" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="alertBellVibra" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="alertMessage" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="alertMessageBuzzer" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="alertMessageVibra" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="nagTimeout" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="output" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="outputBuzzer" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="outputMilliseconds" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="outputVibra" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="useI2SAsBuzzer" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="usePWM" optional="YES" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
<relationship name="externalNotificationConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="externalNotificationConfig" inverseEntity="NodeInfoEntity"/>
</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="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="numTotalNodes" 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="altitude" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="hasPositions" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="id" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="latitudeI" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="longitudeI" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="num" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="response" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="route" optional="YES" attributeType="Transformable" customClassName="[UInt32]"/>
<attribute name="routeText" optional="YES" attributeType="String"/>
<attribute name="time" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<relationship name="hops" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="TraceRouteHopEntity" inverseName="traceRoute" inverseEntity="TraceRouteHopEntity"/>
<relationship name="node" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="traceRoutes" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="TraceRouteHopEntity" representedClassName="TraceRouteHopEntity" syncable="YES" codeGenerationType="class">
<attribute name="altitude" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="latitudeI" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="longitudeI" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="name" optional="YES" attributeType="String"/>
<attribute name="num" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="time" attributeType="Date" usesScalarValueType="NO"/>
<relationship name="traceRoute" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="TraceRouteEntity" inverseName="hops" inverseEntity="TraceRouteEntity"/>
</entity>
<entity name="UserEntity" representedClassName="UserEntity" syncable="YES" codeGenerationType="class">
<attribute name="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>

View file

@ -9,9 +9,9 @@ import SwiftUI
import OSLog
class MeshtasticAppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate, ObservableObject {
var router: Router?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
Logger.services.info("🚀 [App] Meshtstic Apple App launched!")
// Default User Default Values
@ -22,7 +22,7 @@ class MeshtasticAppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificat
if #available(iOS 17.0, macOS 14.0, *) {
let locationsHandler = LocationsHandler.shared
locationsHandler.startLocationUpdates()
// If a background activity session was previously active, reinstantiate it after the background launch.
if locationsHandler.backgroundActivity {
locationsHandler.backgroundActivity = true
@ -38,7 +38,7 @@ class MeshtasticAppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificat
) {
completionHandler([.list, .banner, .sound])
}
// This method is called when a user clicks on the notification
func userNotificationCenter(
_ center: UNUserNotificationCenter,
@ -46,6 +46,64 @@ class MeshtasticAppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificat
withCompletionHandler completionHandler: @escaping () -> Void
) {
let userInfo = response.notification.request.content.userInfo
switch response.actionIdentifier {
case UNNotificationDefaultActionIdentifier:
break
case "messageNotification.thumbsUpAction":
if let channel = userInfo["channel"] as? Int32,
let replyID = userInfo["messageId"] as? Int64 {
let tapbackResponse = !BLEManager.shared.sendMessage(
message: Tapbacks.thumbsUp.emojiString,
toUserNum: userInfo["userNum"] as? Int64 ?? 0,
channel: channel,
isEmoji: true,
replyID: replyID
)
Logger.services.info("Tapback response sent")
} else {
Logger.services.error("Failed to retrieve channel or messageId from userInfo")
}
break
case "messageNotification.thumbsDownAction":
if let channel = userInfo["channel"] as? Int32,
let replyID = userInfo["messageId"] as? Int64 {
let tapbackResponse = !BLEManager.shared.sendMessage(
message: Tapbacks.thumbsDown.emojiString,
toUserNum: userInfo["userNum"] as? Int64 ?? 0,
channel: channel,
isEmoji: true,
replyID: replyID
)
Logger.services.info("Tapback response sent")
} else {
Logger.services.error("Failed to retrieve channel or messageId from userInfo")
}
break
case "messageNotification.replyInputAction":
if let userInput = (response as? UNTextInputNotificationResponse)?.userText,
let channel = userInfo["channel"] as? Int32,
let replyID = userInfo["messageId"] as? Int64 {
let tapbackResponse = !BLEManager.shared.sendMessage(
message: userInput,
toUserNum: userInfo["userNum"] as? Int64 ?? 0,
channel: channel,
isEmoji: false,
replyID: replyID
)
Logger.services.info("Actionable notification reply sent")
} else {
Logger.services.error("Failed to retrieve user input, channel, or messageId from userInfo")
}
break
default:
break
}
if let targetValue = userInfo["target"] as? String,
let deepLink = userInfo["path"] as? String,
let url = URL(string: deepLink) {
@ -54,7 +112,7 @@ class MeshtasticAppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificat
} else {
Logger.services.error("Failed to handle notification response: \(userInfo)")
}
completionHandler()
}
}

View file

@ -181,8 +181,11 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext)
newUser.role = Int32(newUserMessage.role.rawValue)
newUser.hwModel = String(describing: newUserMessage.hwModel).uppercased()
newUser.hwModelId = Int32(newUserMessage.hwModel.rawValue)
newUser.pkiEncrypted = packet.pkiEncrypted
newUser.publicKey = packet.publicKey
if !newUserMessage.publicKey.isEmpty {
newUser.pkiEncrypted = true
newUser.publicKey = newUserMessage.publicKey
}
Task {
Api().loadDeviceHardwareData { (hw) in
let dh = hw.first(where: { $0.hwModel == newUser.hwModelId })
@ -209,8 +212,10 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext)
} else {
if packet.from > Constants.minimumNodeNum {
let newUser = createUser(num: Int64(packet.from), context: context)
newNode.user?.pkiEncrypted = packet.pkiEncrypted
newNode.user?.publicKey = packet.publicKey
if !packet.publicKey.isEmpty {
newNode.user?.pkiEncrypted = true
newNode.user?.publicKey = packet.publicKey
}
newNode.user = newUser
}
}
@ -224,6 +229,7 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext)
myInfoEntity.rebootCount = 0
do {
try context.save()
Logger.data.info("💾 [NodeInfo] Saved a NodeInfo for node number: \(packet.from.toHex(), privacy: .public)")
Logger.data.info("💾 [MyInfoEntity] Saved a new myInfo for node number: \(packet.from.toHex(), privacy: .public)")
} catch {
context.rollback()
@ -271,10 +277,9 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext)
fetchedNode[0].user!.role = Int32(nodeInfoMessage.user.role.rawValue)
fetchedNode[0].user!.hwModel = String(describing: nodeInfoMessage.user.hwModel).uppercased()
fetchedNode[0].user!.hwModelId = Int32(nodeInfoMessage.user.hwModel.rawValue)
if !packet.publicKey.isEmpty {
fetchedNode[0].user!.pkiEncrypted = packet.pkiEncrypted
fetchedNode[0].user!.publicKey = packet.publicKey
if !nodeInfoMessage.user.publicKey.isEmpty {
fetchedNode[0].user!.pkiEncrypted = true
fetchedNode[0].user!.publicKey = nodeInfoMessage.user.publicKey
}
Task {
Api().loadDeviceHardwareData { (hw) in
@ -458,7 +463,6 @@ func upsertDeviceConfigPacket(config: Config.DeviceConfig, nodeNum: Int64, sessi
let newDeviceConfig = DeviceConfigEntity(context: context)
newDeviceConfig.role = Int32(config.role.rawValue)
newDeviceConfig.serialEnabled = config.serialEnabled
newDeviceConfig.debugLogEnabled = config.debugLogEnabled
newDeviceConfig.buttonGpio = Int32(config.buttonGpio)
newDeviceConfig.buzzerGpio = Int32(config.buzzerGpio)
newDeviceConfig.rebroadcastMode = Int32(config.rebroadcastMode.rawValue)
@ -471,7 +475,6 @@ func upsertDeviceConfigPacket(config: Config.DeviceConfig, nodeNum: Int64, sessi
} else {
fetchedNode[0].deviceConfig?.role = Int32(config.role.rawValue)
fetchedNode[0].deviceConfig?.serialEnabled = config.serialEnabled
fetchedNode[0].deviceConfig?.debugLogEnabled = config.debugLogEnabled
fetchedNode[0].deviceConfig?.buttonGpio = Int32(config.buttonGpio)
fetchedNode[0].deviceConfig?.buzzerGpio = Int32(config.buzzerGpio)
fetchedNode[0].deviceConfig?.rebroadcastMode = Int32(config.rebroadcastMode.rawValue)
@ -595,6 +598,7 @@ func upsertLoRaConfigPacket(config: Config.LoRaConfig, nodeNum: Int64, sessionPa
newLoRaConfig.channelNum = Int32(config.channelNum)
newLoRaConfig.sx126xRxBoostedGain = config.sx126XRxBoostedGain
newLoRaConfig.ignoreMqtt = config.ignoreMqtt
newLoRaConfig.okToMqtt = config.configOkToMqtt
fetchedNode[0].loRaConfig = newLoRaConfig
} else {
fetchedNode[0].loRaConfig?.regionCode = Int32(config.region.rawValue)
@ -612,6 +616,7 @@ func upsertLoRaConfigPacket(config: Config.LoRaConfig, nodeNum: Int64, sessionPa
fetchedNode[0].loRaConfig?.channelNum = Int32(config.channelNum)
fetchedNode[0].loRaConfig?.sx126xRxBoostedGain = config.sx126XRxBoostedGain
fetchedNode[0].loRaConfig?.ignoreMqtt = config.ignoreMqtt
fetchedNode[0].loRaConfig?.okToMqtt = config.configOkToMqtt
fetchedNode[0].loRaConfig?.sx126xRxBoostedGain = config.sx126XRxBoostedGain
}
if sessionPasskey != nil {
@ -813,21 +818,23 @@ func upsertSecurityConfigPacket(config: Config.SecurityConfig, nodeNum: Int64, s
let newSecurityConfig = SecurityConfigEntity(context: context)
newSecurityConfig.publicKey = config.publicKey
newSecurityConfig.privateKey = config.privateKey
newSecurityConfig.adminKey = config.adminKey
if config.adminKey.count > 0 {
newSecurityConfig.adminKey = config.adminKey[0]
}
newSecurityConfig.isManaged = config.isManaged
newSecurityConfig.serialEnabled = config.serialEnabled
newSecurityConfig.debugLogApiEnabled = config.debugLogApiEnabled
newSecurityConfig.bluetoothLoggingEnabled = config.bluetoothLoggingEnabled
newSecurityConfig.adminChannelEnabled = config.adminChannelEnabled
fetchedNode[0].securityConfig = newSecurityConfig
} else {
fetchedNode[0].securityConfig?.publicKey = config.publicKey
fetchedNode[0].securityConfig?.privateKey = config.privateKey
fetchedNode[0].securityConfig?.adminKey = config.adminKey
if config.adminKey.count > 0 {
fetchedNode[0].securityConfig?.adminKey = config.adminKey[0]
}
fetchedNode[0].securityConfig?.isManaged = config.isManaged
fetchedNode[0].securityConfig?.serialEnabled = config.serialEnabled
fetchedNode[0].securityConfig?.debugLogApiEnabled = config.debugLogApiEnabled
fetchedNode[0].securityConfig?.bluetoothLoggingEnabled = config.bluetoothLoggingEnabled
fetchedNode[0].securityConfig?.adminChannelEnabled = config.adminChannelEnabled
}
if sessionPasskey?.count != 0 {

View file

@ -92,6 +92,7 @@ struct Connect: View {
}
VStack {
let localStats = node?.telemetries?.filtered(using: NSPredicate(format: "metricsType == 6")).lastObject as? TelemetryEntity
if localStats != nil {
Divider()
if localStats?.numTotalNodes ?? 0 >= 100 {
@ -111,25 +112,29 @@ struct Connect: View {
.font(.caption)
.fontWeight(.medium)
.foregroundStyle(.secondary)
Text("Packets Sent: \(localStats?.numPacketsTx ?? 0)")
Text("Packets: Sent: \(localStats?.numPacketsTx ?? 0) Received: \(localStats?.numPacketsRx ?? 0)")
.font(.caption)
.fontWeight(.medium)
.foregroundStyle(.secondary)
Text("Packets Received: \(localStats?.numPacketsRx ?? 0)")
.font(.caption)
.fontWeight(.medium)
.foregroundStyle(.secondary)
Text("Bad Packets: \(localStats?.numPacketsRxBad ?? 0)")
let errorRate = (Double(localStats?.numPacketsRxBad ?? -1) / Double(localStats?.numPacketsRx ?? -1)) * 100
Text("Bad Packets: \(localStats?.numPacketsRxBad ?? 0) \(String(format: "Error Rate: %.2f", errorRate))%")
.font(.caption)
.fontWeight(.medium)
.foregroundStyle(.secondary)
.fixedSize()
let now = Date.now
let later = now + TimeInterval(Double(localStats?.numPacketsRxBad ?? 0))
let uptime = (now..<later).formatted(.components(style: .narrow))
Text("Uptime: \(uptime)")
.font(.caption)
.fontWeight(.medium)
.foregroundStyle(.secondary)
}
}
}
.font(.caption)
.foregroundColor(Color.gray)
.padding([.top, .bottom])
.padding([.top])
.swipeActions {
Button(role: .destructive) {
if let connectedPeripheral = bleManager.connectedPeripheral,

View file

@ -72,6 +72,20 @@ private func getColor(signalStrength: LoRaSignalStrength) -> Color {
}
func getLoRaSignalStrength(snr: Float, rssi: Int32, preset: ModemPresets) -> LoRaSignalStrength {
// rssi is 0 when not available
if rssi == 0 {
if snr > (preset.snrLimit()) {
return .good
}
if snr < (preset.snrLimit() - 7.5) {
return .none
}
if snr <= (preset.snrLimit() - 5.5) {
return .bad
}
return .fair
}
if rssi > -115 && snr > (preset.snrLimit()) {
return .good
} else if rssi < -126 && snr < (preset.snrLimit() - 7.5) {

View file

@ -35,7 +35,6 @@ struct UserList: View {
var boolFilters: [Bool] {[
isFavorite,
isOnline,
isPkiEncrypted,
isEnvironment,
distanceFilter,
roleFilter
@ -45,11 +44,12 @@ struct UserList: View {
sortDescriptors: [NSSortDescriptor(key: "lastMessage", ascending: false),
NSSortDescriptor(key: "userNode.favorite", ascending: false),
NSSortDescriptor(key: "pkiEncrypted", ascending: false),
NSSortDescriptor(key: "userNode.lastHeard", ascending: false),
NSSortDescriptor(key: "longName", ascending: true)],
predicate: NSPredicate(format: "hwModelId != nil"),
predicate: NSPredicate(format: "longName != ''"),
animation: .default
)
private var users: FetchedResults<UserEntity>
var users: FetchedResults<UserEntity>
@Binding var node: NodeInfoEntity?
@Binding var userSelection: UserEntity?
@ -202,34 +202,55 @@ struct UserList: View {
DirectMessagesHelp()
}
.onChange(of: searchText) { _ in
searchUserList()
Task {
await searchUserList()
}
}
.onChange(of: viaLora) { _ in
if !viaLora && !viaMqtt {
viaMqtt = true
}
searchUserList()
Task {
await searchUserList()
}
}
.onChange(of: viaMqtt) { _ in
if !viaLora && !viaMqtt {
viaLora = true
}
searchUserList()
Task {
await searchUserList()
}
}
.onChange(of: [deviceRoles]) { _ in
searchUserList()
Task {
await searchUserList()
}
}
.onChange(of: hopsAway) { _ in
searchUserList()
Task {
await searchUserList()
}
}
.onChange(of: [boolFilters]) { _ in
searchUserList()
Task {
await searchUserList()
}
}
.onChange(of: maxDistance) { _ in
searchUserList()
Task {
await searchUserList()
}
}
.onChange(of: isPkiEncrypted) { _ in
Task {
await searchUserList()
}
}
.onAppear {
searchUserList()
Task {
await searchUserList()
}
}
.safeAreaInset(edge: .bottom, alignment: .leading) {
HStack {
@ -267,8 +288,7 @@ struct UserList: View {
.scrollDismissesKeyboard(.immediately)
}
}
private func searchUserList() {
private func searchUserList() async {
/// Case Insensitive Search Text Predicates
let searchPredicates = ["userId", "numString", "hwModel", "hwDisplayName", "longName", "shortName"].map { property in

View file

@ -130,7 +130,9 @@ struct NodeMapSwiftUI: View {
if node.positions?.count ?? 0 > 1 {
position = .automatic
} else {
position = .camera(MapCamera(centerCoordinate: mostRecent!.coordinate, distance: 8000, heading: 0, pitch: 60))
if let mrCoord = mostRecent?.coordinate {
position = .camera(MapCamera(centerCoordinate: mrCoord, distance: 8000, heading: 0, pitch: 60))
}
}
if self.scene == nil {
Task {

View file

@ -93,7 +93,7 @@ struct Channels: View {
preciseLocation = true
positionsEnabled = true
if channelKey == "AQ==" {
positionPrecision = 13
positionPrecision = 14
preciseLocation = false
}
} else if !supportedVersion && channelRole == 2 {
@ -103,8 +103,8 @@ struct Channels: View {
} else {
if channelKey == "AQ==" {
preciseLocation = false
if (positionPrecision > 0 && positionPrecision < 10) || positionPrecision > 16 {
positionPrecision = 13
if (positionPrecision > 0 && positionPrecision < 11) || positionPrecision > 14 {
positionPrecision = 14
}
} else if positionPrecision == 32 {
preciseLocation = true

View file

@ -149,7 +149,7 @@ struct ChannelForm: View {
.listRowSeparator(.visible)
.onChange(of: preciseLocation) { pl in
if pl == false {
positionPrecision = 13
positionPrecision = 14
}
}
}
@ -158,7 +158,7 @@ struct ChannelForm: View {
VStack(alignment: .leading) {
Label("Approximate Location", systemImage: "location.slash.circle.fill")
Slider(value: $positionPrecision, in: 10...16, step: 1) {
Slider(value: $positionPrecision, in: 11...14, step: 1) {
} minimumValueLabel: {
Image(systemName: "minus")
} maximumValueLabel: {
@ -220,7 +220,7 @@ struct ChannelForm: View {
}
positionPrecision = 32
} else {
positionPrecision = 13
positionPrecision = 14
}
hasChanges = true
}
@ -230,7 +230,7 @@ struct ChannelForm: View {
.onChange(of: positionsEnabled) { pe in
if pe {
if positionPrecision == 0 {
positionPrecision = 13
positionPrecision = 14
}
} else {
positionPrecision = 0

View file

@ -24,7 +24,6 @@ struct DeviceConfig: View {
@State var buzzerGPIO = 0
@State var buttonGPIO = 0
@State var serialEnabled = true
@State var debugLogEnabled = false
@State var rebroadcastMode = 0
@State var nodeInfoBroadcastSecs = 10800
@State var doubleTapAsButtonPress = false
@ -89,10 +88,6 @@ struct DeviceConfig: View {
Label("Serial Console", systemImage: "terminal")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
Toggle(isOn: $debugLogEnabled) {
Label("Debug Log", systemImage: "ant.fill")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
VStack(alignment: .leading) {
HStack {
Label("Time Zone", systemImage: "clock.badge.exclamationmark")
@ -200,7 +195,6 @@ struct DeviceConfig: View {
var dc = Config.DeviceConfig()
dc.role = DeviceRoles(rawValue: deviceRole)!.protoEnumValue()
dc.serialEnabled = serialEnabled
dc.debugLogEnabled = debugLogEnabled
dc.buttonGpio = UInt32(buttonGPIO)
dc.buzzerGpio = UInt32(buzzerGPIO)
dc.rebroadcastMode = RebroadcastModes(rawValue: rebroadcastMode)?.protoEnumValue() ?? RebroadcastModes.all.protoEnumValue()
@ -259,9 +253,6 @@ struct DeviceConfig: View {
.onChange(of: serialEnabled) {
if $0 != node?.deviceConfig?.serialEnabled { hasChanges = true }
}
.onChange(of: debugLogEnabled) {
if $0 != node?.deviceConfig?.debugLogEnabled { hasChanges = true }
}
.onChange(of: buttonGPIO) { newButtonGPIO in
if newButtonGPIO != node?.deviceConfig?.buttonGpio ?? -1 { hasChanges = true }
}
@ -289,7 +280,6 @@ struct DeviceConfig: View {
func setDeviceValues() {
self.deviceRole = Int(node?.deviceConfig?.role ?? 0)
self.serialEnabled = (node?.deviceConfig?.serialEnabled ?? true)
self.debugLogEnabled = node?.deviceConfig?.debugLogEnabled ?? false
self.buttonGPIO = Int(node?.deviceConfig?.buttonGpio ?? 0)
self.buzzerGPIO = Int(node?.deviceConfig?.buzzerGpio ?? 0)
self.rebroadcastMode = Int(node?.deviceConfig?.rebroadcastMode ?? 0)

View file

@ -45,6 +45,7 @@ struct LoRaConfig: View {
@State var rxBoostedGain = false
@State var overrideFrequency: Float = 0.0
@State var ignoreMqtt = false
@State var okToMqtt = false
let floatFormatter: NumberFormatter = {
let formatter = NumberFormatter()
@ -100,6 +101,10 @@ struct LoRaConfig: View {
Label("Ignore MQTT", systemImage: "server.rack")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
Toggle(isOn: $okToMqtt) {
Label("Ok to MQTT", systemImage: "network")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
Toggle(isOn: $txEnabled) {
Label("Transmit Enabled", systemImage: "waveform.path")
@ -209,6 +214,7 @@ struct LoRaConfig: View {
lc.sx126XRxBoostedGain = rxBoostedGain
lc.overrideFrequency = overrideFrequency
lc.ignoreMqtt = ignoreMqtt
lc.configOkToMqtt = okToMqtt
if connectedNode?.num ?? -1 == node?.user?.num ?? 0 {
UserDefaults.modemPreset = modemPreset
}
@ -292,6 +298,9 @@ struct LoRaConfig: View {
.onChange(of: ignoreMqtt) {
if $0 != node?.loRaConfig?.ignoreMqtt { hasChanges = true }
}
.onChange(of: okToMqtt) {
if $0 != node?.loRaConfig?.okToMqtt { hasChanges = true }
}
}
func setLoRaValues() {
self.hopLimit = Int(node?.loRaConfig?.hopLimit ?? 3)
@ -307,6 +316,7 @@ struct LoRaConfig: View {
self.rxBoostedGain = node?.loRaConfig?.sx126xRxBoostedGain ?? false
self.overrideFrequency = node?.loRaConfig?.overrideFrequency ?? 0.0
self.ignoreMqtt = node?.loRaConfig?.ignoreMqtt ?? false
self.okToMqtt = node?.loRaConfig?.okToMqtt ?? false
self.hasChanges = false
}
}

View file

@ -32,8 +32,7 @@ struct MQTTConfig: View {
@State var nearbyTopics = [String]()
@State var mapReportingEnabled = false
@State var mapPublishIntervalSecs = 3600
@State var preciseLocation: Bool = false
@State var mapPositionPrecision: Double = 13.0
@State var mapPositionPrecision: Double = 14.0
let locale = Locale.current
@ -105,35 +104,17 @@ struct MQTTConfig: View {
}
}
.pickerStyle(DefaultPickerStyle())
VStack(alignment: .leading) {
Toggle(isOn: $preciseLocation) {
Label("Precise Location", systemImage: "scope")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
.listRowSeparator(.visible)
.onChange(of: preciseLocation) { pl in
if pl == false {
mapPositionPrecision = 12
} else {
mapPositionPrecision = 32
}
}
}
if !preciseLocation {
VStack(alignment: .leading) {
Label("Approximate Location", systemImage: "location.slash.circle.fill")
Slider(value: $mapPositionPrecision, in: 11...16, step: 1) {
} minimumValueLabel: {
Image(systemName: "minus")
} maximumValueLabel: {
Image(systemName: "plus")
}
Text(PositionPrecision(rawValue: Int(mapPositionPrecision))?.description ?? "")
.foregroundColor(.gray)
.font(.callout)
Label("Approximate Location", systemImage: "location.slash.circle.fill")
Slider(value: $mapPositionPrecision, in: 11...14, step: 1) {
} minimumValueLabel: {
Image(systemName: "minus")
} maximumValueLabel: {
Image(systemName: "plus")
}
Text(PositionPrecision(rawValue: Int(mapPositionPrecision))?.description ?? "")
.foregroundColor(.gray)
.font(.callout)
}
}
}
@ -429,11 +410,12 @@ struct MQTTConfig: View {
self.mqttConnected = bleManager.mqttProxyConnected
self.mapReportingEnabled = node?.mqttConfig?.mapReportingEnabled ?? false
self.mapPublishIntervalSecs = Int(node?.mqttConfig?.mapPublishIntervalSecs ?? 3600)
self.mapPositionPrecision = Double(node?.mqttConfig?.mapPositionPrecision ?? 12)
if mapPositionPrecision == 0.0 {
self.mapPositionPrecision = 12
self.mapPositionPrecision = Double(node?.mqttConfig?.mapPositionPrecision ?? 14)
if mapPositionPrecision < 11 || mapPositionPrecision > 14 {
self.mapPositionPrecision = 14
self.hasChanges = true
} else {
self.hasChanges = false
}
self.preciseLocation = mapPositionPrecision == 32
self.hasChanges = false
}
}

View file

@ -186,7 +186,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()]
config.isManaged = isManaged
config.serialEnabled = serialEnabled
config.debugLogApiEnabled = debugLogApiEnabled

View file

@ -844,6 +844,7 @@ public struct AdminMessage {
///
/// TODO: REPLACE
case securityConfig // = 7
case sessionkeyConfig // = 8
case UNRECOGNIZED(Int)
public init() {
@ -860,6 +861,7 @@ public struct AdminMessage {
case 5: self = .loraConfig
case 6: self = .bluetoothConfig
case 7: self = .securityConfig
case 8: self = .sessionkeyConfig
default: self = .UNRECOGNIZED(rawValue)
}
}
@ -874,6 +876,7 @@ public struct AdminMessage {
case .loraConfig: return 5
case .bluetoothConfig: return 6
case .securityConfig: return 7
case .sessionkeyConfig: return 8
case .UNRECOGNIZED(let i): return i
}
}
@ -998,6 +1001,7 @@ extension AdminMessage.ConfigType: CaseIterable {
.loraConfig,
.bluetoothConfig,
.securityConfig,
.sessionkeyConfig,
]
}
@ -1755,6 +1759,7 @@ extension AdminMessage.ConfigType: SwiftProtobuf._ProtoNameProviding {
5: .same(proto: "LORA_CONFIG"),
6: .same(proto: "BLUETOOTH_CONFIG"),
7: .same(proto: "SECURITY_CONFIG"),
8: .same(proto: "SESSIONKEY_CONFIG"),
]
}

View file

@ -93,6 +93,14 @@ public struct Config {
set {payloadVariant = .security(newValue)}
}
public var sessionkey: Config.SessionkeyConfig {
get {
if case .sessionkey(let v)? = payloadVariant {return v}
return Config.SessionkeyConfig()
}
set {payloadVariant = .sessionkey(newValue)}
}
public var unknownFields = SwiftProtobuf.UnknownStorage()
///
@ -106,6 +114,7 @@ public struct Config {
case lora(Config.LoRaConfig)
case bluetooth(Config.BluetoothConfig)
case security(Config.SecurityConfig)
case sessionkey(Config.SessionkeyConfig)
#if !swift(>=4.1)
public static func ==(lhs: Config.OneOf_PayloadVariant, rhs: Config.OneOf_PayloadVariant) -> Bool {
@ -145,6 +154,10 @@ public struct Config {
guard case .security(let l) = lhs, case .security(let r) = rhs else { preconditionFailure() }
return l == r
}()
case (.sessionkey, .sessionkey): return {
guard case .sessionkey(let l) = lhs, case .sessionkey(let r) = rhs else { preconditionFailure() }
return l == r
}()
default: return false
}
}
@ -167,12 +180,6 @@ public struct Config {
/// Moved to SecurityConfig
public var serialEnabled: Bool = false
///
/// By default we turn off logging as soon as an API client connects (to keep shared serial link quiet).
/// Set this to true to leave the debug log outputting even when API is active.
/// Moved to SecurityConfig
public var debugLogEnabled: Bool = false
///
/// For boards without a hard wired button, this is the pin number that will be used
/// Boards that have more than one button can swap the function with this one. defaults to BUTTON_PIN if defined.
@ -1250,6 +1257,13 @@ public struct Config {
set {_uniqueStorage()._ignoreMqtt = newValue}
}
///
/// Sets the ok_to_mqtt bit on outgoing packets
public var configOkToMqtt: Bool {
get {return _storage._configOkToMqtt}
set {_uniqueStorage()._configOkToMqtt = newValue}
}
public var unknownFields = SwiftProtobuf.UnknownStorage()
public enum RegionCode: SwiftProtobuf.Enum {
@ -1492,11 +1506,6 @@ public struct Config {
/// Specified PIN for PairingMode.FixedPin
public var fixedPin: UInt32 = 0
///
/// Enables device (serial style logs) over Bluetooth
/// Moved to SecurityConfig
public var deviceLoggingEnabled: Bool = false
public var unknownFields = SwiftProtobuf.UnknownStorage()
public enum PairingMode: SwiftProtobuf.Enum {
@ -1559,7 +1568,7 @@ public struct Config {
///
/// The public key authorized to send admin messages to this node.
public var adminKey: Data = Data()
public var adminKey: [Data] = []
///
/// If true, device is considered to be "managed" by a mesh administrator via admin messages
@ -1572,13 +1581,9 @@ public struct Config {
///
/// By default we turn off logging as soon as an API client connects (to keep shared serial link quiet).
/// Output live debug logging over serial.
/// Output live debug logging over serial or bluetooth is set to true.
public var debugLogApiEnabled: Bool = false
///
/// Enables device (serial style logs) over Bluetooth
public var bluetoothLoggingEnabled: Bool = false
///
/// Allow incoming device control over the insecure legacy admin channel.
public var adminChannelEnabled: Bool = false
@ -1588,6 +1593,18 @@ public struct Config {
public init() {}
}
///
/// Blank config request, strictly for getting the session key
public struct SessionkeyConfig {
// 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.
public var unknownFields = SwiftProtobuf.UnknownStorage()
public init() {}
}
public init() {}
}
@ -1784,6 +1801,7 @@ extension Config.LoRaConfig.ModemPreset: @unchecked Sendable {}
extension Config.BluetoothConfig: @unchecked Sendable {}
extension Config.BluetoothConfig.PairingMode: @unchecked Sendable {}
extension Config.SecurityConfig: @unchecked Sendable {}
extension Config.SessionkeyConfig: @unchecked Sendable {}
#endif // swift(>=5.5) && canImport(_Concurrency)
// MARK: - Code below here is support for the SwiftProtobuf runtime.
@ -1801,6 +1819,7 @@ extension Config: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBas
6: .same(proto: "lora"),
7: .same(proto: "bluetooth"),
8: .same(proto: "security"),
9: .same(proto: "sessionkey"),
]
public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
@ -1913,6 +1932,19 @@ extension Config: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBas
self.payloadVariant = .security(v)
}
}()
case 9: try {
var v: Config.SessionkeyConfig?
var hadOneofValue = false
if let current = self.payloadVariant {
hadOneofValue = true
if case .sessionkey(let m) = current {v = m}
}
try decoder.decodeSingularMessageField(value: &v)
if let v = v {
if hadOneofValue {try decoder.handleConflictingOneOf()}
self.payloadVariant = .sessionkey(v)
}
}()
default: break
}
}
@ -1956,6 +1988,10 @@ extension Config: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBas
guard case .security(let v)? = self.payloadVariant else { preconditionFailure() }
try visitor.visitSingularMessageField(value: v, fieldNumber: 8)
}()
case .sessionkey?: try {
guard case .sessionkey(let v)? = self.payloadVariant else { preconditionFailure() }
try visitor.visitSingularMessageField(value: v, fieldNumber: 9)
}()
case nil: break
}
try unknownFields.traverse(visitor: &visitor)
@ -1973,7 +2009,6 @@ extension Config.DeviceConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImpl
public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .same(proto: "role"),
2: .standard(proto: "serial_enabled"),
3: .standard(proto: "debug_log_enabled"),
4: .standard(proto: "button_gpio"),
5: .standard(proto: "buzzer_gpio"),
6: .standard(proto: "rebroadcast_mode"),
@ -1993,7 +2028,6 @@ extension Config.DeviceConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImpl
switch fieldNumber {
case 1: try { try decoder.decodeSingularEnumField(value: &self.role) }()
case 2: try { try decoder.decodeSingularBoolField(value: &self.serialEnabled) }()
case 3: try { try decoder.decodeSingularBoolField(value: &self.debugLogEnabled) }()
case 4: try { try decoder.decodeSingularUInt32Field(value: &self.buttonGpio) }()
case 5: try { try decoder.decodeSingularUInt32Field(value: &self.buzzerGpio) }()
case 6: try { try decoder.decodeSingularEnumField(value: &self.rebroadcastMode) }()
@ -2015,9 +2049,6 @@ extension Config.DeviceConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImpl
if self.serialEnabled != false {
try visitor.visitSingularBoolField(value: self.serialEnabled, fieldNumber: 2)
}
if self.debugLogEnabled != false {
try visitor.visitSingularBoolField(value: self.debugLogEnabled, fieldNumber: 3)
}
if self.buttonGpio != 0 {
try visitor.visitSingularUInt32Field(value: self.buttonGpio, fieldNumber: 4)
}
@ -2051,7 +2082,6 @@ extension Config.DeviceConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImpl
public static func ==(lhs: Config.DeviceConfig, rhs: Config.DeviceConfig) -> Bool {
if lhs.role != rhs.role {return false}
if lhs.serialEnabled != rhs.serialEnabled {return false}
if lhs.debugLogEnabled != rhs.debugLogEnabled {return false}
if lhs.buttonGpio != rhs.buttonGpio {return false}
if lhs.buzzerGpio != rhs.buzzerGpio {return false}
if lhs.rebroadcastMode != rhs.rebroadcastMode {return false}
@ -2595,6 +2625,7 @@ extension Config.LoRaConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem
15: .standard(proto: "pa_fan_disabled"),
103: .standard(proto: "ignore_incoming"),
104: .standard(proto: "ignore_mqtt"),
105: .standard(proto: "config_ok_to_mqtt"),
]
fileprivate class _StorageClass {
@ -2615,6 +2646,7 @@ extension Config.LoRaConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem
var _paFanDisabled: Bool = false
var _ignoreIncoming: [UInt32] = []
var _ignoreMqtt: Bool = false
var _configOkToMqtt: Bool = false
#if swift(>=5.10)
// This property is used as the initial default value for new instances of the type.
@ -2646,6 +2678,7 @@ extension Config.LoRaConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem
_paFanDisabled = source._paFanDisabled
_ignoreIncoming = source._ignoreIncoming
_ignoreMqtt = source._ignoreMqtt
_configOkToMqtt = source._configOkToMqtt
}
}
@ -2681,6 +2714,7 @@ extension Config.LoRaConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem
case 15: try { try decoder.decodeSingularBoolField(value: &_storage._paFanDisabled) }()
case 103: try { try decoder.decodeRepeatedUInt32Field(value: &_storage._ignoreIncoming) }()
case 104: try { try decoder.decodeSingularBoolField(value: &_storage._ignoreMqtt) }()
case 105: try { try decoder.decodeSingularBoolField(value: &_storage._configOkToMqtt) }()
default: break
}
}
@ -2740,6 +2774,9 @@ extension Config.LoRaConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem
if _storage._ignoreMqtt != false {
try visitor.visitSingularBoolField(value: _storage._ignoreMqtt, fieldNumber: 104)
}
if _storage._configOkToMqtt != false {
try visitor.visitSingularBoolField(value: _storage._configOkToMqtt, fieldNumber: 105)
}
}
try unknownFields.traverse(visitor: &visitor)
}
@ -2766,6 +2803,7 @@ extension Config.LoRaConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem
if _storage._paFanDisabled != rhs_storage._paFanDisabled {return false}
if _storage._ignoreIncoming != rhs_storage._ignoreIncoming {return false}
if _storage._ignoreMqtt != rhs_storage._ignoreMqtt {return false}
if _storage._configOkToMqtt != rhs_storage._configOkToMqtt {return false}
return true
}
if !storagesAreEqual {return false}
@ -2819,7 +2857,6 @@ extension Config.BluetoothConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageI
1: .same(proto: "enabled"),
2: .same(proto: "mode"),
3: .standard(proto: "fixed_pin"),
4: .standard(proto: "device_logging_enabled"),
]
public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
@ -2831,7 +2868,6 @@ extension Config.BluetoothConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageI
case 1: try { try decoder.decodeSingularBoolField(value: &self.enabled) }()
case 2: try { try decoder.decodeSingularEnumField(value: &self.mode) }()
case 3: try { try decoder.decodeSingularUInt32Field(value: &self.fixedPin) }()
case 4: try { try decoder.decodeSingularBoolField(value: &self.deviceLoggingEnabled) }()
default: break
}
}
@ -2847,9 +2883,6 @@ extension Config.BluetoothConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageI
if self.fixedPin != 0 {
try visitor.visitSingularUInt32Field(value: self.fixedPin, fieldNumber: 3)
}
if self.deviceLoggingEnabled != false {
try visitor.visitSingularBoolField(value: self.deviceLoggingEnabled, fieldNumber: 4)
}
try unknownFields.traverse(visitor: &visitor)
}
@ -2857,7 +2890,6 @@ extension Config.BluetoothConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageI
if lhs.enabled != rhs.enabled {return false}
if lhs.mode != rhs.mode {return false}
if lhs.fixedPin != rhs.fixedPin {return false}
if lhs.deviceLoggingEnabled != rhs.deviceLoggingEnabled {return false}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}
@ -2880,7 +2912,6 @@ extension Config.SecurityConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageIm
4: .standard(proto: "is_managed"),
5: .standard(proto: "serial_enabled"),
6: .standard(proto: "debug_log_api_enabled"),
7: .standard(proto: "bluetooth_logging_enabled"),
8: .standard(proto: "admin_channel_enabled"),
]
@ -2892,11 +2923,10 @@ extension Config.SecurityConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageIm
switch fieldNumber {
case 1: try { try decoder.decodeSingularBytesField(value: &self.publicKey) }()
case 2: try { try decoder.decodeSingularBytesField(value: &self.privateKey) }()
case 3: try { try decoder.decodeSingularBytesField(value: &self.adminKey) }()
case 3: try { try decoder.decodeRepeatedBytesField(value: &self.adminKey) }()
case 4: try { try decoder.decodeSingularBoolField(value: &self.isManaged) }()
case 5: try { try decoder.decodeSingularBoolField(value: &self.serialEnabled) }()
case 6: try { try decoder.decodeSingularBoolField(value: &self.debugLogApiEnabled) }()
case 7: try { try decoder.decodeSingularBoolField(value: &self.bluetoothLoggingEnabled) }()
case 8: try { try decoder.decodeSingularBoolField(value: &self.adminChannelEnabled) }()
default: break
}
@ -2911,7 +2941,7 @@ extension Config.SecurityConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageIm
try visitor.visitSingularBytesField(value: self.privateKey, fieldNumber: 2)
}
if !self.adminKey.isEmpty {
try visitor.visitSingularBytesField(value: self.adminKey, fieldNumber: 3)
try visitor.visitRepeatedBytesField(value: self.adminKey, fieldNumber: 3)
}
if self.isManaged != false {
try visitor.visitSingularBoolField(value: self.isManaged, fieldNumber: 4)
@ -2922,9 +2952,6 @@ extension Config.SecurityConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageIm
if self.debugLogApiEnabled != false {
try visitor.visitSingularBoolField(value: self.debugLogApiEnabled, fieldNumber: 6)
}
if self.bluetoothLoggingEnabled != false {
try visitor.visitSingularBoolField(value: self.bluetoothLoggingEnabled, fieldNumber: 7)
}
if self.adminChannelEnabled != false {
try visitor.visitSingularBoolField(value: self.adminChannelEnabled, fieldNumber: 8)
}
@ -2938,9 +2965,27 @@ extension Config.SecurityConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageIm
if lhs.isManaged != rhs.isManaged {return false}
if lhs.serialEnabled != rhs.serialEnabled {return false}
if lhs.debugLogApiEnabled != rhs.debugLogApiEnabled {return false}
if lhs.bluetoothLoggingEnabled != rhs.bluetoothLoggingEnabled {return false}
if lhs.adminChannelEnabled != rhs.adminChannelEnabled {return false}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}
}
extension Config.SessionkeyConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
public static let protoMessageName: String = Config.protoMessageName + ".SessionkeyConfig"
public static let _protobuf_nameMap = SwiftProtobuf._NameMap()
public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
while let _ = try decoder.nextFieldNumber() {
}
}
public func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
try unknownFields.traverse(visitor: &visitor)
}
public static func ==(lhs: Config.SessionkeyConfig, rhs: Config.SessionkeyConfig) -> Bool {
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}
}

View file

@ -346,6 +346,19 @@ public enum HardwareModel: SwiftProtobuf.Enum {
/// Minewsemi ME25LS01 (ME25LE01_V1.0). NRF52840 w/ LR1110 radio, buttons and leds and pins.
case me25Ls014Y10Td // = 75
///
/// RP2040_FEATHER_RFM95
/// Adafruit Feather RP2040 with RFM95 LoRa Radio RFM95 with SX1272, SSD1306 OLED
/// https://www.adafruit.com/product/5714
/// https://www.adafruit.com/product/326
/// https://www.adafruit.com/product/938
/// ^^^ 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/
case m5StackCorebasic // = 77
case m5StackCore2 // = 78
///
/// ------------------------------------------------------------------------------------------------------------------------------------------
/// 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.
@ -434,6 +447,9 @@ public enum HardwareModel: SwiftProtobuf.Enum {
case 73: self = .wioE5
case 74: self = .radiomaster900Bandit
case 75: self = .me25Ls014Y10Td
case 76: self = .rp2040FeatherRfm95
case 77: self = .m5StackCorebasic
case 78: self = .m5StackCore2
case 255: self = .privateHw
default: self = .UNRECOGNIZED(rawValue)
}
@ -516,6 +532,9 @@ public enum HardwareModel: SwiftProtobuf.Enum {
case .wioE5: return 73
case .radiomaster900Bandit: return 74
case .me25Ls014Y10Td: return 75
case .rp2040FeatherRfm95: return 76
case .m5StackCorebasic: return 77
case .m5StackCore2: return 78
case .privateHw: return 255
case .UNRECOGNIZED(let i): return i
}
@ -603,6 +622,9 @@ extension HardwareModel: CaseIterable {
.wioE5,
.radiomaster900Bandit,
.me25Ls014Y10Td,
.rp2040FeatherRfm95,
.m5StackCorebasic,
.m5StackCore2,
.privateHw,
]
}
@ -1520,9 +1542,22 @@ public struct DataMessage {
/// a message a heart or poop emoji.
public var emoji: UInt32 = 0
///
/// Bitfield for extra flags. First use is to indicate that user approves the packet being uploaded to MQTT.
public var bitfield: UInt32 {
get {return _bitfield ?? 0}
set {_bitfield = newValue}
}
/// Returns true if `bitfield` has been explicitly set.
public var hasBitfield: Bool {return self._bitfield != nil}
/// Clears the value of `bitfield`. Subsequent reads from it will return its default value.
public mutating func clearBitfield() {self._bitfield = nil}
public var unknownFields = SwiftProtobuf.UnknownStorage()
public init() {}
fileprivate var _bitfield: UInt32? = nil
}
///
@ -1907,6 +1942,15 @@ public struct MeshPacket {
/// assume it is important and use a slightly higher priority
case reliable // = 70
///
/// If priority is unset but the packet is a response to a request, we want it to get there relatively quickly.
/// Furthermore, responses stop relaying packets directed to a node early.
case response // = 80
///
/// Higher priority for specific message types (portnums) to distinguish between other reliable packets.
case high // = 100
///
/// Ack/naks are sent with very high priority to ensure that retransmission
/// stops as soon as possible
@ -1928,6 +1972,8 @@ public struct MeshPacket {
case 10: self = .background
case 64: self = .default
case 70: self = .reliable
case 80: self = .response
case 100: self = .high
case 120: self = .ack
case 127: self = .max
default: self = .UNRECOGNIZED(rawValue)
@ -1941,6 +1987,8 @@ public struct MeshPacket {
case .background: return 10
case .default: return 64
case .reliable: return 70
case .response: return 80
case .high: return 100
case .ack: return 120
case .max: return 127
case .UNRECOGNIZED(let i): return i
@ -2006,6 +2054,8 @@ extension MeshPacket.Priority: CaseIterable {
.background,
.default,
.reliable,
.response,
.high,
.ack,
.max,
]
@ -3241,6 +3291,9 @@ extension HardwareModel: SwiftProtobuf._ProtoNameProviding {
73: .same(proto: "WIO_E5"),
74: .same(proto: "RADIOMASTER_900_BANDIT"),
75: .same(proto: "ME25LS01_4Y10TD"),
76: .same(proto: "RP2040_FEATHER_RFM95"),
77: .same(proto: "M5STACK_COREBASIC"),
78: .same(proto: "M5STACK_CORE2"),
255: .same(proto: "PRIVATE_HW"),
]
}
@ -3779,6 +3832,7 @@ extension DataMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementati
6: .standard(proto: "request_id"),
7: .standard(proto: "reply_id"),
8: .same(proto: "emoji"),
9: .same(proto: "bitfield"),
]
public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
@ -3795,12 +3849,17 @@ extension DataMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementati
case 6: try { try decoder.decodeSingularFixed32Field(value: &self.requestID) }()
case 7: try { try decoder.decodeSingularFixed32Field(value: &self.replyID) }()
case 8: try { try decoder.decodeSingularFixed32Field(value: &self.emoji) }()
case 9: try { try decoder.decodeSingularUInt32Field(value: &self._bitfield) }()
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
if self.portnum != .unknownApp {
try visitor.visitSingularEnumField(value: self.portnum, fieldNumber: 1)
}
@ -3825,6 +3884,9 @@ extension DataMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementati
if self.emoji != 0 {
try visitor.visitSingularFixed32Field(value: self.emoji, fieldNumber: 8)
}
try { if let v = self._bitfield {
try visitor.visitSingularUInt32Field(value: v, fieldNumber: 9)
} }()
try unknownFields.traverse(visitor: &visitor)
}
@ -3837,6 +3899,7 @@ extension DataMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementati
if lhs.requestID != rhs.requestID {return false}
if lhs.replyID != rhs.replyID {return false}
if lhs.emoji != rhs.emoji {return false}
if lhs._bitfield != rhs._bitfield {return false}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}
@ -4224,6 +4287,8 @@ extension MeshPacket.Priority: SwiftProtobuf._ProtoNameProviding {
10: .same(proto: "BACKGROUND"),
64: .same(proto: "DEFAULT"),
70: .same(proto: "RELIABLE"),
80: .same(proto: "RESPONSE"),
100: .same(proto: "HIGH"),
120: .same(proto: "ACK"),
127: .same(proto: "MAX"),
]

View file

@ -140,6 +140,10 @@ public enum TelemetrySensorType: SwiftProtobuf.Enum {
///
/// MAX17048 1S lipo battery sensor (voltage, state of charge, time to go)
case max17048 // = 28
///
/// Custom I2C sensor implementation based on https://github.com/meshtastic/i2c-sensor
case customSensor // = 29
case UNRECOGNIZED(Int)
public init() {
@ -177,6 +181,7 @@ public enum TelemetrySensorType: SwiftProtobuf.Enum {
case 26: self = .bmp3Xx
case 27: self = .icm20948
case 28: self = .max17048
case 29: self = .customSensor
default: self = .UNRECOGNIZED(rawValue)
}
}
@ -212,6 +217,7 @@ public enum TelemetrySensorType: SwiftProtobuf.Enum {
case .bmp3Xx: return 26
case .icm20948: return 27
case .max17048: return 28
case .customSensor: return 29
case .UNRECOGNIZED(let i): return i
}
}
@ -252,6 +258,7 @@ extension TelemetrySensorType: CaseIterable {
.bmp3Xx,
.icm20948,
.max17048,
.customSensor,
]
}
@ -1003,6 +1010,7 @@ extension TelemetrySensorType: SwiftProtobuf._ProtoNameProviding {
26: .same(proto: "BMP3XX"),
27: .same(proto: "ICM20948"),
28: .same(proto: "MAX17048"),
29: .same(proto: "CUSTOM_SENSOR"),
]
}

View file

@ -38,7 +38,7 @@ struct WidgetsLiveActivity: Widget {
.fixedSize()
Spacer()
}
if context.state.nodesOnline >= 100 {
if context.state.totalNodes >= 100 {
Text("100+ online")
.font(.caption)
.foregroundStyle(.secondary)
@ -81,7 +81,7 @@ struct WidgetsLiveActivity: Widget {
.font(.caption)
.foregroundStyle(.secondary)
.fixedSize()
Text("Bad \(context.state.badReceivedPackets)")
Text("Bad: \(context.state.badReceivedPackets)")
.font(.caption)
.foregroundStyle(.secondary)
.fixedSize()
@ -97,7 +97,7 @@ struct WidgetsLiveActivity: Widget {
} compactLeading: {
Image("m-logo-black")
.resizable()
.frame(width: 30.0)
.frame(width: 25)
.padding(4)
.background(.green.gradient, in: ContainerRelativeShape())
} compactTrailing: {
@ -120,27 +120,26 @@ 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(
// timerRange: Date.now...Date(timeIntervalSinceNow: 60), connected: true, channelUtilization: 25.84, airtime: 10.01, batteryLevel: 39, nodes: 17, nodesOnline: 9)
//
// static var previews: some View {
// attributes
// .previewContext(state, viewKind: .dynamicIsland(.compact))
// .previewDisplayName("Compact")
// attributes
// .previewContext(state, viewKind: .dynamicIsland(.minimal))
// .previewDisplayName("Minimal")
// attributes
// .previewContext(state, viewKind: .dynamicIsland(.expanded))
// .previewDisplayName("Expanded")
// attributes
// .previewContext(state, viewKind: .content)
// .previewDisplayName("Notification")
// }
// }
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 var previews: some View {
attributes
.previewContext(state, viewKind: .dynamicIsland(.compact))
.previewDisplayName("Compact")
attributes
.previewContext(state, viewKind: .dynamicIsland(.minimal))
.previewDisplayName("Minimal")
attributes
.previewContext(state, viewKind: .dynamicIsland(.expanded))
.previewDisplayName("Expanded")
attributes
.previewContext(state, viewKind: .content)
.previewDisplayName("Notification")
}
}
struct LiveActivityView: View {
@Environment(\.colorScheme) private var colorScheme
@Environment(\.isLuminanceReduced) var isLuminanceReduced
@ -192,6 +191,7 @@ struct NodeInfoView: View {
var timerRange: ClosedRange<Date>
var body: some View {
let errorRate = (Double(badReceivedPackets) / Double(receivedPackets)) * 100
VStack(alignment: .leading, spacing: 0) {
Text(nodeName)
.font(nodeName.count > 14 ? .callout : .title3)
@ -203,19 +203,13 @@ struct NodeInfoView: View {
.foregroundStyle(.secondary)
.opacity(isLuminanceReduced ? 0.8 : 1.0)
.fixedSize()
Text("Packets Sent: \(sentPackets)")
Text("Packets: Sent \(sentPackets) Rec. \(receivedPackets)")
.font(.caption)
.fontWeight(.medium)
.foregroundStyle(.secondary)
.opacity(isLuminanceReduced ? 0.8 : 1.0)
.fixedSize()
Text("Packets Received: \(receivedPackets)")
.font(.caption)
.fontWeight(.medium)
.foregroundStyle(.secondary)
.opacity(isLuminanceReduced ? 0.8 : 1.0)
.fixedSize()
Text("Bad Packets: \(badReceivedPackets)")
Text("Bad: \(badReceivedPackets) \(String(format: "Error Rate: %.2f", errorRate))%")
.font(.caption)
.fontWeight(.medium)
.foregroundStyle(.secondary)

@ -1 +1 @@
Subproject commit b623762940ebdb1887a3b31b86f4d9cdaa7e6ecf
Subproject commit 0acaec6eff00e748beeae89148093221f131cd9c