mirror of
https://github.com/meshtastic/Meshtastic-Apple.git
synced 2026-04-20 22:13:56 +00:00
Merge pull request #370 from meshtastic/mqtt_client_proxy
Setup MQTT client proxy connection
This commit is contained in:
commit
d8dcff51dc
17 changed files with 783 additions and 41 deletions
|
|
@ -11,6 +11,7 @@
|
|||
C9697FA527933B8C00250207 /* SQLite in Frameworks */ = {isa = PBXBuildFile; productRef = C9697FA427933B8C00250207 /* SQLite */; };
|
||||
DD0D3D222A55CEB10066DB71 /* CocoaMQTT in Frameworks */ = {isa = PBXBuildFile; productRef = DD0D3D212A55CEB10066DB71 /* CocoaMQTT */; };
|
||||
DD0F791B28713C8A00A6FDAD /* AdminMessageList.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD0F791A28713C8A00A6FDAD /* AdminMessageList.swift */; };
|
||||
DD14E72E2A82A614006E39BC /* RemoteHardware.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD14E72D2A82A614006E39BC /* RemoteHardware.swift */; };
|
||||
DD1925B728CDA5A400720036 /* CannedMessagesConfigEnums.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1925B628CDA5A400720036 /* CannedMessagesConfigEnums.swift */; };
|
||||
DD1925B928CDA93900720036 /* SerialConfigEnums.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1925B828CDA93900720036 /* SerialConfigEnums.swift */; };
|
||||
DD1BF2F92776FE2E008C8D2F /* UserMessageList.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1BF2F82776FE2E008C8D2F /* UserMessageList.swift */; };
|
||||
|
|
@ -121,6 +122,7 @@
|
|||
DDCDC6CB29481FCC004C1DDA /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = DDCDC6CD29481FCC004C1DDA /* Localizable.strings */; };
|
||||
DDCE4E2C2869F92900BE9F8F /* UserConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDCE4E2B2869F92900BE9F8F /* UserConfig.swift */; };
|
||||
DDD3BBD5292D763200D609B3 /* MeshtasticTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD3BBD4292D763200D609B3 /* MeshtasticTests.swift */; };
|
||||
DDD43FE32A78C8900083A3E9 /* MqttClientProxyManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD43FE22A78C8900083A3E9 /* MqttClientProxyManager.swift */; };
|
||||
DDD6EEAF29BC024700383354 /* Firmware.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD6EEAE29BC024700383354 /* Firmware.swift */; };
|
||||
DDD94A502845C8F5004A87A0 /* DateTimeText.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD94A4F2845C8F5004A87A0 /* DateTimeText.swift */; };
|
||||
DDD9E4E4284B208E003777C5 /* UserEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD9E4E3284B208E003777C5 /* UserEntityExtension.swift */; };
|
||||
|
|
@ -196,6 +198,8 @@
|
|||
C9697F9C279336B700250207 /* LocalMBTileOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalMBTileOverlay.swift; sourceTree = "<group>"; };
|
||||
DD0E9C222A30CE3A00580CBB /* MeshtasticDataModelV14.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV14.xcdatamodel; sourceTree = "<group>"; };
|
||||
DD0F791A28713C8A00A6FDAD /* AdminMessageList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdminMessageList.swift; sourceTree = "<group>"; };
|
||||
DD14E72C2A80738F006E39BC /* MeshtasticDataModelV15.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV15.xcdatamodel; sourceTree = "<group>"; };
|
||||
DD14E72D2A82A614006E39BC /* RemoteHardware.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteHardware.swift; sourceTree = "<group>"; };
|
||||
DD1925B628CDA5A400720036 /* CannedMessagesConfigEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CannedMessagesConfigEnums.swift; sourceTree = "<group>"; };
|
||||
DD1925B828CDA93900720036 /* SerialConfigEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SerialConfigEnums.swift; sourceTree = "<group>"; };
|
||||
DD1BF2F82776FE2E008C8D2F /* UserMessageList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserMessageList.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -323,6 +327,7 @@
|
|||
DDCDC6CE294821AD004C1DDA /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
DDCE4E2B2869F92900BE9F8F /* UserConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserConfig.swift; sourceTree = "<group>"; };
|
||||
DDD3BBD4292D763200D609B3 /* MeshtasticTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MeshtasticTests.swift; sourceTree = "<group>"; };
|
||||
DDD43FE22A78C8900083A3E9 /* MqttClientProxyManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MqttClientProxyManager.swift; sourceTree = "<group>"; };
|
||||
DDD6EEAE29BC024700383354 /* Firmware.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Firmware.swift; sourceTree = "<group>"; };
|
||||
DDD94A4F2845C8F5004A87A0 /* DateTimeText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateTimeText.swift; sourceTree = "<group>"; };
|
||||
DDD9E4E3284B208E003777C5 /* UserEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserEntityExtension.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -422,6 +427,7 @@
|
|||
DD47E3CD26F103C600029299 /* NodeList.swift */,
|
||||
DD90860D26F69BAE00DC5189 /* NodeMap.swift */,
|
||||
DD73FD1028750779000852D6 /* PositionLog.swift */,
|
||||
DD14E72D2A82A614006E39BC /* RemoteHardware.swift */,
|
||||
);
|
||||
path = Nodes;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -710,6 +716,7 @@
|
|||
DDC2E1A526CEB32B0042C5E4 /* Helpers */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DDD43FE12A78C86B0083A3E9 /* Mqtt */,
|
||||
DDB75A122A0593CD006ED576 /* Map */,
|
||||
DDAF8C5226EB1DF10058C060 /* BLEManager.swift */,
|
||||
DDC2E1A626CEB3400042C5E4 /* LocationHelper.swift */,
|
||||
|
|
@ -736,6 +743,14 @@
|
|||
path = Persistence;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DDD43FE12A78C86B0083A3E9 /* Mqtt */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DDD43FE22A78C8900083A3E9 /* MqttClientProxyManager.swift */,
|
||||
);
|
||||
path = Mqtt;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DDDB443E29F79A9400EE2349 /* Extensions */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
|
@ -1032,6 +1047,7 @@
|
|||
DD3CC6C228EB9D4900FA9159 /* UpdateCoreData.swift in Sources */,
|
||||
DDE0F7C5295F77B700B8AAB3 /* AppSettingsEnums.swift in Sources */,
|
||||
DDB6ABE628B1406100384BA1 /* LoraConfigEnums.swift in Sources */,
|
||||
DDD43FE32A78C8900083A3E9 /* MqttClientProxyManager.swift in Sources */,
|
||||
DDDB444629F8A96500EE2349 /* Character.swift in Sources */,
|
||||
DD23A50F26FD1B4400D9B90C /* PeripheralModel.swift in Sources */,
|
||||
DDB6ABDB28B0AC6000384BA1 /* DistanceText.swift in Sources */,
|
||||
|
|
@ -1098,6 +1114,7 @@
|
|||
DD8169F9271F1A6100F4AB02 /* MeshLogger.swift in Sources */,
|
||||
DD41582A28585C32009B0E59 /* RangeTestConfig.swift in Sources */,
|
||||
DD1925B728CDA5A400720036 /* CannedMessagesConfigEnums.swift in Sources */,
|
||||
DD14E72E2A82A614006E39BC /* RemoteHardware.swift in Sources */,
|
||||
DDDB444429F8A8DD00EE2349 /* Float.swift in Sources */,
|
||||
DD5E5211298EE33B00D21B61 /* remote_hardware.pb.swift in Sources */,
|
||||
DD5E5204298EE33B00D21B61 /* xmodem.pb.swift in Sources */,
|
||||
|
|
@ -1311,7 +1328,7 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.1.16;
|
||||
MARKETING_VERSION = 2.2.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
|
|
@ -1345,7 +1362,7 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.1.16;
|
||||
MARKETING_VERSION = 2.2.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
|
|
@ -1464,7 +1481,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.1.16;
|
||||
MARKETING_VERSION = 2.2.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
|
|
@ -1495,7 +1512,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.1.16;
|
||||
MARKETING_VERSION = 2.2.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
|
|
@ -1605,6 +1622,7 @@
|
|||
DD3CC6BA28E366DF00FA9159 /* Meshtastic.xcdatamodeld */ = {
|
||||
isa = XCVersionGroup;
|
||||
children = (
|
||||
DD14E72C2A80738F006E39BC /* MeshtasticDataModelV15.xcdatamodel */,
|
||||
DD0E9C222A30CE3A00580CBB /* MeshtasticDataModelV14.xcdatamodel */,
|
||||
DDB75A1F2A10766D006ED576 /* MeshtasticDataModelV13.xcdatamodel */,
|
||||
DDB759E12A04B264006ED576 /* MeshtasticDataModelV12.xcdatamodel */,
|
||||
|
|
@ -1620,7 +1638,7 @@
|
|||
DD5D0A9A2931AD6B00F7EA61 /* MeshtasticDataModelV2.xcdatamodel */,
|
||||
DD3CC6BB28E366DF00FA9159 /* MeshtasticDataModel.xcdatamodel */,
|
||||
);
|
||||
currentVersion = DD0E9C222A30CE3A00580CBB /* MeshtasticDataModelV14.xcdatamodel */;
|
||||
currentVersion = DD14E72C2A80738F006E39BC /* MeshtasticDataModelV15.xcdatamodel */;
|
||||
name = Meshtastic.xcdatamodeld;
|
||||
path = Meshtastic/Meshtastic.xcdatamodeld;
|
||||
sourceTree = "<group>";
|
||||
|
|
|
|||
|
|
@ -32,6 +32,13 @@ enum HardwareModels: String, CaseIterable, Identifiable {
|
|||
case M5STACK
|
||||
case HELTECV3
|
||||
case HELTECWSLV3
|
||||
case NANOG2ULTRA
|
||||
case RAK11310
|
||||
case RPIPICO
|
||||
case HELTECWIRELESSTRACKER
|
||||
case HELTECWIRELESSPAPER
|
||||
case TDECK
|
||||
case TWATCHS3
|
||||
|
||||
var id: String { self.rawValue }
|
||||
var description: String {
|
||||
|
|
@ -83,6 +90,20 @@ enum HardwareModels: String, CaseIterable, Identifiable {
|
|||
return "Heltec V3"
|
||||
case .HELTECWSLV3:
|
||||
return "Heltec wireless stick lite V3"
|
||||
case .NANOG2ULTRA:
|
||||
return "Nano G2 Ultra"
|
||||
case .RAK11310:
|
||||
return "RAK 11310 Pi Pico"
|
||||
case .RPIPICO:
|
||||
return "Pi Pico"
|
||||
case .HELTECWIRELESSTRACKER:
|
||||
return "Heltec Wireless Tracker"
|
||||
case .HELTECWIRELESSPAPER:
|
||||
return "Heltec Wireless Paper"
|
||||
case .TDECK:
|
||||
return "T-Deck"
|
||||
case .TWATCHS3:
|
||||
return "T-Watch S3"
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -135,6 +156,20 @@ enum HardwareModels: String, CaseIterable, Identifiable {
|
|||
return ["firmware-heltec-v3-"]
|
||||
case .HELTECWSLV3:
|
||||
return ["firmware-heltec-wsl-v3-"]
|
||||
case .NANOG2ULTRA:
|
||||
return ["firmware-nano-g2-ultra-"]
|
||||
case .RAK11310:
|
||||
return ["firmware-rak11310-"]
|
||||
case .RPIPICO:
|
||||
return ["firmware-pico-"]
|
||||
case .HELTECWIRELESSTRACKER:
|
||||
return ["firmware-heltec-wireless-tracker-"]
|
||||
case .HELTECWIRELESSPAPER:
|
||||
return ["firmware-heltec-wireless-paper-"]
|
||||
case .TDECK:
|
||||
return ["firmware-t-echo-"]
|
||||
case .TWATCHS3:
|
||||
return ["firmware-t-watch-s3-"]
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -188,6 +223,20 @@ enum HardwareModels: String, CaseIterable, Identifiable {
|
|||
return HardwarePlatforms.esp32
|
||||
case .HELTECWSLV3:
|
||||
return HardwarePlatforms.esp32
|
||||
case .NANOG2ULTRA:
|
||||
return HardwarePlatforms.nrf52
|
||||
case .RAK11310:
|
||||
return HardwarePlatforms.piPico
|
||||
case .RPIPICO:
|
||||
return HardwarePlatforms.piPico
|
||||
case .HELTECWIRELESSTRACKER:
|
||||
return HardwarePlatforms.esp32
|
||||
case .HELTECWIRELESSPAPER:
|
||||
return HardwarePlatforms.esp32
|
||||
case .TDECK:
|
||||
return HardwarePlatforms.esp32
|
||||
case .TWATCHS3:
|
||||
return HardwarePlatforms.esp32
|
||||
}
|
||||
}
|
||||
func protoEnumValue() -> HardwareModel {
|
||||
|
|
@ -240,6 +289,20 @@ enum HardwareModels: String, CaseIterable, Identifiable {
|
|||
return HardwareModel.heltecV3
|
||||
case .HELTECWSLV3:
|
||||
return HardwareModel.heltecWslV3
|
||||
case .NANOG2ULTRA:
|
||||
return HardwareModel.nanoG2Ultra
|
||||
case .RAK11310:
|
||||
return HardwareModel.rak11310
|
||||
case .RPIPICO:
|
||||
return HardwareModel.rpiPico
|
||||
case .HELTECWIRELESSTRACKER:
|
||||
return HardwareModel.heltecWirelessTracker
|
||||
case .HELTECWIRELESSPAPER:
|
||||
return HardwareModel.heltecWirelessPaper
|
||||
case .TDECK:
|
||||
return HardwareModel.tDeck
|
||||
case .TWATCHS3:
|
||||
return HardwareModel.tWatchS3
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -135,6 +135,26 @@ enum ModemPresets: Int, CaseIterable, Identifiable {
|
|||
return "Short Range - Fast"
|
||||
}
|
||||
}
|
||||
var name: String {
|
||||
switch self {
|
||||
case .longFast:
|
||||
return "LongFast"
|
||||
case .longSlow:
|
||||
return "LongSlow"
|
||||
case .longModerate:
|
||||
return "LongModerate"
|
||||
case .vLongSlow:
|
||||
return "VLongFast"
|
||||
case .medSlow:
|
||||
return "MediumSlow"
|
||||
case .medFast:
|
||||
return "MediumFast"
|
||||
case .shortSlow:
|
||||
return "ShortSlow"
|
||||
case .shortFast:
|
||||
return "ShortFast"
|
||||
}
|
||||
}
|
||||
func snrLimit() -> Float {
|
||||
switch self {
|
||||
case .longFast:
|
||||
|
|
|
|||
|
|
@ -3,12 +3,52 @@ import CoreData
|
|||
import CoreBluetooth
|
||||
import SwiftUI
|
||||
import MapKit
|
||||
import CocoaMQTT
|
||||
|
||||
// ---------------------------------------------------------------------------------------
|
||||
// Meshtastic BLE Device Manager
|
||||
// ---------------------------------------------------------------------------------------
|
||||
class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
||||
class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate, ObservableObject {
|
||||
|
||||
// MqttClientProxyManagerDelegate
|
||||
func onMqttConnected() {
|
||||
mqttManager.status = .connected
|
||||
print("📲 Mqtt Client Proxy onMqttConnected now subscribing to \(mqttManager.topic).")
|
||||
mqttManager.mqttClientProxy?.subscribe(mqttManager.topic)
|
||||
}
|
||||
|
||||
func onMqttDisconnected() {
|
||||
mqttManager.status = .disconnected
|
||||
print("MQTT Disconnected")
|
||||
}
|
||||
|
||||
func onMqttMessageReceived(message: CocoaMQTTMessage) {
|
||||
|
||||
print("📲 Mqtt Client Proxy onMqttMessageReceived for topic: \(message.topic)")
|
||||
if message.topic.contains("/stat/") {
|
||||
return
|
||||
}
|
||||
var proxyMessage = MqttClientProxyMessage()
|
||||
proxyMessage.topic = message.topic
|
||||
proxyMessage.data = Data(message.payload)
|
||||
proxyMessage.retained = message.retained
|
||||
|
||||
var toRadio: ToRadio!
|
||||
toRadio = ToRadio()
|
||||
toRadio.mqttClientProxyMessage = proxyMessage
|
||||
let binaryData: Data = try! toRadio.serializedData()
|
||||
if connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected {
|
||||
connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse)
|
||||
print("📲 Sent Mqtt client proxy message to the connected device.")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func onMqttError(message: String) {
|
||||
print("MQTT Error")
|
||||
}
|
||||
|
||||
|
||||
private static var documentsFolder: URL {
|
||||
do {
|
||||
return try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
|
||||
|
|
@ -37,6 +77,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
var positionTimer: Timer?
|
||||
var lastPosition: CLLocationCoordinate2D?
|
||||
let emptyNodeNum: UInt32 = 4294967295
|
||||
let mqttManager = MqttClientProxyManager.shared
|
||||
/* Meshtastic Service Details */
|
||||
var TORADIO_characteristic: CBCharacteristic!
|
||||
var FROMRADIO_characteristic: CBCharacteristic!
|
||||
|
|
@ -55,6 +96,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
self.connectedVersion = "0.0.0"
|
||||
super.init()
|
||||
centralManager = CBCentralManager(delegate: self, queue: nil)
|
||||
mqttManager.delegate = self
|
||||
// centralManager = CBCentralManager(delegate: self, queue: nil, options: [CBCentralManagerOptionRestoreIdentifierKey: restoreKey])
|
||||
}
|
||||
|
||||
|
|
@ -272,7 +314,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
|
||||
func requestDeviceMetadata(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32, context: NSManagedObjectContext) -> Int64 {
|
||||
|
||||
guard connectedPeripheral!.peripheral.state == CBPeripheralState.connected else { return 0 }
|
||||
guard connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected else { return 0 }
|
||||
|
||||
var adminPacket = AdminMessage()
|
||||
adminPacket.getDeviceMetadataRequest = true
|
||||
|
|
@ -298,7 +340,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
func sendTraceRouteRequest(destNum: Int64, wantResponse: Bool) -> Bool {
|
||||
|
||||
var success = false
|
||||
guard connectedPeripheral!.peripheral.state == CBPeripheralState.connected else { return success }
|
||||
guard connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected else { return success }
|
||||
|
||||
let fromNodeNum = connectedPeripheral.num
|
||||
let routePacket = RouteDiscovery()
|
||||
|
|
@ -317,7 +359,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
toRadio.packet = meshPacket
|
||||
let binaryData: Data = try! toRadio.serializedData()
|
||||
|
||||
if connectedPeripheral!.peripheral.state == CBPeripheralState.connected {
|
||||
if connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected {
|
||||
connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse)
|
||||
success = true
|
||||
|
||||
|
|
@ -328,7 +370,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
}
|
||||
|
||||
func sendWantConfig() {
|
||||
guard connectedPeripheral!.peripheral.state == CBPeripheralState.connected else { return }
|
||||
guard connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected else { return }
|
||||
|
||||
if FROMRADIO_characteristic == nil {
|
||||
MeshLogger.log("🚨 \("firmware.version.unsupported".localized)")
|
||||
|
|
@ -391,6 +433,17 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
print(characteristic.value!)
|
||||
}
|
||||
|
||||
// Publish mqttClientProxyMessages received on the from radio
|
||||
if decodedInfo.payloadVariant == FromRadio.OneOf_PayloadVariant.mqttClientProxyMessage(decodedInfo.mqttClientProxyMessage) {
|
||||
let message = CocoaMQTTMessage (
|
||||
topic: decodedInfo.mqttClientProxyMessage.topic,
|
||||
payload: [UInt8](decodedInfo.mqttClientProxyMessage.data),
|
||||
retained: decodedInfo.mqttClientProxyMessage.retained
|
||||
)
|
||||
print("📲 Publish Mqtt client proxy message received on FromRadio to the Mqtt server \(message)")
|
||||
mqttManager.mqttClientProxy?.publish(message)
|
||||
}
|
||||
|
||||
switch decodedInfo.packet.decoded.portnum {
|
||||
|
||||
// Handle Any local only packets we get over BLE
|
||||
|
|
@ -538,22 +591,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
print("MAX PORT NUM OF 511")
|
||||
}
|
||||
|
||||
// MARK: Check for an All / Broadcast User and delete it as a transition to multi channel
|
||||
let fetchBCUserRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "UserEntity")
|
||||
fetchBCUserRequest.predicate = NSPredicate(format: "num == %lld", Int64(emptyNodeNum))
|
||||
|
||||
do {
|
||||
guard let fetchedUser = try context?.fetch(fetchBCUserRequest) as? [UserEntity] else {
|
||||
return
|
||||
}
|
||||
if fetchedUser.count > 0 {
|
||||
context?.delete(fetchedUser[0])
|
||||
print("🗑️ Deleted the All - Broadcast User")
|
||||
}
|
||||
} catch {
|
||||
print("💥 Error Deleting the All - Broadcast User")
|
||||
}
|
||||
|
||||
if decodedInfo.configCompleteID != 0 && decodedInfo.configCompleteID == configNonce {
|
||||
invalidVersion = false
|
||||
lastConnectionError = ""
|
||||
|
|
@ -561,6 +598,26 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
print("🤜 Want Config Complete. ID:\(decodedInfo.configCompleteID)")
|
||||
peripherals.removeAll(where: { $0.peripheral.state == CBPeripheralState.disconnected })
|
||||
// Config conplete returns so we don't read the characteristic again
|
||||
|
||||
/// MQTT Client Proxy
|
||||
if connectedPeripheral.num > 0 {
|
||||
|
||||
let fetchNodeInfoRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "NodeInfoEntity")
|
||||
fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(connectedPeripheral.num))
|
||||
do {
|
||||
let fetchedNodeInfo = try context?.fetch(fetchNodeInfoRequest) as? [NodeInfoEntity] ?? []
|
||||
if fetchedNodeInfo.count == 1 && fetchedNodeInfo[0].mqttConfig != nil {
|
||||
|
||||
//Subscribe to Mqtt Client Proxy if enabled
|
||||
if fetchedNodeInfo[0].mqttConfig?.proxyToClientEnabled ?? false {
|
||||
mqttManager.connectFromConfigSettings(node: fetchedNodeInfo[0])
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
print("Failed to find a node info for the connected node")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Share Location Position Update Timer
|
||||
// Use context to pass the radio name with the timer
|
||||
// Use a RunLoop to prevent the timer from running on the main UI thread
|
||||
|
|
@ -573,8 +630,10 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
RunLoop.current.add(positionTimer!, forMode: .common)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
case FROMNUM_UUID:
|
||||
print("🗞️ BLE (Notify) characteristic, value will be read next")
|
||||
|
|
@ -587,6 +646,8 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public func sendMessage(message: String, toUserNum: Int64, channel: Int32, isEmoji: Bool, replyID: Int64) -> Bool {
|
||||
var success = false
|
||||
|
||||
|
|
@ -676,7 +737,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
toRadio = ToRadio()
|
||||
toRadio.packet = meshPacket
|
||||
let binaryData: Data = try! toRadio.serializedData()
|
||||
if connectedPeripheral!.peripheral.state == CBPeripheralState.connected {
|
||||
if connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected {
|
||||
connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse)
|
||||
let logString = String.localizedStringWithFormat("mesh.log.textmessage.sent %@ %@ %@".localized, String(newMessage.messageId), String(fromUserNum), String(toUserNum))
|
||||
|
||||
|
|
@ -721,7 +782,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
let binaryData: Data = try! toRadio.serializedData()
|
||||
let logString = String.localizedStringWithFormat("mesh.log.waypoint.sent %@".localized, String(fromNodeNum))
|
||||
MeshLogger.log("📍 \(logString)")
|
||||
if connectedPeripheral!.peripheral.state == CBPeripheralState.connected {
|
||||
if connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected {
|
||||
connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse)
|
||||
success = true
|
||||
let wayPointEntity = getWaypoint(id: Int64(waypoint.id), context: context!)
|
||||
|
|
@ -803,7 +864,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
toRadio = ToRadio()
|
||||
toRadio.packet = meshPacket
|
||||
let binaryData: Data = try! toRadio.serializedData()
|
||||
if connectedPeripheral!.peripheral.state == CBPeripheralState.connected {
|
||||
if connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected {
|
||||
connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse)
|
||||
success = true
|
||||
let logString = String.localizedStringWithFormat("mesh.log.sharelocation %@".localized, String(fromNodeNum))
|
||||
|
|
@ -1026,7 +1087,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
toRadio = ToRadio()
|
||||
toRadio.packet = meshPacket
|
||||
let binaryData: Data = try! toRadio.serializedData()
|
||||
if connectedPeripheral!.peripheral.state == CBPeripheralState.connected {
|
||||
if connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected {
|
||||
self.connectedPeripheral.peripheral.writeValue(binaryData, for: self.TORADIO_characteristic, type: .withResponse)
|
||||
let logString = String.localizedStringWithFormat("mesh.log.channel.sent %@ %d".localized, String(connectedPeripheral.num), chan.index)
|
||||
MeshLogger.log("🎛️ \(logString)")
|
||||
|
|
@ -1050,7 +1111,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
toRadio = ToRadio()
|
||||
toRadio.packet = meshPacket
|
||||
let binaryData: Data = try! toRadio.serializedData()
|
||||
if connectedPeripheral!.peripheral.state == CBPeripheralState.connected {
|
||||
if connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected {
|
||||
self.connectedPeripheral.peripheral.writeValue(binaryData, for: self.TORADIO_characteristic, type: .withResponse)
|
||||
let logString = String.localizedStringWithFormat("mesh.log.lora.config.sent %@".localized, String(connectedPeripheral.num))
|
||||
MeshLogger.log("📻 \(logString)")
|
||||
|
|
@ -1527,7 +1588,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
|
||||
let binaryData: Data = try! toRadio.serializedData()
|
||||
|
||||
if connectedPeripheral!.peripheral.state == CBPeripheralState.connected {
|
||||
if connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected {
|
||||
connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse)
|
||||
let logString = String.localizedStringWithFormat("mesh.log.cannedmessages.messages.get %@".localized, String(connectedPeripheral.num))
|
||||
MeshLogger.log("🥫 \(logString)")
|
||||
|
|
@ -1902,7 +1963,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject {
|
|||
toRadio.packet = meshPacket
|
||||
let binaryData: Data = try! toRadio.serializedData()
|
||||
|
||||
if connectedPeripheral!.peripheral.state == CBPeripheralState.connected {
|
||||
if connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected{
|
||||
let newMessage = MessageEntity(context: context!)
|
||||
newMessage.messageId = Int64(meshPacket.id)
|
||||
newMessage.messageTimestamp = Int32(Date().timeIntervalSince1970)
|
||||
|
|
|
|||
219
Meshtastic/Helpers/Mqtt/MqttClientProxyManager.swift
Normal file
219
Meshtastic/Helpers/Mqtt/MqttClientProxyManager.swift
Normal file
|
|
@ -0,0 +1,219 @@
|
|||
//
|
||||
// MQTTManager.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Created by Garth Vander Houwen on 7/31/23.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CocoaMQTT
|
||||
|
||||
protocol MqttClientProxyManagerDelegate: AnyObject {
|
||||
func onMqttConnected()
|
||||
func onMqttDisconnected()
|
||||
func onMqttMessageReceived(message: CocoaMQTTMessage)
|
||||
func onMqttError(message: String)
|
||||
}
|
||||
|
||||
class MqttClientProxyManager {
|
||||
enum ConnectionStatus {
|
||||
case connecting
|
||||
case connected
|
||||
case disconnecting
|
||||
case disconnected
|
||||
case error
|
||||
case none
|
||||
}
|
||||
|
||||
enum MqttQos: Int {
|
||||
case atMostOnce = 0
|
||||
case atLeastOnce = 1
|
||||
case exactlyOnce = 2
|
||||
}
|
||||
|
||||
// Singleton Instance
|
||||
static let shared = MqttClientProxyManager()
|
||||
|
||||
private static let defaultKeepAliveInterval: Int32 = 60
|
||||
|
||||
weak var delegate: MqttClientProxyManagerDelegate?
|
||||
var status = ConnectionStatus.none
|
||||
|
||||
var mqttClientProxy: CocoaMQTT?
|
||||
|
||||
var topic = "msh/2/c"
|
||||
|
||||
private init() {
|
||||
|
||||
}
|
||||
|
||||
func connectFromConfigSettings(node: NodeInfoEntity) {
|
||||
|
||||
let defaultServerAddress = "mqtt.meshtastic.org"
|
||||
let useSsl = node.mqttConfig?.tlsEnabled == true
|
||||
var defaultServerPort = useSsl ? 8883 : 1883
|
||||
var host = node.mqttConfig?.address
|
||||
if host == nil || host!.isEmpty {
|
||||
host = defaultServerAddress
|
||||
}
|
||||
else if host != nil && host!.contains(":") {
|
||||
host = host!.components(separatedBy: ":")[0]
|
||||
defaultServerPort = Int(host!.components(separatedBy: ":")[1])!
|
||||
}
|
||||
|
||||
if let host = host {
|
||||
let port = defaultServerPort
|
||||
let username = node.mqttConfig?.username
|
||||
let password = node.mqttConfig?.password
|
||||
|
||||
let root = node.mqttConfig?.root?.count ?? 0 > 0 ? node.mqttConfig?.root : "msh"
|
||||
let prefix = root! + "/2/c"
|
||||
topic = prefix + "/#"
|
||||
let qos = CocoaMQTTQoS(rawValue :UInt8(1))!
|
||||
connect(host: host, port: port, useSsl: useSsl, username: username, password: password, topic: topic, qos: qos, cleanSession: true)
|
||||
}
|
||||
}
|
||||
|
||||
func connect(host: String, port: Int, useSsl: Bool, username: String?, password: String?, topic: String?, qos: CocoaMQTTQoS, cleanSession: Bool) {
|
||||
|
||||
guard !host.isEmpty else {
|
||||
delegate?.onMqttDisconnected()
|
||||
return
|
||||
}
|
||||
|
||||
status = .connecting
|
||||
|
||||
let clientId = "MeshtasticAppleMqttProxy-" + String(ProcessInfo().processIdentifier)
|
||||
|
||||
mqttClientProxy = CocoaMQTT(clientID: clientId, host: host, port: UInt16(port))
|
||||
if let mqttClient = mqttClientProxy {
|
||||
|
||||
mqttClient.enableSSL = useSsl
|
||||
mqttClient.allowUntrustCACertificate = true
|
||||
mqttClient.username = username
|
||||
mqttClient.password = password
|
||||
mqttClient.keepAlive = 60
|
||||
mqttClient.cleanSession = cleanSession
|
||||
#if DEBUG
|
||||
mqttClient.logLevel = .debug
|
||||
#endif
|
||||
mqttClient.willMessage = CocoaMQTTMessage(topic: "/will", string: "dieout")
|
||||
mqttClient.autoReconnect = true
|
||||
mqttClient.delegate = self
|
||||
let success = mqttClient.connect()
|
||||
if !success {
|
||||
delegate?.onMqttError(message: "Mqtt connect error")
|
||||
status = .error
|
||||
}
|
||||
} else {
|
||||
delegate?.onMqttError(message: "Mqtt initialization error")
|
||||
status = .error
|
||||
}
|
||||
}
|
||||
|
||||
func subscribe(topic: String, qos: MqttQos) {
|
||||
print("📲 MQTT Client Proxy subscribed to: " + topic)
|
||||
let qos = CocoaMQTTQoS(rawValue :UInt8(qos.rawValue))!
|
||||
mqttClientProxy?.subscribe(topic, qos: qos)
|
||||
}
|
||||
|
||||
func unsubscribe(topic: String) {
|
||||
mqttClientProxy?.unsubscribe(topic)
|
||||
print("📲 MQTT Client Proxy unsubscribe for: " + topic)
|
||||
}
|
||||
|
||||
func publish(message: String, topic: String, qos: MqttQos) {
|
||||
let qos = CocoaMQTTQoS(rawValue :UInt8(qos.rawValue))!
|
||||
mqttClientProxy?.publish(topic, withString: message, qos: qos)
|
||||
print("📲 MQTT Client Proxy publish for: " + topic)
|
||||
}
|
||||
|
||||
func disconnect() {
|
||||
//MqttSettings.shared.isConnected = false
|
||||
|
||||
if let client = mqttClientProxy {
|
||||
status = .disconnecting
|
||||
client.disconnect()
|
||||
print("📲 MQTT Client Proxy Disconnected")
|
||||
} else {
|
||||
status = .disconnected
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension MqttClientProxyManager: CocoaMQTTDelegate {
|
||||
|
||||
func mqtt(_ mqtt: CocoaMQTT, didConnectAck ack: CocoaMQTTConnAck) {
|
||||
|
||||
print("📲 MQTT Client Proxy didConnectAck: \(ack)")
|
||||
if ack == .accept {
|
||||
delegate?.onMqttConnected()
|
||||
} else {
|
||||
// Connection error
|
||||
var errorDescription = "Unknown Error"
|
||||
switch ack {
|
||||
case .accept:
|
||||
errorDescription = "No Error"
|
||||
case .unacceptableProtocolVersion:
|
||||
errorDescription = "Proto ver"
|
||||
case .identifierRejected:
|
||||
errorDescription = "Invalid Id"
|
||||
case .serverUnavailable:
|
||||
errorDescription = "Invalid Server"
|
||||
case .badUsernameOrPassword:
|
||||
errorDescription = "Invalid Credentials"
|
||||
case .notAuthorized:
|
||||
errorDescription = "Authorization Error"
|
||||
default:
|
||||
errorDescription = "Unknown Error"
|
||||
}
|
||||
print(errorDescription)
|
||||
delegate?.onMqttError(message: errorDescription)
|
||||
|
||||
//self.disconnect() // Stop reconnecting
|
||||
//mqttSettings.isConnected = false // Disable automatic connect on start
|
||||
}
|
||||
|
||||
self.status = ack == .accept ? ConnectionStatus.connected : ConnectionStatus.error // Set AFTER sending onMqttError (so the delegate can detect that was an error while establishing connection)
|
||||
}
|
||||
|
||||
func mqttDidDisconnect(_ mqtt: CocoaMQTT, withError err: Error?) {
|
||||
print("mqttDidDisconnect: \(err?.localizedDescription ?? "")")
|
||||
|
||||
if let error = err, status == .connecting {
|
||||
delegate?.onMqttError(message: error.localizedDescription)
|
||||
}
|
||||
|
||||
status = err == nil ? .disconnected : .error
|
||||
delegate?.onMqttDisconnected()
|
||||
}
|
||||
|
||||
func mqtt(_ mqtt: CocoaMQTT, didPublishMessage message: CocoaMQTTMessage, id: UInt16) {
|
||||
print("📲 MQTT Client Proxy didPublishMessage from MqttClientProxyManager: \(message)")
|
||||
}
|
||||
|
||||
func mqtt(_ mqtt: CocoaMQTT, didPublishAck id: UInt16) {
|
||||
print("📲 MQTT Client Proxy didPublishAck from MqttClientProxyManager: \(id)")
|
||||
}
|
||||
|
||||
public func mqtt(_ mqtt: CocoaMQTT, didReceiveMessage message: CocoaMQTTMessage, id: UInt16) {
|
||||
delegate?.onMqttMessageReceived(message: message)
|
||||
print("📲 MQTT Client Proxy message received on topic: \(message.topic)")
|
||||
}
|
||||
|
||||
func mqtt(_ mqtt: CocoaMQTT, didSubscribeTopics success: NSDictionary, failed: [String]) {
|
||||
print("📲 MQTT Client Proxy didSubscribeTopics: \(success.allKeys.count) topics. failed: \(failed.count) topics")
|
||||
}
|
||||
|
||||
func mqtt(_ mqtt: CocoaMQTT, didUnsubscribeTopics topics: [String]) {
|
||||
print("didUnsubscribeTopics: \(topics.joined(separator: ", "))")
|
||||
}
|
||||
|
||||
func mqttDidPing(_ mqtt: CocoaMQTT) {
|
||||
print("📲 MQTT Client Proxy mqttDidPing")
|
||||
}
|
||||
|
||||
func mqttDidReceivePong(_ mqtt: CocoaMQTT) {
|
||||
print("📲 MQTT Client Proxy mqttDidReceivePong")
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,6 @@
|
|||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>_XCCurrentVersionName</key>
|
||||
<string>MeshtasticDataModelV14.xcdatamodel</string>
|
||||
<string>MeshtasticDataModelV15.xcdatamodel</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,332 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="21754" systemVersion="22F82" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
||||
<entity name="BluetoothConfigEntity" representedClassName="BluetoothConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="fixedPin" optional="YES" attributeType="Integer 32" defaultValueString="123456" usesScalarValueType="YES"/>
|
||||
<attribute name="mode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="bluetoothConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="bluetoothConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="CannedMessageConfigEntity" representedClassName="CannedMessageConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerEventCcw" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerEventCw" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerEventPress" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerPinA" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerPinB" optional="YES" attributeType="Integer 32" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerPinPress" optional="YES" attributeType="Integer 32" usesScalarValueType="YES"/>
|
||||
<attribute name="messages" optional="YES" attributeType="String" minValueString="0" maxValueString="198"/>
|
||||
<attribute name="rotary1Enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="sendBell" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="updown1Enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<relationship name="cannedMessagesConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="cannedMessageConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="ChannelEntity" representedClassName="ChannelEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="downlinkEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="id" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="index" attributeType="Integer 32" minValueString="0" maxValueString="13" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="mute" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="name" optional="YES" attributeType="String"/>
|
||||
<attribute name="psk" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="role" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="uplinkEnabled" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<relationship name="myInfoChannel" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MyInfoEntity" inverseName="channels" inverseEntity="MyInfoEntity"/>
|
||||
<fetchedProperty name="allPrivateMessages" optional="YES">
|
||||
<fetchRequest name="fetchedPropertyFetchRequest" entity="MessageEntity" predicateString="channel == $FETCH_SOURCE.index && toUser == nil AND isEmoji == false"/>
|
||||
</fetchedProperty>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="index"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="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="doubleTapAsButtonPress" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="isManaged" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="nodeInfoBroadcastSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="rebroadcastMode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="role" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="serialEnabled" optional="YES" attributeType="Boolean" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="deviceConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="deviceConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="DeviceMetadataEntity" representedClassName="DeviceMetadataEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="canShutdown" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="deviceStateVersion" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="firmwareVersion" optional="YES" attributeType="String"/>
|
||||
<attribute name="hasBluetooth" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="hasEthernet" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="hasWifi" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="hwModel" optional="YES" attributeType="String"/>
|
||||
<attribute name="positionFlags" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="role" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="metadataNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="metadata" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="DisplayConfigEntity" representedClassName="DisplayConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="compassNorthTop" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="displayMode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="flipScreen" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="gpsFormat" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="headingBold" optional="YES" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<attribute name="oledType" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="screenCarouselInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="screenOnSeconds" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="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="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" attributeType="Integer 32" usesScalarValueType="YES"/>
|
||||
<attribute name="latitude" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="longitude" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="speed" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="routeLocation" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="RouteEntity" inverseName="locations" inverseEntity="RouteEntity"/>
|
||||
</entity>
|
||||
<entity name="LoRaConfigEntity" representedClassName="LoRaConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="bandwidth" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="channelNum" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="codingRate" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="frequencyOffset" optional="YES" attributeType="Float" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="hopLimit" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="modemPreset" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="overrideDutyCycle" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="overrideFrequency" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="regionCode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="spreadFactor" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="sx126xRxBoostedGain" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="txEnabled" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<attribute name="txPower" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="usePreset" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<relationship name="loRaConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="loRaConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="MessageEntity" representedClassName="MessageEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="ackError" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="ackSNR" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="ackTimestamp" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="admin" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="adminDescription" optional="YES" attributeType="String"/>
|
||||
<attribute name="channel" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="isEmoji" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="messageId" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="messagePayload" optional="YES" attributeType="String" defaultValueString=""/>
|
||||
<attribute name="messagePayloadMarkdown" optional="YES" attributeType="String"/>
|
||||
<attribute name="messageTimestamp" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="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"/>
|
||||
<fetchedProperty name="tapbacks" optional="YES">
|
||||
<fetchRequest name="fetchedPropertyFetchRequest" entity="MessageEntity" predicateString="replyID == $FETCH_SOURCE.messageId AND isEmoji == true"/>
|
||||
</fetchedProperty>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="messageId"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="MQTTConfigEntity" representedClassName="MQTTConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="address" optional="YES" attributeType="String" maxValueString="30"/>
|
||||
<attribute name="enabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="encryptionEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="jsonEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="password" optional="YES" attributeType="String" maxValueString="30"/>
|
||||
<attribute name="proxyToClientEnabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="root" optional="YES" attributeType="String" defaultValueString="msh"/>
|
||||
<attribute name="tlsEnabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="username" optional="YES" attributeType="String" maxValueString="30"/>
|
||||
<relationship name="mqttConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="mqttConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="MyInfoEntity" representedClassName="MyInfoEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="adminIndex" optional="YES" attributeType="Integer 32" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="bleName" optional="YES" attributeType="String"/>
|
||||
<attribute name="minAppVersion" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="myNodeNum" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="peripheralId" optional="YES" attributeType="String"/>
|
||||
<attribute name="rebootCount" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="channels" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="ChannelEntity" inverseName="myInfoChannel" inverseEntity="ChannelEntity"/>
|
||||
<relationship name="myInfoNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="myInfo" inverseEntity="NodeInfoEntity"/>
|
||||
<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="id" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="lastHeard" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="num" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="peripheralId" optional="YES" attributeType="String"/>
|
||||
<attribute name="rssi" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<relationship name="bluetoothConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="BluetoothConfigEntity" inverseName="bluetoothConfigNode" inverseEntity="BluetoothConfigEntity"/>
|
||||
<relationship name="cannedMessageConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="CannedMessageConfigEntity" inverseName="cannedMessagesConfigNode" inverseEntity="CannedMessageConfigEntity"/>
|
||||
<relationship name="deviceConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="DeviceConfigEntity" inverseName="deviceConfigNode" inverseEntity="DeviceConfigEntity"/>
|
||||
<relationship name="displayConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="DisplayConfigEntity" inverseName="displayConfigNode" inverseEntity="DisplayConfigEntity"/>
|
||||
<relationship name="externalNotificationConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="ExternalNotificationConfigEntity" inverseName="externalNotificationConfigNode" inverseEntity="ExternalNotificationConfigEntity"/>
|
||||
<relationship name="loRaConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="LoRaConfigEntity" inverseName="loRaConfigNode" inverseEntity="LoRaConfigEntity"/>
|
||||
<relationship name="metadata" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="DeviceMetadataEntity" inverseName="metadataNode" inverseEntity="DeviceMetadataEntity"/>
|
||||
<relationship name="mqttConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MQTTConfigEntity" inverseName="mqttConfigNode" inverseEntity="MQTTConfigEntity"/>
|
||||
<relationship name="myInfo" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MyInfoEntity" inverseName="myInfoNode" inverseEntity="MyInfoEntity"/>
|
||||
<relationship name="networkConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NetworkConfigEntity" inverseName="networkConfigNode" inverseEntity="NetworkConfigEntity"/>
|
||||
<relationship name="positionConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="PositionConfigEntity" inverseName="positionConfigNode" inverseEntity="PositionConfigEntity"/>
|
||||
<relationship name="positions" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="PositionEntity" inverseName="nodePosition" inverseEntity="PositionEntity"/>
|
||||
<relationship name="rangeTestConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="RangeTestConfigEntity" inverseName="rangeTestConfigNode" inverseEntity="RangeTestConfigEntity"/>
|
||||
<relationship name="rtttlConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="RTTTLConfigEntity" inverseName="rtttlConfigNode" inverseEntity="RTTTLConfigEntity"/>
|
||||
<relationship name="serialConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="SerialConfigEntity" inverseName="serialConfigNode" inverseEntity="SerialConfigEntity"/>
|
||||
<relationship name="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="user" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="UserEntity" inverseName="userNode" inverseEntity="UserEntity"/>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="num"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="PositionConfigEntity" representedClassName="PositionConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="broadcastSmartMinimumDistance" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="broadcastSmartMinimumIntervalSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="deviceGpsEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="fixedPosition" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="gpsAttemptTime" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="gpsUpdateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="positionBroadcastSeconds" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="positionFlags" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="rxGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="smartPositionEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="txGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="positionConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="positionConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="PositionEntity" representedClassName="PositionEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="altitude" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="heading" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="latest" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="latitudeI" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="longitudeI" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="rssi" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="satsInView" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="seqNo" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="speed" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="time" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<relationship name="nodePosition" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="positions" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="RangeTestConfigEntity" representedClassName="RangeTestConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="save" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="sender" optional="YES" attributeType="Integer 32" usesScalarValueType="YES"/>
|
||||
<relationship name="rangeTestConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="rangeTestConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="RouteEntity" representedClassName="RouteEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="color" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="date" 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" maxCount="1" deletionRule="Nullify" destinationEntity="LocationEntity" inverseName="routeLocation" inverseEntity="LocationEntity"/>
|
||||
</entity>
|
||||
<entity name="RTTTLConfigEntity" representedClassName="RTTTLConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="ringtone" optional="YES" attributeType="String" maxValueString="228" defaultValueString=""/>
|
||||
<relationship name="rtttlConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="rtttlConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="SerialConfigEntity" representedClassName="SerialConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="baudRate" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="echo" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="mode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="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="TelemetryConfigEntity" representedClassName="TelemetryConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="deviceUpdateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="environmentDisplayFahrenheit" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="environmentMeasurementEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="environmentScreenEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="environmentUpdateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="telemetryConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="telemetryConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="TelemetryEntity" representedClassName="TelemetryEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="airUtilTx" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="barometricPressure" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="batteryLevel" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="channelUtilization" optional="YES" attributeType="Float" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="current" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="gasResistance" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="metricsType" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="relativeHumidity" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="rssi" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="temperature" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="time" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="voltage" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<relationship name="nodeTelemetry" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="telemetries" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="UserEntity" representedClassName="UserEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="hwModel" attributeType="String"/>
|
||||
<attribute name="isLicensed" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="longName" attributeType="String"/>
|
||||
<attribute name="mute" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="num" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="shortName" attributeType="String"/>
|
||||
<attribute name="userId" attributeType="String"/>
|
||||
<relationship name="receivedMessages" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="MessageEntity" inverseName="toUser" inverseEntity="MessageEntity"/>
|
||||
<relationship name="sentMessages" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="MessageEntity" inverseName="fromUser" inverseEntity="MessageEntity"/>
|
||||
<relationship name="userNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="user" inverseEntity="NodeInfoEntity"/>
|
||||
<fetchedProperty name="adminMessages" optional="YES">
|
||||
<fetchRequest name="fetchedPropertyFetchRequest" entity="MessageEntity" predicateString="(fromUser.num == $FETCH_SOURCE.num) AND isEmoji == false AND admin = true"/>
|
||||
</fetchedProperty>
|
||||
<fetchedProperty name="allMessages" optional="YES">
|
||||
<fetchRequest name="fetchedPropertyFetchRequest" entity="MessageEntity" predicateString="((toUser.num == $FETCH_SOURCE.num) OR (fromUser.num == $FETCH_SOURCE.num)) AND toUser != nil AND fromUser != nil AND isEmoji == false AND admin = false"/>
|
||||
</fetchedProperty>
|
||||
</entity>
|
||||
<entity name="WaypointEntity" representedClassName="WaypointEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="created" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="expire" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="icon" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="id" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="lastUpdated" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="latitudeI" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="locked" attributeType="Integer 64" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="longDescription" optional="YES" attributeType="String" maxValueString="100"/>
|
||||
<attribute name="longitudeI" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="name" attributeType="String" minValueString="1" maxValueString="30"/>
|
||||
</entity>
|
||||
</model>
|
||||
|
|
@ -776,6 +776,7 @@ func upsertMqttModuleConfigPacket(config: Meshtastic.ModuleConfig.MQTTConfig, no
|
|||
if fetchedNode[0].mqttConfig == nil {
|
||||
let newMQTTConfig = MQTTConfigEntity(context: context)
|
||||
newMQTTConfig.enabled = config.enabled
|
||||
newMQTTConfig.proxyToClientEnabled = config.proxyToClientEnabled
|
||||
newMQTTConfig.address = config.address
|
||||
newMQTTConfig.username = config.username
|
||||
newMQTTConfig.password = config.password
|
||||
|
|
@ -786,6 +787,7 @@ func upsertMqttModuleConfigPacket(config: Meshtastic.ModuleConfig.MQTTConfig, no
|
|||
fetchedNode[0].mqttConfig = newMQTTConfig
|
||||
} else {
|
||||
fetchedNode[0].mqttConfig?.enabled = config.enabled
|
||||
fetchedNode[0].mqttConfig?.proxyToClientEnabled = config.proxyToClientEnabled
|
||||
fetchedNode[0].mqttConfig?.address = config.address
|
||||
fetchedNode[0].mqttConfig?.username = config.username
|
||||
fetchedNode[0].mqttConfig?.password = config.password
|
||||
|
|
|
|||
|
|
@ -23,10 +23,10 @@ struct DeviceMetricsLog: View {
|
|||
|
||||
var body: some View {
|
||||
|
||||
let oneDayAgo = Calendar.current.date(byAdding: .day, value: -1, to: Date())
|
||||
let oneWeekAgo = Calendar.current.date(byAdding: .day, value: -7, to: Date())
|
||||
let deviceMetrics = node.telemetries?.filtered(using: NSPredicate(format: "metricsType == 0")).reversed() as? [TelemetryEntity] ?? []
|
||||
let chartData = deviceMetrics
|
||||
.filter { $0.time != nil && $0.time! >= oneDayAgo! }
|
||||
.filter { $0.time != nil && $0.time! >= oneWeekAgo! }
|
||||
.sorted { $0.time! < $1.time! }
|
||||
|
||||
NavigationStack {
|
||||
|
|
@ -48,6 +48,7 @@ struct DeviceMetricsLog: View {
|
|||
.accessibilityValue("X: \(point.time!), Y: \(point.batteryLevel)")
|
||||
.foregroundStyle(batteryChartColor)
|
||||
.interpolationMethod(.cardinal)
|
||||
//.interpolationMethod(.catmullRom(alpha: 1.0))
|
||||
|
||||
Plot {
|
||||
PointMark(
|
||||
|
|
|
|||
|
|
@ -206,7 +206,7 @@ struct EnvironmentMetricsLog: View {
|
|||
isPresented: $isExporting,
|
||||
document: CsvDocument(emptyCsv: exportString),
|
||||
contentType: .commaSeparatedText,
|
||||
defaultFilename: String("\(node.user!.longName ?? "Node") Environment Metrics Log"),
|
||||
defaultFilename: String("\(node.user?.longName ?? "Node") Environment Metrics Log"),
|
||||
onCompletion: { result in
|
||||
if case .success = result {
|
||||
print("Environment metrics log download succeeded.")
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ struct NodeList: View {
|
|||
.padding([.top, .bottom])
|
||||
}
|
||||
}
|
||||
.navigationTitle("nodes")
|
||||
.navigationTitle(String.localizedStringWithFormat("nodes %@".localized, String(nodes.count)))
|
||||
.navigationBarItems(leading:
|
||||
MeshtasticLogo()
|
||||
)
|
||||
|
|
|
|||
8
Meshtastic/Views/Nodes/RemoteHardware.swift
Normal file
8
Meshtastic/Views/Nodes/RemoteHardware.swift
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
//
|
||||
// RemoteHardware.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Created by Garth Vander Houwen on 8/8/23.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
|
@ -15,6 +15,7 @@ struct MQTTConfig: View {
|
|||
@State private var isPresentingSaveConfirm: Bool = false
|
||||
@State var hasChanges: Bool = false
|
||||
@State var enabled = false
|
||||
@State var proxyToClientEnabled = false
|
||||
@State var address = ""
|
||||
@State var username = ""
|
||||
@State var password = ""
|
||||
|
|
@ -59,6 +60,11 @@ struct MQTTConfig: View {
|
|||
Label("enabled", systemImage: "dot.radiowaves.right")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
Toggle(isOn: $proxyToClientEnabled) {
|
||||
|
||||
Label("mqtt.clientproxy", systemImage: "iphone.radiowaves.left.and.right")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
|
||||
Toggle(isOn: $encryptionEnabled) {
|
||||
|
||||
|
|
@ -209,6 +215,7 @@ struct MQTTConfig: View {
|
|||
Button(buttonText) {
|
||||
var mqtt = ModuleConfig.MQTTConfig()
|
||||
mqtt.enabled = self.enabled
|
||||
mqtt.proxyToClientEnabled = self.proxyToClientEnabled
|
||||
mqtt.address = self.address
|
||||
mqtt.username = self.username
|
||||
mqtt.password = self.password
|
||||
|
|
@ -272,6 +279,11 @@ struct MQTTConfig: View {
|
|||
if newEnabled != node!.mqttConfig!.enabled { hasChanges = true }
|
||||
}
|
||||
}
|
||||
.onChange(of: proxyToClientEnabled) { newProxyToClientEnabled in
|
||||
if node != nil && node?.mqttConfig != nil {
|
||||
if newProxyToClientEnabled != node!.mqttConfig!.proxyToClientEnabled { hasChanges = true }
|
||||
}
|
||||
}
|
||||
.onChange(of: encryptionEnabled) { newEncryptionEnabled in
|
||||
if node != nil && node?.mqttConfig != nil {
|
||||
if newEncryptionEnabled != node!.mqttConfig!.encryptionEnabled { hasChanges = true }
|
||||
|
|
@ -291,6 +303,7 @@ struct MQTTConfig: View {
|
|||
|
||||
func setMqttValues() {
|
||||
self.enabled = (node?.mqttConfig?.enabled ?? false)
|
||||
self.proxyToClientEnabled = (node?.mqttConfig?.proxyToClientEnabled ?? false)
|
||||
self.address = node?.mqttConfig?.address ?? ""
|
||||
self.username = node?.mqttConfig?.username ?? ""
|
||||
self.password = node?.mqttConfig?.password ?? ""
|
||||
|
|
|
|||
|
|
@ -302,6 +302,8 @@ struct PositionConfig: View {
|
|||
pc.positionBroadcastSecs = UInt32(positionBroadcastSeconds)
|
||||
pc.broadcastSmartMinimumIntervalSecs = UInt32(broadcastSmartMinimumIntervalSecs)
|
||||
pc.broadcastSmartMinimumDistance = UInt32(broadcastSmartMinimumDistance)
|
||||
pc.rxGpio = UInt32(rxGpio)
|
||||
pc.txGpio = UInt32(txGpio)
|
||||
var pf: PositionFlags = []
|
||||
if includeAltitude { pf.insert(.Altitude) }
|
||||
if includeAltitudeMsl { pf.insert(.AltitudeMsl) }
|
||||
|
|
|
|||
|
|
@ -189,11 +189,12 @@
|
|||
"module.configuration"="Modul Konfiguration";
|
||||
"mqtt"="MQTT";
|
||||
"mqtt.config"="MQTT Config";
|
||||
"mqtt.clientproxy"="MQTT Client Proxy";
|
||||
"mqtt.username"="Benutzername";
|
||||
"name"="Name";
|
||||
"network"="Netzwerk";
|
||||
"network.config"="Netzwerkeinstellungen";
|
||||
"nodes"="Nodes";
|
||||
"nodes %@"="Nodes (%@)";
|
||||
"no.nodes"="Keine Meshtastic Nodes gefunden";
|
||||
"not.connected"="Kein Gerät verbunden";
|
||||
"numbers.punctuation"="Ziffern und Interpunktion";
|
||||
|
|
|
|||
|
|
@ -189,11 +189,12 @@
|
|||
"module.configuration"="Module Configuration";
|
||||
"mqtt"="MQTT";
|
||||
"mqtt.config"="MQTT Config";
|
||||
"mqtt.clientproxy"="MQTT Client Proxy";
|
||||
"mqtt.username"="Username";
|
||||
"name"="Name";
|
||||
"network"="Network";
|
||||
"network.config"="Network Config";
|
||||
"nodes"="Nodes";
|
||||
"nodes %@"="Nodes (%@)";
|
||||
"no.nodes"="No Meshtastic Nodes Found";
|
||||
"not.connected"="No device connected";
|
||||
"numbers.punctuation"="Numbers and Punctuation";
|
||||
|
|
|
|||
|
|
@ -189,11 +189,12 @@
|
|||
"module.configuration"="模块配置";
|
||||
"mqtt"="MQTT";
|
||||
"mqtt.config"="MQTT 配置";
|
||||
"mqtt.clientproxy"="MQTT Client Proxy";
|
||||
"mqtt.username"="用户名称";
|
||||
"name"="名称";
|
||||
"network"="网络";
|
||||
"network.config"="网络配置";
|
||||
"nodes"="节点";
|
||||
"nodes %@"="节点 (%@)";
|
||||
"no.nodes"="未找到 Meshtastic 节点";
|
||||
"not.connected"="未连接到电台";
|
||||
"numbers.punctuation"="数字和标点符号";
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue